分布式锁三种实现方式

主流分布式锁实现基本都是基于redis或zookeeper,下面是它的三种实现方式和对比:

基于Redis

1)单机版:
加锁:set(key,threadId,30,NX)(2.6以上支持)
解锁: 不可以直接del,因为有可能解锁之前锁已经过期了,然后是别人上的锁,所以上面的threadId起作用了,可以先验证threadId是否相同是否是自己的锁,可以用lua脚本保持原子性如下:

if redis.call("get",KEYS[1])==ARGV[1] then
  return redis.call("del",KEYS[1])
else
  return 0
end

上面的版本还不算完美,还有一种情况,有可能A线程的锁过期了,但A还在执行,此时又有另一个B线程加上锁了,这个时候就会出现同时有两个线程在执行,所以更完美的写法是A线程在执行的时候,同时需要给锁续期,可以启用一个守护线程给A的锁续期,完整的代码如下:

@Service
public class RedisLockService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private ReentrantLock reentrantLock = new ReentrantLock();

    public void lock(String lockKey,String owner) {
        boolean r = false;
        do {
            long s = System.currentTimeMillis();
            reentrantLock.lock();//优化,避免高并发下同时大量线程空转消耗cpu资源,影响了整体的性能
            r = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, owner, 30, TimeUnit.SECONDS);

            if (r) {

                Thread thread = new Thread(() -> {
                    long e = System.currentTimeMillis();

                    for (;;) {
                        if(Thread.interrupted()) {
                            break;
                        }
                       try {
                           stringRedisTemplate.expire(lockKey, 30, TimeUnit.SECONDS);
                       } catch (Exception e1) {
                           //下面unlock方法中thread.interrupt();会导致stringRedisTemplate内部捕获到抛出异常
                           break;
                       }
                        LockSupport.parkNanos(10000000000l);
                    }
                });
                thread.setDaemon(true);
                thread.start();

                {

                    System.out.println("我拿到了锁:" + lockKey + ",owner:" + owner);
                    unlock(lockKey,owner,thread);
                }
            } else {

            }
        }while (!r);

    }

    DefaultRedisScript<List> getRedisScript = null;
    public RedisLockService() {
        getRedisScript = new DefaultRedisScript<List>();
        getRedisScript.setResultType(List.class);
        //getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("luascript/lock.lua")));
        getRedisScript.setScriptText("if redis.call(\"get\",KEYS[1])==ARGV[1] then\n" +
                "  return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "  return 0\n" +
                "end");
    }

    public void unlock(String lockKey,String owner,Thread thread) {
        try {
            thread.interrupt();
            stringRedisTemplate.execute(getRedisScript, Arrays.asList(lockKey),owner);
            reentrantLock.unlock();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2)redLock红锁
​ redLock是redis官方给出的分布式锁解决方案,redlock的算法描述就放在Redis的官网上:https://redis.io/topics/distlock,解决上面单机可靠性差(如:宕机)的问题,如果主从模式下中主服务宕机了,而主服务器没有把锁信息同步到从服务器或者同步到从服务器后过期了都会有问题,**redLock的原理是使用n台单个redis服务器,尝试向每一台redis服务器加锁,如果有n/2+1个结点成功了,则表明此次加锁成功,否则失败。某一台宕机不会影响加锁,或直接加锁失败。解锁向每台服务器发出解锁操作.

使用redisson开源工具实现redLock:

 <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.3.2</version>
        </dependency>

首先引入上面的jar包

@Configuration
public class RedisConfig {
    @Bean
    public RedissonClient redissonRed1(){
        Config config = new Config();
        config.useSingleServer().setAddress("192.168.0.109:6379").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean
    public RedissonClient redissonRed2(){
        Config config = new Config();
        config.useSingleServer().setAddress("192.168.0.110:6379").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean
    public RedissonClient redissonRed3(){
        Config config = new Config();
        config.useSingleServer().setAddress("192.168.0.111:6379").setDatabase(0);
        return Redisson.create(config);
    }


}

配置三台redis地址

@Service
public class RedLock {
    @Autowired
    private RedissonClient redissonRed1;
    @Autowired
    private RedissonClient redissonRed2;
    @Autowired
    private RedissonClient redissonRed3;


    public void lock(String lockKey) {
        // RLock默认设置key 超时时间30秒,过10秒再延时
        RLock rLock1 = redissonRed1.getLock(lockKey);
        RLock rLock2 = redissonRed2.getLock(lockKey);
        RLock rLock3 = redissonRed3.getLock(lockKey);

        RedissonRedLock rLock = new RedissonRedLock(rLock1,rLock2,rLock3);
        rLock.lock();
       
        try {
             System.out.println("获得锁:"+lockKey);
        } finally {
        	rLock.unlock();
        }
    }
}

分别向三台redis加锁,rLock.lock()返回表示有至少3/2+1=2台结点成功了

基于zookeeper

zookeeper可以实现多种类型的锁,公平锁,非公平锁,读写锁等。zookeeper相比redis功能更强大,更灵活。
1)公平锁:
1.基于CLH队列实现,当前节点监听前面节点的状态
2.Sequential EPHEMERAL Node 序列化的临时节点,随着client的session消失而删除
​ 3.watch 监听节点删除事件

package com.zookeeper.lock;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

public class ZkLock {
    public String lockName;
    public static byte[] lockValue = "0".getBytes();

    public ZkLock(String lockName) {
        this.lockName = lockName;
    }

    public  String lock(ZooKeeper zooKeeper) throws Exception {
        String path = zooKeeper.create("/"+lockName,lockValue, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);

        while(true) {
            List<String> locks = zooKeeper.getChildren("/", false, new Stat());
            Collections.sort(locks);
            int i = locks.indexOf(path.substring(1));

            if (i == 0) {
                break;
            } else {
                Thread thread = Thread.currentThread();
                //System.out.println("我是:"+path+",我在监听:"+"/" + locks.get(i - 1));
                Stat stat = zooKeeper.exists("/" + locks.get(i - 1), new WatcherExists(zooKeeper, path, thread));
                if(stat == null) {
                    //注意:这里必须要判断stat==null,是因为有可能前面的节点释放锁很快,在监听之前就已经删了,所以一定要看前面节点是否存在
                    zooKeeper.removeAllWatches("/" + locks.get(i - 1), Watcher.WatcherType.Any,false);
                    System.out.println("前面的节点不存在");
                    continue;
                }
                LockSupport.park();
                //System.out.println(path + "被唤醒了");
            }
        }
        return path;
    }

    class WatcherExists implements Watcher{
        private ZooKeeper zooKeeper;
        private String path;
        private Thread thread;

        public WatcherExists(ZooKeeper zooKeeper, String path, Thread thread) {
            this.zooKeeper = zooKeeper;
            this.path = path;
            this.thread = thread;
        }

        @Override
        public void process(WatchedEvent event) {
            //System.out.println(event);
            switch (event.getType()) {
                case NodeDeleted:
                    try {
                        List<String> locks = null;
                        locks = zooKeeper.getChildren("/", false);
                        Collections.sort(locks);
                        int i = locks.indexOf(path.substring(1));
                        if(i == 0) {
                            LockSupport.unpark(thread);
                        }
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    }


    public static void main(String[] args) throws Exception {
        final ZkLock zkLock = new ZkLock("lockk");

        int size = 20;
        Thread[] threads = new Thread[size];

        for(int i = 0;i<size;i++) {
            final int j = i;
            threads[i] = new Thread(()->{
               /* if(j != 0) {
                    try {
                        threads[j-1].join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }*/
                try {
                    ZooKeeper zooKeeper = ZkConnection.getZookeeper();
                    String path = zkLock.lock(zooKeeper);
                    //System.out.println(path+":::::"+j);
                    zkLock.unlock(zooKeeper,path);
                    zooKeeper.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }

        for(int i = 0;i<size;i++) {
            threads[i].join();
        }

    }

    private void unlock(ZooKeeper zooKeeper,String path) {
        try {
            zooKeeper.delete(path,0);
            //System.out.println("释放锁:"+path);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
}

2)非公平锁:

​ 类似redis实现

公平锁锁竞争:基于CLH自旋锁队列排队等待获取锁,每次释放锁发送event事件,只发送给后面的节点,只会唤起单个client。
​非公平锁竞争: 所有节点监听同一个节点锁状态,每次释放锁event会发送给所有节点,引起所有client竞争

三种分布式锁性能测试对比

第一组:100线程,循坏执行10次**
zookeeper:
zookeeper
单机redis:
单机redis
3台redis组成redlock:
3台redis组成redlock
第二组:200线程,循坏执行10次
zookeeper:
zookeeper
单机redis:
单机redis3台redis组成redlock:
在这里插入图片描述

第三组:500线程,循环执行10次
zookeeper:
zookeeper

单机redis:
单机redis

3台redis组成redlock:
台redis组成redlock

结论:
性能:redis>redlock>zookeeper
​ 可靠性: zookeeper>redlock>redis
​ 复杂度:redis>redlock>zookeeper
​ zookeeper还可以实现读写锁,公平非公平锁等
使用场景:
高并发下推荐还是用redis,一个字,快!可靠性不要求那么强的话选择单机redis。zookeeper并发不高的情况下也可,而且它比较灵活,可以实现公平锁,读写锁之类不同的锁(能按照你能想到的方式)。还是那句话,场景决定一切,多做测试!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值