redistemplate分布式锁实现,spring boot redis分布式锁

  redistemplate分布式锁实现,spring boot redis分布式锁

  00-1010 1.使用场景2。锁定解决方案3。分布式锁4。增加到期时间5。增加线程唯一值6。Lua脚本7。Lua如何实现原子性8?代码演示9。摘要

  00-1010要直接获取锁定和解锁代码,请直接进入代码。

  为了减少库存,我们一般会查询库存,扣除库存。

  @ get mapping(value= order )public R order(){ int stock=redis util . getobject( stock ,integer . class);if(stock 0){ redistutil . set( stock ,-stock);} return R.ok(股票);}上面的操作看起来很正常,其实是有问题的。想象一下,当我们有两个线程同时访问这个接口时会发生什么。

  线程1查询库存结果为100

  Thread-2也来查询库存。此时Thread-1还没有进行减库存操作,Thread-2查询库存的结果也是100。

  线程-1集合库存为99

  线程2设置库存为99

  这个有问题。明天会扣两次库存,但是库存只会减一次。

  在使用Idea时,我们可以右击断点将Suspend调整为Thread,只阻塞线程,同时使用多个客户端请求接口,可以重现上述过程。

  

目录

synchronized我们可以使用Java提供的synchronized关键字以分布式方式锁定方法。分布式锁的实现方案有很多,比如zookeeper,redis,db。这里我们使用redis来实现以下分布式锁

 

  00-1010正是因为【查询库存】和【扣除库存】不是一个原子操作,所以上面两个线程没有同时正确扣除库存。我们增加了一个锁机制,允许线程持有锁时的[查询库存]和[扣除库存]。redis有一个sexNx命令,允许在指定的键不存在时进行set操作。在java中,它是redisTemplate的setIfAbsent方法。此方法确保同一时间只有一个线程可以成功设置。当set成功时,意味着我们已经获得了锁,可以执行原子操作。当我们完成原子操作时,我们还需要释放锁。在Redis实现中,我们删除键并允许下一个线程设置值。锁定和解锁的代码如下

  /* * * lock * * @ param key redis primary key * @ param value value */public static Boolean lock(String key,String value) {最终布尔结果=Boolean。true . equals(redis template . opsforvalue()。setIfAbsent(CacheConstant。LOCK_KEY key,value));if(result){ log . info([redis template redis]设置锁缓存URL 3360 {} ,key);}返回结果;}/* * * Unlock * * @ param key redis primary key */public static boolean Unlock(string key){ final boolean result=boolean . true . equals(redis template . delete(cache constant . lock _ key key));if(result){ log . info([redis template redis]release lock cache URL 3360 { } ,key);}返回结果;}然后我们稍微修改一下代码,利用锁来改善界面。

  @ get mapping(value= order )public R order(){ boolean lock;int股票;试试{ lock=热地

  sUtil.lock("stock", ""); if (!lock) { return R.failed("服务繁忙,稍后再试"); } stock = RedisUtil.getObject("stock", Integer.class); if (stock > 0) { RedisUtil.set("stock", --stock); } } finally { RedisUtil.unlock("stock"); } return R.ok(stock);}此时,我们再将断点放在获取库存之后,并先用一个终端请求接口

  

 

  然后,我们再从终端2发起请求,可以看到我们终端1没有结束自己的原子操作时,终端2是无法进行库存的扣除的

  

 

  

 

  

4.增加失效时间

在上一步中,我们仿佛已经完成了需求,同时进行扣除库存的只有一个线程,但是试想一下,当线程获取到锁之后,服务突然宕机了,这时候就算及时重启机器,那么锁也一直得不到释放,那么扣除库存接口始终无法获取到锁,这肯定不是我们想要的效果,那么我们改进一下我们加锁的方法,增加一下失效时间,即使服务宕机了,我们重启机器之后,锁也能正常释放掉不会影响一下个线程获取到锁

 

  

/** * 加锁 * * @param key redis主键 * @param value 值 * @param time 过期时间 */public static boolean lock(String key, String value, long time) { final boolean result = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheConstant.LOCK_KEY + key, value, time, TimeUnit.SECONDS)); if (result) { log.info("[redisTemplate redis]设置锁缓存 缓存 url:{} ========缓存时间为{}秒", key, time); } return result;}

 

  

5.增加线程唯一值

还有一种情况会导致我们可能误删除别人的锁,比如当线程1执行完流程之后准备释放锁之时,这时候锁正好失效了,线程2此时获取到锁,线程1释放锁时并不知道锁失效了,那么线程1执行释放操作就会将线程2拥有的锁释放掉,这肯定是不对的,那么我们再对unlock方法改进一下

 

  

/** * 解锁 * * @param key redis主键 */public static boolean unlock(String key, String value) { if (Objects.equals(value, redisTemplate.opsForValue().get(CacheConstant.LOCK_KEY))) { final boolean result = Boolean.TRUE.equals(redisTemplate.delete(CacheConstant.LOCK_KEY + key)); if (result) { log.info("[redisTemplate redis]释放锁 缓存 url:{}", key); } return result; } return false;}@GetMapping(value = "order")public R order() { boolean lock; int stock; String uuid = IdUtil.fastUUID(); try { lock = RedisUtil.lock("stock", uuid, 60L); if (!lock) { return R.failed("服务繁忙,稍后再试"); } stock = RedisUtil.getObject("stock", Integer.class); if (stock > 0) { RedisUtil.set("stock", --stock); } } finally { // 在此释放锁时,判断锁是为自己持有才进行释放 RedisUtil.unlock("stock", uuid); } return R.ok(stock);}

 

  

6.Lua脚本

上面我们说了为了防止误删别人的锁,我们需要在删除锁时判断一下锁是否为自己持有,那么问题来了,我们这个查询锁值和删除锁的操作也并不是一个原子操作,也就是说可能你在获取锁值时锁还为自己持有,但是执行删除时锁已经不为自己持有了,还是会可能误删别人的锁,想要保证释放锁的原子性,我们可以通过redis原生支持的lua脚本来实现

 

  

/** * 解锁 * * @param key redis主键 * @param value 值 */public static boolean unlock(String key, String value) { String script = "if redis.call(get, KEYS[1]) == ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end"; RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(CacheConstant.LOCK_KEY + key), value); if (Objects.equals(1L, result)) { log.info("[redisTemplate redis]释放锁 缓存 url:{}", key); return true; } return false;}

 

  

7.Lua是如何实现原子性的

可以看到Lua脚本的大致意思也是跟我们自己写的代码差不多,判断是否为自己持有如果是才进行删除,那为什么Lua脚本可以保证原子性呢

 

  

Redis使用同一个Lua解释器来执行所有命令,同时,Redis保证以一种原子性的方式来执行脚本:当lua脚本在执行的时候,不会有其他脚本和命令同时执行,这种语义类似于 MULTI/EXEC。从别的客户端的视角来看,一个lua脚本要么不可见,要么已经执行完。

 

  然而这也意味着,执行一个较慢的lua脚本是不建议的,由于脚本的开销非常低,构造一个快速执行的脚本并非难事。但是你要注意到,当你正在执行一个比较慢的脚本时,所以其他的客户端都无法执行命令。

  

 

  

8.代码演示

代码演示

 

  

/** * 加锁 * * @param key redis主键 * @param value 值 * @param time 过期时间 */public static boolean lock(String key, String value, long time) { final boolean result = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheConstant.LOCK_KEY + key, value, time, TimeUnit.SECONDS)); if (result) { log.info("[redisTemplate redis]设置锁缓存 缓存 url:{} ========缓存时间为{}秒", key, time); } return result;}/** * 解锁 * * @param key redis主键 * @param value 值 */public static boolean unlock(String key, String value) { String script = "if redis.call(get, KEYS[1]) == ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end"; RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(CacheConstant.LOCK_KEY + key), value); if (Objects.equals(1L, result)) { log.info("[redisTemplate redis]释放锁 缓存 url:{}", key); return true; } return false;}
@GetMapping(value = "order")public R order() { boolean lock; int stock; String uuid = IdUtil.fastUUID(); try { lock = RedisUtil.lock("stock", uuid,6000L); if (!lock) { return R.failed("服务繁忙,稍后再试"); } stock = RedisUtil.getObject("stock", Integer.class); if (stock > 0) { RedisUtil.set("stock", --stock); } } finally { RedisUtil.unlock("stock", uuid); } return R.ok(stock);}

 

  

9. 总结

分布式锁在使用的过程中还是有挺多的讲究的,主要看应用场景例如还需要保证上述流程中可能碰到的锁失效时间小于代码执行时间,锁提前失效的问题,锁如何保证重入性的问题,欢迎大家讨论

 

  

到此这篇关于SpringBoot RedisTemplate分布式锁的项目实战的文章就介绍到这了,更多相关SpringBoot RedisTemplate分布式锁内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • 关于redis数据库入门详细介绍图片,redis数据库的使用,关于Redis数据库入门详细介绍
  • redis队列操作命令,redis 循环队列
  • redis队列操作命令,redis 循环队列,redis实现简单队列
  • redis部署应用服务器上,redis如何启动服务器
  • redis部署应用服务器上,redis如何启动服务器,搭建Redis服务器步骤详细介绍
  • redis缓存穿透和击穿解决方案,redis缓存穿透,缓存雪崩解决
  • redis缓存穿透和击穿解决方案,redis缓存穿透,缓存雪崩解决,redis缓存穿透解决方法
  • Redis缓存,redis和缓存
  • Redis缓存,redis和缓存,Redis缓存详解
  • redis的配置,启动,操作和关闭方法有哪些,关闭redis的命令,Redis的配置、启动、操作和关闭方法
  • redis的主从配置方法详解图,Redis主从配置
  • redis的主从配置方法详解图,Redis主从配置,redis的主从配置方法详解
  • redis界面工具,mac安装redis可视化工具
  • redis界面工具,mac安装redis可视化工具,推荐几款 Redis 可视化工具(太厉害了)
  • redis正确使用的十个技巧是什么,redis正确使用的十个技巧有哪些
  • 留言与评论(共有 条评论)
       
    验证码: