分布式锁的解决方案

分布式锁的要求:

a:互斥

b:宕机避免死锁

c:只能自己解锁

d 能具备公平性最好

1 数据库版本

https://blog.csdn.net/linsongbin1/article/details/79444274 

2 redis

3zookeeper

a:pom文件

<!-- 原生zk支持-->
 <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.3.6</version>
        </dependency>
<!-- zkclient原生客户端支持-->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>

b:必要参数采用yml配置注入

zklock:
  url: 127.0.0.1:2181
  rootPath: /newlock17

c: 创建锁工具类

要点介绍:

1:实现lock接口,开闭原则

2:构造函数中实例化ZkClient

3:  在根节点下创建临时顺序节点记得加分隔符“/”:

package com.test.domi.common.utils.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class ZKlock implements Lock {

    private ZkClient zkClient;
    private String rootPath;
    private String currentPath;
    private String beforePath;

    public ZKlock(String url,String rootPath){
        this.rootPath = rootPath;
        zkClient = new ZkClient(url);
        if (!zkClient.exists(rootPath)) {
            try {
                zkClient.createPersistent(rootPath);
            } catch (RuntimeException e) {
            }
        }
    }

    @Override
    public boolean tryLock() {
        if (currentPath == null) {
            currentPath = zkClient.createEphemeralSequential(rootPath + "/","aaa");
        }
        List<String> childrens = zkClient.getChildren(rootPath);
        Collections.sort(childrens);
        if (currentPath.equals(rootPath + "/" + childrens.get(0))) {
            return true;
        }else{
            int curIndex = childrens.indexOf(currentPath.substring(rootPath.length() + 1));
            beforePath = rootPath + "/" + childrens.get(curIndex - 1);
        }
        return false;
    }

    @Override
    public void lock() {
        if (!tryLock()) {
            waiForLock();
            lock();
        }
    }

    @Override
    public void unlock() {
        zkClient.delete(currentPath);
    }

    private void waiForLock(){
        CountDownLatch cdl = new CountDownLatch(1);
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {
            }
            @Override
            public void handleDataDeleted(String s) throws Exception {
                cdl.countDown();
            }
        };
        zkClient.subscribeDataChanges(beforePath,listener);
        if (zkClient.exists(beforePath)) {
            try {
                cdl.await();
            } catch (InterruptedException e) {
            }
        }
        zkClient.unsubscribeDataChanges(beforePath,listener);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }

}
currentPath = zkClient.createEphemeralSequential(rootPath + "/","aaa");

d: 将锁工具类交给Spring管理,这样就不需要每次new的时候传入客户端的连接ip,一次注入终生受用,

但是切记@Scope为多例,因为ZKLock里面的全局变量不能再多线程共享。

package com.test.domi.config;

import com.test.domi.common.utils.lock.ZKlock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class ZkLockConfig {

    @Value("${zklock.rootPath}")
    private String rootPath;
    @Value("${zklock.url}")
    private String url;

    @Bean
    @Scope("prototype")
    public ZKlock getZKlock(){
        return new ZKlock(url,rootPath);
    }
}

e : 使用方式:

要点:

1- zklock.unlock(); 一般要放在finally中
2- 重入之后一般需要判断业务是否执行过,已经执行过的应立即退出

package com.test.domi.controller;

import com.test.domi.common.utils.SpringContextUtil;
import com.test.domi.common.utils.lock.ZKlock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/zk")
public class ZKController {

private int k = 1;

@GetMapping("/lock")
public Boolean getLock() throws Exception{


for (int i = 0; i < 50; i++) {
new Thread(new Runnable() {
@Override
public void run() {
ZKlock zklock = SpringContextUtil.getBean(ZKlock.class);
zklock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获得锁,生成唯一的单据编号" + k++);
} catch (Exception e) {
e.printStackTrace();
}finally {
zklock.unlock();
}
}
}).start();
}

return true;
}
}
 
原文地址:https://www.cnblogs.com/domi22/p/9746181.html