在单机环境中,应用是在同一进程下的,只需要保证单进程多线程环境中的线程安全性,通过 JAVA 提供的 volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一些线程安全的类等就可以做到。而在多机部署环境中,不同机器不同进程,就需要在多进程下保证线程的安全性了。因此,分布式锁应运而生。
第一种
基于redis命令
- 加锁:执行setnx,若成功再执行expire添加过期时间
- 解锁:执行delete命令
实现简单,相比数据库和分布式系统的实现,该方案最轻,性能最好
1.setnx和expire分2步执行,非原子操作;若setnx执行成功,但expire执行失败,就可能出现死锁
2.delete命令存在误删除非当前线程持有的锁的可能
3.不支持阻塞等待、不可重入
第二种
基于redis Lua脚本能力
-
加锁:执行SET lock_name random_value EX seconds NX 命令
-
解锁:执行Lua脚本,释放锁时验证random_value
– ARGV[1]为random_value, KEYS[1]为lock_name
if redis.call(“get”, KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end
redis+lua基本可应付工作中分布式锁的需求。
不足之处:不支持锁重入,不支持阻塞等待
第三种
redisson 分布式锁
redisson保持了简单易用、支持锁重入、支持阻塞等待、Lua脚本原子操作。
我们在网上看到的redis分布式锁的工具方法,大都满足互斥、防止死锁的特性,有些工具方法会满足可重入特性。
如果只满足上述3种特性会有哪些隐患呢?redis分布式锁无法自动续期,比如,一个锁设置了1分钟超时释放,如果拿到这个锁的线程在一分钟内没有执行完毕,那么这个锁就会被其他线程拿到,可能会导致严重的线上问题,我已经在秒杀系统故障排查文章中,看到好多因为这个缺陷导致的超卖了。
redisson 提供的分布式锁是支持锁自动续期的,也就是说,如果线程仍旧没有执行完,那么redisson会自动给redis中的目标key延长超时时间,这在Redisson中称之为 Watch Dog 机制。
同时 redisson 还有公平锁、读写锁的实现。