一、单体应用,使用JUC包中提供的锁
问题:单一应用无法应对高并发场景。如果对服务进行横向扩展,即增加应用节点,然后nginx配置负载均衡的方式,那么单个进程里的锁无法对其他进程中的线程生效。
解决方案:基于redis的setnx命令的原子性,使用redis缓存中间件来实现分布式锁,使用一个try/catch包裹业务代码,在finally中释放锁
二、多节点应用,使用setnx实现分布式加锁
问题:没有设置超时时间,如果加锁的线程的应用宕机则会出现死锁
解决方案:给锁添加一个超时时间,如果使用setnx和expire命令来完成加锁,则可能出现setnx完成之后就宕机依然会死锁,因此需要使用一个原子性的命令如 setex,来完成加锁
三、有过期时间的分布式锁
问题:假设A线程先获取锁,设置超时时间10秒钟,然后A线程因为一些原因导致10秒都没完成业务操作,锁过期被释放并被线程B获取到了锁,然后A线程完成业务操作去释放锁,此时释放的是线程B的锁,而后续另外的线程就开始获取锁,但锁会被线程B释放,如此往复,锁就近乎完全失效
解决方案:将redis中的value设置每个线程随机生成的唯一ID,在finally中释放锁时,每个线程需要先对redis缓存中的value进行判断,只有相同才能释放。
四、有过期时间和唯一value的分布式锁
问题:假设A线程先获取锁,设置超时时间10秒钟,然后A线程在完成对value的判断 将要去执行del命令释放锁时,但因为前面的步骤执行过久,导致刚完成判断后redis中的锁就过期了,而另外一个节点的线程B已经完成了加锁,此时线程A释放的是线程B的锁
解决方案:使用一个异步线程给锁进行续期
五、有过期时间和唯一value且可续命的分布式锁
问题:高并发场景下,为了保证redis的高可用,最起码需要一个redis的主从架构,因为主从同步的延迟,可能出现从节点还没完同步,主节点就宕机了,此时应用需要在原来的从节点上获取锁,那么之前主节点加的锁就失效了,又出现了并发问题
解决方案:redlock,部署三个或三个以上的redis主从节点集群,每次加锁时应用分别向每个主节点加锁,每个节点加锁完成后返回确认信息给应用,应用收到超过半数的确认消息,则表示完成加锁,否则加锁失败。
注:redlock并不能完美的解决redis一致性带来的并发问题,列如三个redis主节点A、B、C,一个线程先获取锁,A和B上的锁获取成功、C上的锁获取失败,此时B的主节点宕机,从节点转为主节点但是没有同步到加锁信息,此时另一个线程来获取锁,A上的锁无法获取,但是B和C都获取成功,则又出现了并发问题。可以加更多的节点来解决数据一致性的问题,但是这样会影响性能而且需要维护更多的redis。