分布式锁的几种实现方式

github源码下载

一.分布式锁的几种实现方式和对比

  实现思路 优点 缺点
利用MySQL数据库实现

利用数据库自身提供的锁

要求数据库支持行级锁

实现简单,稳定可靠

性能差,挖坟适应高并发场景

容易出现死锁的的情况

无法优雅的实现阻塞式锁

利用缓存(redis)的实现方案

基于redis的setnx命令实现

并通过lua脚本保证解锁时对缓存操作序列的原子性

性能好

实现相对复杂

又出现死锁的可能性

无法优雅的实现阻塞式锁

利用zookeeper的实现方案 基于zk节点特性以及watch机制实现 性能好,稳定可靠性,能较好的实现阻塞式锁 实现相对复杂

    定义一个接口:

package com.roger.lock;

import java.util.concurrent.TimeUnit;

public interface DistributeLock {

    void lock(String lockKey,String lockValue);

    boolean tryLock(String lockKey,String lockValue);

    boolean tryLock(String lockKey,String lockValue,long time, TimeUnit timeUnit);

    boolean unLock(String lockKey,String lockValue);

}

 

二.分布式锁的具体实施方案

(1)利用MySQL数据库实现

实现思路:根据数据库表的主键唯一性特点,实现分布式锁,只要当insert成功后,才能获取到锁

package com.roger.lock.impl;

import com.roger.entity.DbDistriLock;
import com.roger.lock.DistributeLock;
import com.roger.mapper.DistributeLockMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service("dbLock")
public class DbLock implements DistributeLock {

    @Autowired
    private DistributeLockMapper distributeLockMapper;

    @Override
    public void lock(String lockKey, String lockValue) {
       if(!tryLock(lockKey,lockValue)){
           //无法优雅的阻塞自己 -- 线程沉睡500ms
           waitForLock();
           //再次去尝试获取锁,直到获取成功
           lock(lockKey,lockValue);
       }
    }

    private void waitForLock() {
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {

        }
    }

    @Override
    public boolean tryLock(String lockKey, String lockValue) {
        DbDistriLock dbDistriLock = new DbDistriLock();
        dbDistriLock.setId(lockKey);
        dbDistriLock.setDistributeLockName(lockValue);
        try {
            distributeLockMapper.insertDistributeLock(dbDistriLock);
        }catch (Exception e){
            return false;
        }
        return true;
    }

    @Override
    public boolean tryLock(String lockKey, String lockValue, long time, TimeUnit timeUnit) {
        if(tryLock(lockKey,lockValue)){
            return true;
        }

        if(time <= 0){
            return false;
        }

        long nanoTimes = timeUnit.toNanos(time);
        long deadlineTime = System.nanoTime() + nanoTimes;
        for (;;){
            if(tryLock(lockKey,lockValue)){
                return true;
            }

            nanoTimes = deadlineTime - System.nanoTime();
            if(nanoTimes <= 0){
                return false;
            }
        }
    }

    /**
     * 解铃还须系铃人,避免高并发情况下,误解锁的情况
     * @param lockKey
     * @param lockValue
     * @return
     */
    @Override
    public boolean unLock(String lockKey, String lockValue) {
        DbDistriLock dbDistriLock = new DbDistriLock();
        dbDistriLock.setId(lockKey);
        dbDistriLock.setDistributeLockName(lockValue);
        int delNum = distributeLockMapper.delDistriLockByObject(dbDistriLock);
        if(delNum == 1){
            //解锁成功
            return true;
        }
        return false;
    }
}

(2)利用redis实现 

实现思路:redis中set命令,NX模式,如果已经存在则不能插入成功,只有当不存在的情况下,才能插入成功

package com.roger.lock.impl;

import com.roger.lock.DistributeLock;
import com.roger.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Service("redisLock")
public class RedisLock implements DistributeLock{

    //key是唯一的
    private static final String LOCK_KEY = "lockKey";
    //有key是唯一的前提,配上这个参数,保证在同一时间,只能有一个客户端持有锁
    private static final String SET_IF_NOT_EXIST = "NX";
    //加锁的值不能是固定值,因为要保证可靠性
    //哪个客户端加的锁,需要哪个客户端解锁
    //自己不能把别人的锁给解了
    private static final String LOCK_VALUE = "lockValue";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final String LOCK_SUCCESS = "OK";
    private static final Long RELEASE_SUCCESS = 1L;
    //保证不会发生死锁的情况,即使有一个客户端在持有锁期间崩溃,
    //但是没有主动解锁,到了过期时间,锁会自动解开
    private static final long EXPIRE_TIME = 180 * 1000;

    @Override
    public void lock(String lockKey,String reqestId) {
        if(!tryLock(lockKey,reqestId)) {
            //无法优雅的阻塞自己 -- 线程沉睡500ms
            waitForLock();
            //再次尝试获取锁
            lock(lockKey, reqestId);
        }
    }

    private void waitForLock() {
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {

        }
    }

    @Override
    public boolean tryLock(String lockKey,String reqestId) {
        //多线程环境,不能使用单例模式的jedis实例
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        String retResult = jedis.set(lockKey, reqestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, EXPIRE_TIME);
        if (LOCK_SUCCESS.equals(retResult)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean tryLock(String lockKey,String requestId, long time, TimeUnit timeUnit) {
        if (tryLock(lockKey,requestId)) {
            return true;
        }
        if (time < 0) {
            return false;
        }
        long nanoTimeout = timeUnit.toNanos(time);
        long deadLine = System.nanoTime() + nanoTimeout;
        for (; ; ) {
            if (tryLock(lockKey,requestId)) {
                return true;
            }
            nanoTimeout = deadLine - System.nanoTime();
            if (nanoTimeout <= 0) {
                return false;
            }
        }
    }

    @Override
    public boolean unLock(String lockKey,String requestId) {
        StringBuffer scriptBuffer = new StringBuffer();
        scriptBuffer.append(" if ");
        scriptBuffer.append(" redis.call('get',KEYS[1]) == ARGV[1] ");
        scriptBuffer.append(" then ");
        scriptBuffer.append(" return redis.call('del',KEYS[1]) ");
        scriptBuffer.append(" else ");
        scriptBuffer.append(" return 0 ");
        scriptBuffer.append(" end");
        //多线程环境,不能使用单例模式的jedis实例
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        Object result = jedis.eval(scriptBuffer.toString(), Collections.singletonList(lockKey), Collections.singletonList(requestId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

}

遇到的问题:

redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Connection reset

产生的原因:在高并发环境下使用单例模式创建Jedis连接导致的

(3)利用zookeeper的实现

                  1)zookeeper的四种节点及其特性

                       A:PERSISTENT 持久化节点

                      B:PERSISTENT_SEQUENTIAL 顺序自动编号持久化节点,这种节点会根据当前已存在的节点自动加1

                     C:EPHEMERAL 临时节点,客户端session超时 这类节点就会被自动删除

                     D:EPHEMERAL_SEQUENTIAL 临时自动编号节点

               2)zookeeper的第一种实现方式

                   这种实现方案:会出现一种惊群现象

                   流程图:

分布式锁的几种实现方式_第1张图片

package com.roger.lock.impl;

import com.roger.lock.DistributeLock;
import com.roger.utils.ZkClientUtil;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.springframework.stereotype.Service;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Service("zkLockV1")
public class ZkLockV1 implements DistributeLock {

    @Override
    public void lock(String lockKey, String lockValue) {
        if(!tryLock(lockKey,lockValue)){
            //优雅的阻塞自己
            waitForLock(lockKey);
            //再次尝试获取锁,直到获取锁成功
            lock(lockKey,lockValue);
        }
    }


    @Override
    public boolean tryLock(String lockKey, String lockValue) {
        try {
            ZkClientUtil.getInstance().createEphemeral(lockKey, lockValue);
        }catch (ZkNodeExistsException e){
            return false;
        }
        return true;
    }

    @Override
    public boolean tryLock(String lockKey, String lockValue, long time, TimeUnit timeUnit) {
        if(tryLock(lockKey,lockValue)){
            return true;
        }

        if(time <= 0){
            return false;
        }

        long nanoTimes = timeUnit.toNanos(time);
        long deadlineTime = System.nanoTime() + nanoTimes;
        for (;;){
            if(tryLock(lockKey,lockValue)){
                return true;
            }

            nanoTimes = deadlineTime - System.nanoTime();
            if(nanoTimes <= 0){
                return false;
            }
        }
    }

    @Override
    public boolean unLock(String lockKey, String lockValue) {
        String oldValue = ZkClientUtil.getInstance().readData(lockKey);
        if(lockValue.equals(oldValue)){
            return ZkClientUtil.getInstance().delete(lockKey);
        }
        return false;
    }

    private void waitForLock(String lockKey) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        //注册监听事件
        IZkDataListener iZkDataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                //当节点被删除后,唤醒阻塞中的线程,再次竞争共享资源
                countDownLatch.countDown();
            }
        };
        ZkClientUtil.getInstance().subscribeDataChanges(lockKey,iZkDataListener);
        //注册完成后,判断节点是否依然存在
        // 如果依然存在,则阻塞线程
        if(ZkClientUtil.getInstance().exists(lockKey)){
            try {
                countDownLatch.await();
            }catch (InterruptedException e){

            }
        }
        //取消监听事件
        ZkClientUtil.getInstance().unsubscribeDataChanges(lockKey,iZkDataListener);

    }
}

       3)zookeeper的另一种实现方案,避免出现惊群现象,使用创建临时顺序节点的方案

   流程图

 

分布式锁的几种实现方式_第2张图片

package com.roger.lock.impl;

import com.roger.lock.DistributeLock;
import com.roger.utils.ZkClientUtil;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 这种锁的使用方法不能是单例的
 *      每个线程必须有自己的锁对象,即线程独享对象
 */
public class ZkLockV2 implements DistributeLock {

    //当前节点
    private String currentPath;
    //上一个节点
    private String previousPath;

    @Override
    public void lock(String lockKey, String lockValue) {
        if(!tryLock(lockKey,lockValue)){
            //优雅的阻塞自己
            waitForLock(lockKey);
            //再次尝试获取锁
            lock(lockKey,lockValue);
        }
    }

    @Override
    public boolean tryLock(String lockKey, String lockValue) {
        //如果lockKey节点不存在,则创建要给持久节点
        //用来存数临时顺序节点
        if(!ZkClientUtil.getInstance().exists(lockKey)){
            try {
                ZkClientUtil.getInstance().createPersistent(lockKey);
            }catch (ZkNodeExistsException e){
                //高并发情况下,可能有多个线程同时进入创建持久节点
                //因此这里需要捕获异常,不需做任何处理
            }
        }

        //如果currentPath为null,则该线程还没有进入阻塞状态
        //即是第一次尝试获取锁
        if(currentPath == null) {
            //创建当前临时顺序节点
            currentPath = ZkClientUtil.getInstance().createEphemeralSequential(lockKey + "/", lockValue);
            currentPath = currentPath.substring(currentPath.lastIndexOf("/")+1);
        }

        List childNodeList = ZkClientUtil.getInstance().getChildren(lockKey);
        Collections.sort(childNodeList);
        String minNodePath = childNodeList.get(0);
        if(minNodePath.equals(currentPath)){
            return true;
        }
        previousPath = childNodeList.get(childNodeList.indexOf(currentPath) - 1);
        return false;
    }

    @Override
    public boolean tryLock(String lockKey, String lockValue, long time, TimeUnit timeUnit) {
        if(tryLock(lockKey,lockValue)){
            return true;
        }

        if(time <= 0){
            return false;
        }

        long nanoTimes = timeUnit.toNanos(time);
        long deadlineTime = System.nanoTime() + nanoTimes;
        for (;;){
            if(tryLock(lockKey,lockValue)){
                return true;
            }

            nanoTimes = deadlineTime - System.nanoTime();
            if(nanoTimes <= 0){
                return false;
            }
        }
    }

    @Override
    public boolean unLock(String lockKey, String lockValue) {
        String oldValue = ZkClientUtil.getInstance().readData(lockKey + "/" + currentPath);
        if(lockValue.equals(oldValue)){
            return ZkClientUtil.getInstance().delete(lockKey + "/" + currentPath);
        }
        return false;
    }

    private void waitForLock(String lockKey) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        //注册监听事件
        IZkDataListener iZkDataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                //当节点被删除后,唤醒阻塞中的线程,再次竞争共享资源
                countDownLatch.countDown();
            }
        };
        ZkClientUtil.getInstance().subscribeDataChanges(lockKey + "/" + previousPath,iZkDataListener);
        //注册完成后,判断节点是否依然存在
        // 如果依然存在,则阻塞线程
        if(ZkClientUtil.getInstance().exists(lockKey + "/" + previousPath)){
            try {
                countDownLatch.await();
            }catch (InterruptedException e){

            }
        }
        //取消监听事件
        ZkClientUtil.getInstance().unsubscribeDataChanges(lockKey + "/" + previousPath,iZkDataListener);

    }
}

 

你可能感兴趣的:(Java,redis,zookeeper)