学习分布式的时候,了解到 Redis 分布式锁的时候,觉得这是个很实用的功能,于是实操了一下,使用 Jmeter 进行线程压测,记录一下结论

Synchronized 只能锁当前服务的线程,锁不了其他服务,所以在分布式情况下不实用,而且大并发使用 Synchronized 也存在性能问题

Redis 使用 SetNX 做一把锁,setnx 只有在key不存在的情况下,才会设置value ,已经存在key,不做任何操作,利用这个特性来制作一把锁。

大致流程如下:
使用 StringRedisTemplate 对象的 opsForValue().setIfAbsent(K,V) 方法创建一把锁,类似于 jedis.setnx(K,V),方法返回值的返回值是布尔值,如果结果 false 就不执行逻辑代码返回自定义 error
线程在拿到锁在执行完后,必须去释放锁,使用 StringRedisTemplate 对象的 delete(K) 释放锁,让其他线程来争抢锁

问题一,如果一个线程拿到锁,中途逻辑代码抛异常,导致该线程不能正常释放锁,就会导致锁一直不能释放,解决办法是用 try finally 来释放锁,加锁和逻辑代码放在 try 捕获里,释放锁放在 finally 里。

问题二,线程拿到锁,中途程序挂掉,导致不能执行 finally ,也将不能释放锁,解决办法是使用 StringRedisTemplate 对象的 expire(K,timeout) 设置超时时间,比如设置10秒,不管程序是否挂掉,都能释放锁

问题三,如果线程刚拿到锁就挂了,没来得及设置超时,也就不能释放锁,解决办法是使用 StringRedisTemplate 的 opsForValue.setIfAbSent(K,V,timeout,timeunit) 设置锁的超时,保证加锁和超时是一个原子执行

问题四,如果逻辑代码执行时间超过了超时时间,代码没执行完,锁就被释放掉了,下一个线程又来加锁,最后第一个线程执行完后会去释放锁,但它释放的是其他线程加的锁,多个线程反复,锁会永久失效,解决办法是给当前线程锁设置一个token,比如使用 UUID,当成 value 保存,释放锁时判断 key 取到的 value 是否等于生成的token,不相等就不去释放

问题五,超市时间设置合理问题,解决办法是每10秒分线程检测锁是否存在,存在就续期

最终方案,使用 Redisson 客户端,实现了最完善的分布式锁,加锁之前 redissonLock.getLock(K) 拿锁, try 里加锁 redissonLock.lock() 方法,finally 里面释放锁,redissonLock.unlock() 方法。

最后修改:2022 年 02 月 28 日
請我飲一杯咖啡