@@ -824,6 +824,95 @@ Redis 的哈希表使用链地址法(separate chaining)来解决键冲突:
824824
825825原理跟 Java 的 HashMap 类似,都是数组+链表的结构。当发生 hash 碰撞时将会把元素追加到链表上。
826826
827+ ## Redis实现分布式锁有哪些方案?
828+
829+ 在这里分享六种Redis分布式锁的正确使用方式,由易到难。
830+
831+ 方案一:SETNX+EXPIRE
832+
833+ 方案二:SETNX+value值(系统时间+过期时间)
834+
835+ 方案三:使用Lua脚本(包含SETNX+EXPIRE两条指令)
836+
837+ 方案四::ET的扩展命令(SETEXPXNX)
838+
839+ 方案五:开源框架~ Redisson
840+
841+ 方案六:多机实现的分布式锁Redlock
842+
843+ ** 首先什么是分布式锁** ?
844+
845+ 分布式锁是一种机制,用于确保在分布式系统中,多个节点在同一时刻只能有一个节点对共享资源进行操作。它是解决分布式环境下并发控制和数据一致性问题的关键技术之一。
846+
847+ 分布式锁的特征:
848+
849+ 1、「互斥性」:任意时刻,只有一个客户端能持有锁。
850+
851+ 2、「锁超时释放」:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。
852+
853+ 3、「可重入性」“一个线程如果获取了锁之后,可以再次对其请求加锁。
854+
855+ 4、「安全性」:锁只能被持有的客户端删除,不能被其他客户端删除
856+
857+ 5、「高性能和高可用」:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
858+
859+
860+
861+ ** Redis分布式锁方案一:SETNX+EXPIRE**
862+
863+ 提到Redis的分布式锁,很多朋友可能就会想到setnx+expire命令。即先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记了释放。SETNX是SETIF NOT EXISTS的简写。日常命令格式是SETNXkey value,如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。假设某电商网站的某商品做秒杀活动,key可以设置为key_resource_id,value设置任意值,伪代码如下:
864+
865+ ![ img] ( https://cdn.nlark.com/yuque/0/2024/png/28848830/1718076327854-c75a4b72-4a8a-4afb-87fe-378082b36046.png )
866+
867+ 缺陷:加锁与设置过期时间是非原子操作,如果加锁后未来得及设置过期时间系统异常等,会导致其他线程永远获取不到锁。
868+
869+ ** Redis分布式锁方案二** :SETNX+value值(系统时间+过期时间)
870+
871+ 为了解决方案一,「发生异常锁得不到释放的场景」,有小伙伴认为,可以把过期时间放到setnx的value值里面。如果加锁失败,再拿出value值校验一下即可。
872+
873+ 这个方案的优点是,避免了expire 单独设置过期时间的操作,把「过期时间放到setnx的value值」里面来。解决了方案一发生异常,锁得不到释放的问题。
874+
875+ 但是这个方案有别的缺点:过期时间是客户端自己生成的(System.currentTimeMillis()是当前系统的时间),必须要求分布式环境下,每个客户端的时间必须同步。如果锁过期的时候,并发多个客户端同时请求过来,都执行jedis.get()和set(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。该锁没有保存持有者的唯一标识,可能坡别的客户端释放/解锁
876+
877+ ** 分布式锁方案三:使用Lua脚本(包含SETNX+EXPIRE两条指令)**
878+
879+ 实际上,我们还可以使用Lua脚本来保证原子性(包含setnx和expire两条指令),lua脚本如下:
880+
881+ ![ img] ( https://cdn.nlark.com/yuque/0/2024/png/28848830/1718075869527-a3704805-53a6-4bd4-be07-2558cff533a2.png )
882+
883+ 加锁代码如下:
884+
885+ ![ img] ( https://cdn.nlark.com/yuque/0/2024/png/28848830/1718075859795-a0cfcfe0-7c56-49ac-9182-6b203739a99e.png )
886+
887+ ** Redis分布式锁方案四: SET 的扩展命令(SET EX PX NX)**
888+
889+ 除了使用,使用Lua脚本,保证SETNX+EXPIRE两条指令的原子性,我们还可以巧用Redis的SET指令扩展参数。(` SET key value[EX seconds] ` PX milliseconds] [ NX|XX ] `),它也是原子性的
890+
891+ ` SET key value[EX seconds][PX milliseconds][NX|XX] `
892+
893+ 1 . NX:表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁, 才能获取。
894+ 2 . EXseconds:设定key的过期时间,时间单位是秒。
895+ 3 . PX milliseconds:设定key的过期时间,单位为毫秒。
896+ 4 . XX:仅当key存在时设置值。
897+
898+ 伪代码如下:
899+
900+ ![ img] ( https://cdn.nlark.com/yuque/0/2024/png/28848830/1718075985907-86dd8066-001a-4957-a998-897cdc27c831.png )
901+
902+ ** Redis分布式锁方案五: Redisson 框架**
903+
904+ 方案四还是可能存在「锁过期释放,业务没执行完」的问题。设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。当前开源框架Redisson解决了这个问题。一起来看下Redisson底层原理图:
905+
906+ ![ img] ( https://cdn.nlark.com/yuque/0/2024/png/28848830/1718076061807-8b2419dd-13ff-441e-a238-30bf402b07fb.png )
907+
908+ 只要线程一加锁成功,就会启动一个watchdog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了「锁过期释放,业务没执行完」问题。
909+
910+ ** 分布式锁方案六:多机实现的分布式锁Redlock+Redisson**
911+
912+ 前面五种方案都是基于单机版的讨论,那么集群部署该怎么处理?
913+
914+ 答案是多机实现的分布式锁Redlock+Redisson
915+
827916
828917
829918![ ] ( http://img.topjavaer.cn/img/20220612101342.png )
0 commit comments