首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >基于 redis 实现分布式锁思考

基于 redis 实现分布式锁思考

作者头像
Java小咖秀
修改2021-04-25 11:03:03
修改2021-04-25 11:03:03
6270
举报
文章被收录于专栏:Java冰冻三尺Java冰冻三尺

> 公众号:[Java小咖秀](https://t.1yb.co/jwkk),网站:[javaxks.com](https://www.javaxks.com)

> 作者 : 溪~ 源 ,链接: blog.csdn.net/xuan_lu/article/details/111600302

分布式锁

基于 redis 实现分布式锁思考几个问题???

synchronized 锁为什么不能应用于分布式锁?

synchronized 虽然能够解决同步问题,但是每次只有一个线程访问,并且 synchronized 锁属于 JVM 锁,仅适用于单点部署;然而分布式需要部署多台实例,属于不同的 JVM 线程对象

使用 redis 中 setnx 实现分布式锁。

代码语言:javascript
复制
//设置分布式锁
String lockKey = "product_001_key";
//语义:如何不存在则存入缓存中,且返回true;
//否则已存在,则返回false即加锁失败
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "product_001_lock");
if (!result) {
  //没有加锁成功,则返回提示等
}
try{
  
}catch() {
  
}finally{
  //释放锁
  stringRedisTemplate.delete(lockKey);
}

针对以上设置分布式锁思考一下问题?

1. 如果突然服务器宕机,那么必然造成锁无法释放,即造成死锁?

解决方案:设置超时时间。

代码语言:javascript
复制
//设置分布式锁
String lockKey = "product_001_key";
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "product_001_lock");
//设置锁超时时间30s
stringRedisTemplate.expire(lockKey,30, TimeUnit.SECONDS);
if (!result) {
  //没有加锁成功,则返回提示等
}
try{
  
}catch() {
  
}finally{
  //释放锁
  stringRedisTemplate.delete(lockKey);
}

2. 加锁和设置超时时间中间引起服务器宕机,则一样会导致死锁。

代码语言:javascript
复制
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "product_001_lock");
//------服务器宕机,则超时时间未设置成功-------
//设置锁超时时间30s
stringRedisTemplate.expire(lockKey,30, TimeUnit.SECONDS);

解决方案:原子性操作,即同时加锁和设置超时时间;

即上面的代码合并成一句操作:

代码语言:javascript
复制
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"product_001_lock", 30, TimeUnit.SECONDS)

3. 思考超时时间设置是否合理呢?即线程执行时间和锁超时时间并非一致。

场景:假设设置加锁超时时间 10s;

高并发场景下,线程 A 执行时间为 15s,redis 依据超时时间,将其线程 A 加的锁释放掉;然后线程 B 获取锁,并加锁成功,此时线程 A 执行结束,执行 finally 代码块就会将线程 B 加的锁释放。

解决方案:设置线程随机 ID,释放锁时判断是否为当前线程加的锁,即使存在线程 A 因线程执行时间超时被动释放其锁,但至少保证当前超时线程不会释放其他线程加的锁。但是面对线程执行时间大于设置的超时时间,也是会存在并发问题。

代码语言:javascript
复制
String lockKey = "product_001";
String clientId = UUID.randomUUID().toString();
//设置超时时间,且加锁和设置线程ID
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId, 30, TimeUnit.SECONDS)`
  
if (!result) {
  //没有加锁成功,则返回提示等
}
try{
  
}catch() {
  
}finally{
  //释放锁:加锁线程ID和当前执行线程ID相同,才允许释放锁
 if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
        stringRedisTemplate.delete(lockKey);
    }
} 

4. 上面场景解决方案:加锁续命即续线程锁超时时间

解决方案:加锁成功时,开启一个后台线程,每隔 10s(自定义)判断当前线程是否还持有锁,持有锁则再续命 30s 等

Redission 实现分布式锁

实现原理流程:

代码语言:javascript
复制
            String lockKey = "product_001";
        //获取锁对象,并未加锁
        RLock redissonLock = redisson.getLock(lockKey);
        try {
            // **此时加锁**,实现锁续命功能
            redissonLock.lock();
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); 
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); 
                System.out.println("扣减成功,剩余库存:" + realStock + "");
            } else {
                System.out.println("扣减失败,库存不足");
            }
        }finally {
          //释放锁
            redissonLock.unlock();
        }

总结

综上,设计实现分布式锁需要满足一下条件:

\1. 互斥性;在任意时刻,只有一个客户端能持有锁。

\2. 不能发生死锁;即使存在一个线程持有锁的期间崩溃而没有主动解锁,也能保证后续其他线程能加锁。

\3. 加锁和解锁必须是同一个线程。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分布式锁
    • synchronized 锁为什么不能应用于分布式锁?
    • 使用 redis 中 setnx 实现分布式锁。
    • 1. 如果突然服务器宕机,那么必然造成锁无法释放,即造成死锁?
    • 2. 加锁和设置超时时间中间引起服务器宕机,则一样会导致死锁。
    • 3. 思考超时时间设置是否合理呢?即线程执行时间和锁超时时间并非一致。
    • 4. 上面场景解决方案:加锁续命即续线程锁超时时间
    • Redission 实现分布式锁
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档