分布式锁的核心就是实现多线程之间的互斥,能够实现这一点的方式有很多,比如MySQL的的锁机制,而今天要说的是Redis。在上一篇文章中提到,在单体服务器下,前面的代码逻辑是可以很好地实现的,但是在集群的时候,由于每台服务器都有自己的锁监视器,所以不能保证每个用户获取到的锁是同一把锁,也就无法保证一人一单的问题。
所以今天要说的分布式锁就是为了解决在集群模式下,如何保证锁在多线程的情况下,每个用户拿到的锁是同一把锁。
实现分布式锁需要两个方法:
获取锁:
互斥:确保只能有一个线程获取锁
非阻塞:尝试一次,成功返回
true
,失败返回false
释放锁:
手动释放
超时释放,获取锁的时候添加一个超时时间
这里是基于UUID + 锁ID来标记锁的 key,保证锁的唯一性的。下面是实现步骤:
1、首先定义一个接口,定义好获取锁和释放锁的方法。
1 | public interface ILock { |
2、定义一个类,实现上面的接口,利用Redis实现分布式锁功能。
1 | public class SimpleRedisLock implements ILock { |
这里获取分布式锁的逻辑很简单,就是使用多种验证,先用UUID + 当前虚拟机的线程id来作为标识,保证唯一性;在释放锁的时候,也需要判断是否是同一个线程才允许释放锁,如果不加以判断的话,若当前线程在执行业务过程中,出现卡顿异常情况而导致的锁超时释放,这时其他线程会乘机获取锁,等到当前线程恢复的时候,就会把其他线程的锁释放掉而造成更大的问题。
笔者这里多次使用到了hutool包,这是一个很好用的工具包,在允许使用的情况下,可以去官网查看。
3、改造秒杀下单的代码:
1 | @Override |
createVoucherOrder方法的逻辑不变,只是获取锁的时候,把悲观锁换成了分布式锁,保证在集群下也能够实现一人一单的业务需求。
以上就是基于Redis实现分布锁来解决集群模式下一人一单问题的解决方法,但正如前面所说,如果当前线程执行业务过程中,遇到JVM
正在GC
的情况,就会造成业务阻塞,等到线程恢复的时候,可能锁已经超时失效了,即便在释放锁放在 finally
关键字里面,也是无法保证命令的原子性的。比如,在线程获取到锁,刚刚判断完获取锁成功的时候,就出现了卡顿,造成业务阻塞,等到线程恢复的时候,此时锁已经超时释放了,这里的解决策略可以是延长锁超时时间,但在高并发情况下,可能不太适用。这里推荐Lua脚本,这是一个轻量级的工具,一个脚本可以编写多条Redis命令,因为命令都是在同一个脚本里面的,可以很好地保证它的原子性。
下一篇文章会继续优化Redis分布式锁的原子性问题。
本文链接: https://longzas.github.io/2023/08/27/Redis%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!