redis分布式锁的流程
一些参数说明
- DynmaicConfigurerBean:项目中配置,方便获取动态属性。
- 分布式锁相关的属性有:分布式锁的超时时间、重试次数、睡眠时间等
- 超时时间默认1s,重试次数默认5次,睡眠时间默认200ms
流程图
流程图及关键函数说明
-
long expires = System.currentTimeMillis() + timeout + 1;
设置分布式锁的超时时间
-
redisClient.setNX(lockKey, expiresStr)
尝试获取锁,键为锁的名字,值为超时时间
如果获取成功就返回。
没有获取会走下面逻辑:分析
/**
1. 对于以下部分场景考虑的【重要注释】:
2. ① 某一时刻所有线程都检测到之前已经成功获取锁的线程已经持有锁。
3. 此时,如果检测到已持有锁的线程已经锁超时,那么他们可以通过CAS机制竞争锁,可以保证只有一个线程获取锁。
4. ② 某一时刻,之前有一个线程已经获取锁了,剩余的线程,会发现锁的生命周期结束时间大于当前时间,因此不会获取锁。
5. ③判断过期时间的好处:如果在发布时的时候,或者因为某些原因机器崩溃,假设碰巧没有解锁,那么就会产生死锁,判断过期时间也有自动检测死锁的功能。
6. ④ 对于多线程getSet会不会让锁的生存周期结束时间更新?首先,即使更新,分布式锁的判断依据是是否获取锁,并不关心超时的情况,并且对于已经获取锁
7. 的线程会解锁。即使已经获取锁的线程超时了,但是对应key值的value值已经被重新赋值生命周期,但是因为既然是锁竞争,走到这里他们的生存周期的截止时间
8. 都是相近的,也没有什么问题。
*/
- String currentValueStr = redisClient.get(lockKey);
Long.parseLong(currentValueStr) < System.currentTimeMillis()
没有获取到锁则获取该锁的超时时间,判断超时时间有没有超过当前时间
-
String oldValueStr = redisClient.getSet(lockKey, expiresStr);
如果已经小于当前时间,说明获得锁的线程并没有及时释放锁,则去cas抢锁,抢锁成功,则返回
-
没有抢到,则重试次数加1,重试次数大于阈值,则退出,否则睡眠一段时间再重试。
代码实现
@Component
public class DistributedLock {
private Logger logger = LoggerFactory.getLogger(DistributedLock.class);
/** 动态配置中分布式锁是否生效的标识 */
private static final String distributedLockValid = "distributedLockValid";
/** 动态配置中分布式锁的超时时间 */
private static final String distributedLockTimeout = "distributedLockTimeout";
/** 动态配置中分布式锁的重试次数 */
private static final String distributedLockRetryCount = "distributedLockRetryCount";
/** 动态配置中分布式锁的睡眠时间 */
private static final String distributedLockSleepTime = "distributedLockSleepTime";
@Autowired
private DynmaicConfigurerBean dynmaicConfigurerBean;
@Resource(name = "redisClient")
private RedisClient redisClient;
/**
* 分布式锁-加锁[ms]级别加锁
*
* @param logHead 日志头
* @param lockKey key
* @return lockBean
*/
public LockBean lock(String logHead, String lockKey) {
try