首页 > 数据库 >分布式场景中确保线程安全的解决方案,redis实现分布式锁

分布式场景中确保线程安全的解决方案,redis实现分布式锁

时间:2022-10-06 22:23:56浏览次数:87  
标签:请求 redis 用户 lua 线程 失效 分布式

 

1. 死锁问题

  • 场景:
    当用 redis 做分布式锁时,当 A 用户竞争锁成功,A 用户所在的主机挂了,这时候还没有来得及释放锁,那么其他用户去用 setnx 指令去竞争锁时发现 redis 有这个锁的 key,所以就导致其他用户永远都竞争不成功。

解决方案: 当用户加锁成功时给锁设置一个跟业务执行时间匹配的锁失效时间, 这样就算锁没有释放过一段时间锁也会实现,其他用户就可以竞争到锁了(这里在设置失效时间的操作,要和 setnx 指令一起执行,不能分成2步执行,否则在setnx之后,还没来得及设置失效时间,主机挂了,还是会发生死锁)。

2. 锁续命问题

  • 场景:
    当 A 用户获取锁成功,但是 A 执行业务操作的时间比较长,就可能会导致业务执行时间大于锁失效时间,那么 A 还在执行业务,锁失效了,就会导致其他用户也可以成功竞争到锁,这样就没办法很好的锁住业务,就会导致脏数据的出现。

解决办法: 当 A 竞争到锁时,就需要开启一个 timer 对锁续命,续命就是重新设置锁的失效时间,由定时器定时的去做这件事情。业务操作完成,unlock,timer.cancel()。

3. 主从锁失效的问题

  • 场景:
    当 redis 是一个主从架构时,当 A 用户从 master 节点获取到了锁,这时候出现这么一个情况,这个锁没有及时同步到 slave 节点时,master 节点挂了,由 redis 哨兵做了主从切换,以前的 slave 成了主,而这时 slave 是没有锁的这个 key 的,那么就会导致其他用户也可以成功竞争到锁进行业务操作,从而导致脏数据。

解决方案: 这时候可以从奇数台 redis 节点去获取锁,如果超过半数的节点能成功获取到锁,并且获取锁的耗时要小于设置的锁失效时间时就认为获取锁成功了,反之就失败。
这里注意两个点:
1、获取锁的时间,获取时间必须要小于锁失效时间
2、必须超过半数节点获取锁成功
如果这两点没有达到则会执行 unlock 操作释放各节点的锁。

  • 这种方案因为需要和多个redis进行通信,执行效率会很低。

上面提到的3个问题都可以使用Redis官方推荐的Java版的Redis客户端Redisson解决,它提供的功能非常多,也非常强大
https://github.com/redisson/redisson

  • Reentrant Lock解决死锁和锁续命问题

在这里插入图片描述

  • RedLock解决主从锁失效的问题

在这里插入图片描述

4. lua 脚本做扣减库存

  • 场景
    在高并发情况下,对库存数据做扣减,存在线程安全问题

解决方案: 扣减库存必须满足原子操作,而 redis 的 lua 是严格的原子操作,所以不会有线 程安全问题。

5. 使用 redis 分布式锁做扣减库存

  • 场景
    同样是在高并发情况下,对库存数据做扣减

解决方案: 分布式锁理论上也可以完成原子操作
1、先竞争锁 lock.lock(2, TimeUnit.SECONDS);
2、查询库存 Integer stock = (Integer) redisTemplate.opsForValue().get(killGoodCount);
3、扣减库存 redisTemplate.opsForValue().increment(killGoodCount, -1)
这里跟 redis 有三次通讯,而使用 lua 脚本只需要跟 redis 进行一次 通讯,理论上 lua 的效率要高于 redis 分布式锁

6. lua 和分布式锁的优化

  • Lua 和分布式锁都是严格意义上的原子操作,也就是同一时间只允许一个用户操作成功,这里肯定会有一个限制吞吐量的问题

Lua 脚本的优化:
Lua 脚本的执行在同一台 redis 主机是 one by one 执行的,那么我们可以把库 存分散到多台 redis 实例中,然后我们程序随机的选择一台 redis 节点去执行扣 减库存的 lua 脚本,处理业务的通道变多了,吞吐量就提高了。

分布式锁的优化:
优化思路跟 lua 差不多,就是加大口子,以前我们在一个 redis 节点中是去竞争一把锁,现在是有多把锁多个库存,一个锁就对应着一个库存,是一一对应关系, 程序随机的去竞争锁,如果竞争成功去扣减这把锁对应的库存就可以了,如果库存不足则又继续随机的从剩下的锁中去竞争。这样多把锁理论上是同时运行多个用户竞争多把锁成功的,比之前的一把锁吞吐量明显要高。这个就是分段锁的思路。

分段锁要注意的点:
分段锁需要物理主机有足够的性能,因为分段后同一时间跟 redis 的通讯就加多了,直接就会导致耗尽 redis 连接池的连接对象,导致大量请求在连接池的等待队列中,从而导致请求失败,redis 连接超时。

7. 并行转串行思路

  • 并行转串行就是一种加快单次请求速度的处理方式,当用户请求过来时,不立刻处理这个请求的业务,而是把这次请求用本地队列缓存起来,然后用线程异步去一个个处理本地队列里的请求,这样就对本来高并发的并行请求进行了削峰处理。这样理论上单次请求速度肯定是要加快的,以前是去处理业务,现在只是把请求塞到本地队列,这样占用的 tomcat 连接池中的连接对象的时间就短了,这些连接又可以去处理其他用户的请求,所以提高单次请求速度就肯定会提高整个系统的吞吐量。

8. redis 和 zk 分布式锁比较

  • redis分布式锁
    速度要快,没有办法保证一致性
  • Zk分布式锁
    效率要低于redis分布式锁的,CAP 保证 CP,获取锁更安全
 

标签:请求,redis,用户,lua,线程,失效,分布式
From: https://www.cnblogs.com/go1166/p/16758678.html

相关文章

  • Redis实现分布式锁的7种方案,及正确使用姿势!
    Redis实现分布式锁的7种方案,及正确使用姿势!种方案前言日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个......
  • 云原生分布式存储基石etcd深入解析 pdf
    高清扫描版下载链接:https://pan.baidu.com/s/1y74UuURRksrhwvKn1OKCkw点击这里获取提取码 ......
  • REDIS入门指南 pdf
    高清扫描版下载链接:https://pan.baidu.com/s/1ss4alB1LxebBvPLc4eduxw点击这里获取提取码 ......
  • Java多线程(day2—重要关键词)
    Java多线程中的几个关键词Synchronized与ReentrantLock SynchronizedReentrantLock层次JVM层面的锁,是Java关键词JDK提供的,属于API层面的锁使用1.修饰实......
  • 分布式存储系统之Ceph集群存储池操作
    前文我们了解了ceph的存储池、PG、CRUSH、客户端IO的简要工作过程、Ceph客户端计算PG_ID的步骤的相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/16733806.......
  • 价值万元干货,高级架构师,精通JAVA/高并发/微服务/分布式/中间件
    价值万元干货,高级架构师,精通JAVA/高并发/微服务/分布式/中间件前言价值1万多的网易高级架构师知识分享给大家,请大家往下看,绝对很多干货。大纲介绍这是网易最新一期开......
  • 数据库历险记(二) | Redis 和 Mecached 到底哪个好?
    说起缓存框架,我们最常用的缓存框架有memcached、Redis这两个,但它们之间其实是有差异的。Memcached的诞生2003年5月,BradFitzpatrick发布了第一个版本的Memcached,一开......
  • Java—多线程
    Java多线程基础概念进程与线程进程:操作系统分配资源的最小单位线程:CPU执行的最小单位线程分类1.用户线程用户自己创建的业务线程;2.守护线程......
  • Redis Cluster的部署与维护
    1、RedisCluster的工作原理  不管是Redis的主从复制,还是基于主从复制环境之上的Redis哨兵(Sentinel)模式,这些都是做到了数据的远程备份,并且在哨兵模式下还可以做到原主节......
  • redis
                                ......