基于redis实现分布式锁(SETNX和Redisson)(以扣减库存来当作案例)

一:实现原理:

利用redis中的set命令来实现分布式锁。

从Redis 2.6.12版本开始,set可以使用下列参数:

SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

EX second :设置键的过期时间为second秒。 SET key value EX second效果等同于SETEX key second value 。
PX millisecond :设置键的过期时间为millisecond毫秒。 SET key value PX millisecond效果等同于PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX效果等同于SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。

二:SETNX
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁,比如setnx name xiaowang。

当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

返回值:设置成功,返回 1 。设置失败,返回 0 。

三:接下来我们以一个案例来看看,如何使用redis实现分布式锁

@RestController
public class IndexController {
  //  private static final Logger LOGGER= LoggerFactory.getLogger(IndexController.class);
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/deduct_stock")
    public String deductStock(){
        int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
        if(stock>0){
            int realStock=stock-1;
            stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
        }else{
            System.out.println("扣减失败,库存不足");
        }
        return "end";
    }
}

这是一个经典的库存扣减的案例,每实现一次则库存减一:

这是一个经典的库存扣减的案例,每实现一次则库存减一:

在这里我们使用的是springboot整合redis来实现的,注入了springboot操作redistribution的模板。当然不清楚也没关系,我在每个关键的代码后面都用jedis解释过了。

比如这个去设置key,value的操作,jedis.set(key,value);

stringRedisTemplate.opsForValue().set(“stock”,realStock+"");//jedis.set(key,value)
1:接下来我们具体看看这段代码,首先能不能看出这段代码有没有什么问题?

可以看出是由并发问题,如果有很多个线程来执行这段代码,有很多请求,如果有三个用户同时访问这段代码的话,如果有50件商品,如果同时发生的话,有可能最后返回的是49,按理来说应该47,出现了并发问题,那么怎么解决呢?

2:解决方案:添加synchronized锁能否解决问题呢?

如果在单体架构中的话,添加synchronized是没有问题的。但如果在集群架构中的话,这个war包放在多个tomcat上面的话,jdk为我们提供了很多锁,比如synchronized锁或者lock锁都是在jvm进程上的,只在一个tomcat上面有用,如果通过nginx发放到多个tomcat上面的话,实际上都是锁不住的。

3:使用redis的SETNX实现分布式锁

当有很多请求发过来时,会一个一个请求来执行,因为redis是单线程架构。

问题1:stringRedisTemplate.delete(lockKey);//释放点这个锁,如果在这段代码出现异常了的话, 这个锁没有释放掉,别的线程进来的话拿不到锁,会陷入一种死锁状态。

解决办法:给这段代码加异常进行处理,让他最后都能释放掉这把锁,不要陷入死锁的情况中。

问题2:如果还没有释放掉锁时,系统突然宕机了,finally后面的代码执行不了了,怎么办呢?

解决办法:可以给这个key设置10秒钟的过期时间,当时间到了的时候,redis会自动删除这个key。

问题3:在超高并发场景下,我们设置key’的过期时间是10秒,如果第一个线程过来会执行15秒的话,在高并发的情况下,很有可能会出现第一个线程释放掉了第二个线程的锁,而导致锁失效。

解决办法:我们给每一个线程生成一个不重复的字符串,在释放的锁的时候去验证一下是不是我们这个线程自己的锁

问题4:如果我们代码执行了10秒钟的话,这个key已经过期了,代码还没有执行完,这时应该怎么解决的?

解决办法:我们可以在这个key过期后去演演唱这个key的生命时间去给他续命,我们可以用redisson去实现。

@Autowired
    private StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/deduct_stock")
    public String deductStock(){
        String lockKey="lockKey";
        String redisId= UUID.randomUUID().toString();
       try {  //问题一
           Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"lockkey",10, TimeUnit.SECONDS);//问题二
           if(!result){
               return "err";
           }
           int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
           if(stock>0){
               int realStock=stock-1;
               stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
           }else{
               System.out.println("扣减失败,库存不足");
           }
       }finally {
           if(stringRedisTemplate.opsForValue().get(lockKey).equals(redisId)){  //问题三
               stringRedisTemplate.delete(lockKey);//释放点这个锁
           }   }
    return "end";
}

四:使用Redisson实现分布式锁
Redisson就是redis的一个开源框架,这个框架就已经将上述的问题都封装好了,直接使用就行了

@RestController
public class IndexController {
    @Autowired
    private Redisson redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/deduct_stock")
    public String deductStock(){
        String lockKey="lockKey";
        RLock redisId=redisson.getLock(lockKey);//拿到锁对象
       try {  //问题一
           redisId.lock();//j加锁
           int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
           if(stock>0){
               int realStock=stock-1;
               stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
           }else{
               System.out.println("扣减失败,库存不足");
           }
       }finally {
               redisId.unlock();
   }
    return "end";
}

}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis分布式锁实现可以使用RedisSETNX命令(SET if Not eXists)来完成。SETNX命令在键不存在时设置键的值,如果键已经存在则不执行任何操作。 以下是使用SETNX命令实现Redis分布式锁的示例代码(使用Python语言): ```python import redis def acquire_lock(redis_conn, lock_key, expire_time): # 使用SETNX命令尝试获取锁 lock_acquired = redis_conn.setnx(lock_key, 1) if lock_acquired: # 设置锁的过期时间 redis_conn.expire(lock_key, expire_time) return True else: return False def release_lock(redis_conn, lock_key): # 释放锁,删除键 redis_conn.delete(lock_key) # 创建Redis连接 redis_conn = redis.Redis(host='localhost', port=6379, db=0) # 获取锁 if acquire_lock(redis_conn, 'mylock', 10): try: # 执行业务逻辑 print("Lock acquired. Do something here...") finally: # 释放锁 release_lock(redis_conn, 'mylock') else: print("Failed to acquire lock. Another process holds the lock.") ``` 以上代码中,`acquire_lock`函数尝试获取锁并设置过期时间,如果成功获取到锁则返回True,否则返回False。`release_lock`函数用于释放锁,即删除键。 在使用分布式锁时,可以根据具体业务需求设置合适的锁键(`lock_key`)和过期时间(`expire_time`)。确保在获取到锁之后,执行业务逻辑的代码在适当的位置调用`release_lock`函数释放锁,避免锁一直被占用而无法释放。 需要注意的是,RedisSETNX命令是原子操作,可以确保在并发情况下只有一个客户端能够成功获取到锁。同时,设置合适的过期时间能够避免因为异常情况导致锁一直被占用而无法释放。 这只是一个简单的示例,实际使用中还需要考虑异常处理、加锁失败的重试策略以及其他线程安全的因素,以确保分布式锁的可靠性和性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值