最近在项目开发时遇到线程安全问题,简单来说就是通过分布式锁防止多实例入库相同数据失败,不同实例还是会入库相同数据
背景介绍:实例 A、B 接收完全相同的消息,经过加工后入库。为了防止同一份消息入库两次,A、B 采用分布式锁的方式保证同步,其中每条消息都包含唯一 id 字段,以该字段为 Key 建立分布式锁,假如 A 实例抢占到消息1的锁,执行入库并持有锁100秒不释放,在这100秒内当 B 系统扫描到消息1时,无法抢占锁,不执行入库。每条消息的入库都会封装为任务在线程池中执行,所以抢锁失败后不会二次入库
理想状态是每条消息只入库一次,然而在实现运行过程中,还是出现了大量重复入库的数据,并且这部分数据主要集中在业务高峰期后
导致这种情况的主要原因在于无法保证 A、B 两实例总是能在100秒内处理相同的数据,比如 A 实例入库完消息 a 后101秒 B 实例才扫描到消息 a,此时锁已经自然释放,因此 B 实例又执行了一次入库
上面是导致问题的实际原因,具体根本原因如下:
- 整体架构不好,应该从上游进行业务分流,A、B 两实例处理不同的消息,否则无法做到水平扩展
- A、B 两实例线程池工作线程配置过少,大量的消息都积压在消息队列中,数据库性能完全没有充分发挥(系统中配置单实最多例只有10个线程)
- 两台实例性能不同,而且抢到锁的实例执行入库,线程池中后续任务来不及处理,而另一个实例由于抢不到锁,快速向后执行,随着业务量的增大,两实例之间执行消息的差度越来越多,最后极有可能超过100秒
解决方案:
- 优化线程池,提高线程池工作线程的数量,充分利用数据库性能
- 增加锁失效时间,这个值可以根据业务量设置,一般业务量越大,锁失效的时间越长越安全
- 整体架构优化,上游分流,下游不同实例处理不同消息,同时入库
实际以上实例中,分布式锁的使用方式也有问题,分布式锁主要用于多实例同时访问相同数据时的线程安全问题,而上面实例是不能同时访问的,这点大家知道就好