项目场景:
最近帮朋友看项目上分布式锁失效(暂时推测,实则不然)引起严重事故,他给我看了出事故的代码,描述相关的业务场景,分布式锁在他们项目里面一直都在使用,而且暂时没有出现相关问题。
问题描述:
第三方软件商会通过接口传输数据,为了避免数据重复(不想重复就让推送者控制一下呗,纯属个人唠叨,对业务不了解,所以不清楚为什么会出现重复推数据),将数据的为唯一标识用作分布式锁的key,在锁生效的时间内,不会接收重复的一批数据,就是这个分布式锁(使用姿势错了)没生效,这一天导致有大概6W左右的重复数据,出问题的代码我就不附上了,附我自己模拟的环境吧。
@Test
void contextLoads() {
ConcurrencyTester tester = ThreadUtil.concurrencyTest(10000, () -> {
// 测试的逻辑内容
HttpUtil.get("http://localhost:9952/getOrder");
});
}
String order = "12306";
String name = Thread.currentThread().getName();
/**
* 获redis锁
*/
if (RedisUtils.tryLock(order, TimeUnit.SECONDS, 2, 30)) {
System.out.println(String.format("%s 抢到订单啦", name));
} else {
System.err.println(String.format("%s 抢订单失败", name));
}
看了代码是不是觉得没什么问题呀,但是看实际打印结果,发现同一线程重复抢购了多次,他们怀疑是线程竞争引起的,我用上面的截图马上就打破了这种说法,因为重复抢购的都是同一个线程。
原因分析:
看到这个现象我也很疑惑,为啥都是同一个线程出的问题,其他线程都是好好的。
随后我就去看了一下官网文档对分布式锁的介绍,猜测引发事故的锁是:可重入锁(Reentrant Lock),为何这么说:可重入锁,在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。 说白了就是同一个线程再次进入同样代码时,可以再次拿到该锁。 它的作用是:防止在同一线程中多次获取锁而导致死锁发生。这下就真相大白了,就有我个人相对应的解决方案了:换锁或者在此基础针对同一线程进行特殊处理。
解决方案:
在去设置锁之前先去校验锁的状态即可。
String order = "12306";
String name = Thread.currentThread().getName();
/**
* 获redis锁
*/
if (!RedisUtils.isLock(order) &&RedisUtils.tryLock(order, TimeUnit.SECONDS, 2, 30)) {
System.out.println(String.format("%s 抢到订单啦", name));
} else {
System.err.println(String.format("%s 抢订单失败", name));
}