前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis常见坑及解决方案

Redis常见坑及解决方案

作者头像
关忆北.
发布2023-10-11 09:37:36
2300
发布2023-10-11 09:37:36
举报
文章被收录于专栏:关忆北.
非原子操作(setNx+expire)

代码语言:javascript
复制
//加锁
if(jedis.setnx(lock_key,lock_value) == 1){ 
  //设置过期时间
    jedis.expire(lock_key,timeout);
  //业务逻辑处理
    doBusiness 
}

以上使用Redis的setNx()命令和expire命令实现了加锁,但是本方案是分成了两步完成的加锁操作,并不是原子操作,可能会出现未给该key设置过期时间的问题,因此该问题的解决方案推荐使用Redisson的分布式锁。

解决方案-Redission
  • Redisson 提供的分布式锁功能是原子操作的。Redisson 内部使用了 Redis 的 Lua 脚本来执行获取锁和释放锁的操作,确保这些操作的原子性。
  • 自动续期:Redisson 实现了分布式锁的自动续期功能。在获取锁时,Redisson 会设置一个过期时间,并周期性地对锁进行续期。这样可以确保在锁的持有者在任务执行期间出现阻塞或延迟时,锁不会过期而被其他进程获取,从而保证任务的完整性和准确性。
  • 可重入性支持:Redisson 支持分布式锁的可重入性。即同一个线程可以多次获取同一个锁,而不会产生死锁。Redisson 使用锁的持有者标识和计数来判断锁的重入次数,从而确保同一个线程能够正确地释放锁。
  • 高可用支持:Redission可以与Redis的主从复制或集群模式配合使用,当主节点发生故障时,Redission可以自动切换到可用的从节点或其他节点上,确保分布式锁的可用性和稳定性。

Redisson pom坐标:

代码语言:javascript
复制
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.1</version>
</dependency>

代码实现:

代码语言:javascript
复制
@Service
public class PurchaseService {
    private final RedissonClient redissonClient;

    public PurchaseService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    private int a = 9;

    @Transactional(rollbackFor = Exception.class)
    public boolean purchase(String productId, int quantity) {
        String lockKey = "lock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock(); // 加锁

            // 检查库存是否充足
            int stock = getStockFromDatabase(productId);
            if (stock >= quantity) {
                // 执行下单操作
                // ...

                // 扣减库存
                decreaseStockInDatabase(productId, quantity);

                if (true){
                    throw new RuntimeException("1");
                }
                return true; // 抢购成功
            } else {
                return false; // 库存不足,抢购失败
            }

        } catch (Exception e) {
            throw new RuntimeException(e); // 抛出异常,触发事务回滚
        } finally {
            lock.unlock(); // 释放锁
            int stockFromDatabase = getStockFromDatabase("1");
            System.out.println("下单后" + stockFromDatabase);
        }
    }

    private int getStockFromDatabase(String productId) {
        // 从数据库获取库存信息
        // ...
        return a;
    }

    private void decreaseStockInDatabase(String productId, int quantity) {
        // 更新数据库中的库存信息
        // ...
        a = a - quantity;
        System.out.println("更新数据库,下单减库存后" + a);
    }

}
B的锁被A给释放了
代码语言:javascript
复制
try{
  if(jedis.set(lockKey, requestId, "NX", "PX",expireTime)==1){//加锁
     doBusiness //业务逻辑处理
     return true; //加锁成功,处理完业务逻辑返回
  }
  return false; //加锁失败
} finally {
    unlock(lockKey); //释放锁
} 

业务场景:

并发情况下,A、B两个线程尝试在Redis设置key为lockKey的锁,key三秒后过期,A线程先拿到锁,若A的网络较慢或执行逻辑较为耗时,超过了三秒未执行完成,那么在Redis中,lockKey由于到达过期时间而过期,那么B此时尝试加锁,由于A过期,那么B此时刚好可以拿到锁,而后,A此时正好执行完业务逻辑去释放锁,此时B的业务逻辑还在执行,但是B加的锁被B释放了。

解决方案
代码语言:javascript
复制
try{
  if(jedis.set(lockKey, requestId, "NX", "PX",expireTime)==1){//加锁
     doBusiness //业务逻辑处理
     return true; //加锁成功,处理完业务逻辑返回
  }
  return false; //加锁失败
} finally {
    if (requestId.equals(jedis.get(lockKey))) { //判断一下是不是自己的requestId
      unlock(lockKey);//释放锁
    }   
}  

在设置锁时,设置value值为当前线程客户端的唯一标识。

释放锁时,不是原子性
代码语言:javascript
复制
if(requestId.equals(jedis.get(lockKey))) { //判断一下是不是自己的requestId
   unlock(lockKey);//释放锁
  }  

jedis.get(lockKey)unlock(lockKey)是两个操作,在get操作时,该锁可能被其他线程释放掉,或者是该锁被其他客户端获取到,在执行至unlock()时,释放掉其他客户端设置的锁。

解决方案

Redis+Lua脚本以保证释放锁需要原子性

代码语言:javascript
复制
if redis.call('get',KEYS[1]) == ARGV[1] then 
   return redis.call('del',KEYS[1]) 
else
   return 0
end;  

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 非原子操作(setNx+expire)
  • 解决方案-Redission
  • B的锁被A给释放了
  • 解决方案
  • 释放锁时,不是原子性
  • 解决方案
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档