场景
通常在多消费者单数据源的情况下需要使用分布式锁
多个消费者(或者说多个线程、多个进程、多个服务实例等)需要对单个数据源(或者说共享资源、关键操作等)进行访问和操作,需要确保在同一时间只有一个消费者能够访问数据源,从而避免数据不一致或者操作冲突。
集群下的任务调度
在分布式任务调度系统中,可能会存在多个调度节点同时竞争执行同一个任务的情况。使用分布式锁可以确保同一时间只有一个调度节点能够执行某个任务,避免任务的重复执行或者并发执行。
分布式事务管理
在分布式事务场景中,可能需要对多个资源进行操作,确保所有资源的操作要么全部成功,要么全部失败。使用分布式锁可以实现分布式事务的并发控制和资源管理,确保事务的一致性和可靠性。
并发限流
在高并发系统中,可能需要对某些关键操作进行并发限流,避免系统被过多的请求压垮。使用分布式锁可以控制并发访问的数量,确保系统的稳定性和可用性。
主流使用了redis或者zookeeper来实现分布式锁
redis
redis命令说明:
(1)setnx命令:set if not exists,当且仅当 key 不存在时,将 key 的值设为 value。若给定的 key 已经存在,则 SETNX 不做任何动作。
命令格式:setnx lock.key lock.value
(2)get命令:获取key的值,如果存在,则返回;如果不存在,则返回nil
命令格式:get lock.key
(3)getset命令:该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。
命令格式:getset lock.key newValue
(4)del命令:删除redis中指定的key
命令格式:del lock.key
使用setnx+expire+del+守护线程实现分布式锁
加锁:使用setnx进行加锁,当该指令返回1时,说明成功获得锁
解锁:当得到锁的线程执行完任务之后,使用del命令释放锁,以便其他线程可以继续执行setnx命令来获得锁
存在的问题:假设线程获取了锁之后,在执行任务的过程中挂掉,来不及显示地执行del命令释放锁,那么竞争该锁的线程都会执行不了,产生死锁的情况。
解决方案:设置锁超时时间
设置锁超时时间:setnx 的 key 必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。可以使用expire命令设置锁超时时间
防止没有执行完就超时,可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续期”。
基于setnx、get、getset、value时间的分布式锁
1、实现原理:
(1)setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功,结束;如果返回0则没有获取到锁,转向步骤(2)
(2)get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向步骤(3)
(3)计算新的过期时间 newExpireTime=当前时间+锁超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime
(4)判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。
(5)在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行del命令释放锁(释放锁之前需要判断持有锁的线程是不是当前线程);如果大于锁设置的超时时间,则不需要再锁进行处理。