分布式锁

1. 分布式锁作用

解决缓存击穿问题

2. 分布式锁思想

加锁:就是去存储一个数据,如果一个线程可以把数据存储成功,就说明当前线程获取到了锁;存储不成功,就说明当前线程没有获取到锁。
解锁:删除数据

3. 常用技术

mysql,redis,zookeeper是常用的分布式锁技术
加锁对性能有影响,最常用redis,读写效率高,对性能影响最小

4. 使用redis作为分布式锁的思想

获取锁的时候向Redis中插入数据,释放锁的时候从redis中删除数据
使用的命令:setnx

5. 代码示例

// 加锁
redisTemplate.opsForValue().setIfAbsent("lock", "1");

//解锁
redisTemplate.delete("lock");

6. 使用分布式锁,需要考虑的问题

问题一:执行业务的过程中产生异常,锁没有释放

解决方案:将释放锁的代码放到finally代码块中

问题二:释放锁前服务器宕机导致锁没有释放

解决方案:给锁设置过期时间,锁过期后会自动从redis中删除
代码示例:redisTemplate.expire("lock", 60, TimeUnit.SECONDS);

问题三:在给锁设置过期时间之前服务器宕机了

解决方案:保证加锁和设置过期时间的原子性操作
代码示例:redisTemplate.opsForValue().setIfAbsent("lock", "1", 300, TimeUnit.MILLISECONDS)

问题四:高并发情况下,释放别的线程的线程锁

解决方案:每一个线程在加锁的时候生成一个唯一的标识,把这个唯一的标识作为锁的值存储到redis中,在解锁的时候判断这个线程的标识和redis中存储的值是否一致,如果一致进行解锁操作。

问题五:判断锁之后,释放锁之前,锁过期了,导致释放了别的线程的锁

解决方案:保证判断和释放锁的原子性,使用lua脚本

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end
// 使用lua脚本保证释放锁的原子性
    String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
        "then\n" +
        "    return redis.call(\"del\",KEYS[1])\n" +
        "else\n" +
        "    return 0\n" +
        "end\n" ;

    // 执行lua脚本
    Long result = redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), Arrays.asList("lock"), uuid);
    if(result == 0) {
        log.error("锁是别人的,删除锁失败...");
    }else {
        log.info("锁是自己的,删除锁成功...");
    }

问题六:业务执行时长大于锁的过期时间,导致锁失效

解决方案:开启一个守护线程或创建定时任务,每隔一段时间将锁续期到初始过期时间(看门狗机制)
开启守护线程代码示例:

Thread thread = new Thread(() -> {
            while(true){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                redisTemplate.expire("lock", 30, TimeUnit.SECONDS)
            }
        });
        thread.setDaemon(true);   //设置该线程为守护线程
        thread.start();
posted @ 2023-08-09 16:34  摆烂ing  阅读(27)  评论(0)    收藏  举报