分布式锁问题
1. 分布式锁问题
1.1 问题介绍
- 单机单体中的锁机制在分布式集群系统中失效;
 - 单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问;
 
1.2 解决方案
1.2.1 分布式锁主流的实现方案
- 基于数据库实现分布式锁;
 - 基于缓存(Redis等),性能最高;
 - 基于Zookeeper,可靠性最高;
 
1.2.2 使用Redis实现分布式锁

- 使用set命令添加一个mutex key,同时为该key设置过时时间,并要求该key存在时无法更新数据;
 - 通过set命令设置的mutex key即为分布式锁,在该key过时或被删除以前,此key无法被其他请求获取到;
 - 具体可通过
set mutex_key uuid nx ex 过期时间进行加锁,其中mutex_key为锁名,value为一唯一值,过期时间以秒为单位; - 可通过
del mutex_key手动释放锁,但缺乏原子性,可通过lua脚本进行删除保证操作原子性;

 - set命令中使用UUID做value是为了解决该问题:当前服务器可能释放其他服务器的锁,而自身的锁也可能被其他服务器释放;
 - UUID是一个唯一值,用于标识不同的服务器;
 

 
1.2.3 分布式锁需要满足的四个条件
- 互斥性:在任意时刻,只有一个客户端能持有锁;
 - 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁;
 - 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了;
 - 加锁和解锁必须具有原子性;
 
1.3 实现分布式锁
- 基于SpringBoot框架,常用配置参考https://springdoc.cn/spring-boot/application-properties.html#application-properties.data.spring.data.redis.cluster.nodes;
 - 代码:
 
    // 分布式锁测试
    @RequestMapping("/lockTest")
    public void lockTest(){
        String uuid= UUID.randomUUID().toString();
        // 获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
        // 成功获取锁
        if (lock){
            // 逻辑业务
            Object value = redisTemplate.opsForValue().get("num");
            if(StringUtils.isEmpty(value)){
                return;
            }
            Long num = Long.parseLong(value+"");
            redisTemplate.opsForValue().set("num",num+1);
            // 手动释放锁
            String lockVal = (String) redisTemplate.opsForValue().get("lock");
            if (lockVal.equals(uuid)) {
                redisTemplate.delete("lock");
            }
            // 定义lua脚本
            String luaScript="if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1])" +
                    "else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(luaScript);
            redisScript.setResultType(Long.class);
            // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值
            redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
        }else{
            // 等待一会再重新尝试获取锁
            try {
                Thread.sleep(100);
                lockTest();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }










