业务背景:
把线下各网点的私有数据统一到云上管理,所有需要把线下数据不定时不定量的往云上同步
云上除了需要实时查看各个网点上传的数据,还需要实时的j查询各个网点的统计数据
数据库大体包一个基础表和一个归总表
业务实操:
一开始的做法是,各个网点每次批量的往云上抛数据,云上预先把数据写入MQ,让上传l过程尽早返回。然后MQ再逐条进行分发消费。
每消费一条数据,会重新count后更新到对应网点的中间表中
问题暴露:
在开发库本地调试好功能后,没发现什么问题,于是丢到测试环境(这里的测试环境基本模拟实际生产环境)进行批量数据检测,
发现同一网点上传了1000条数据上来,但是云平台只记录了几条数据,针对问题做了以下几点排查:
1、根据上传记录日志缺点实际的上传条数(发现实实在在有1000条数据上来,基本可以推断问题出现在云上)
2、查看云平台日志发现,系统有大量的死锁日志和锁超时的日志
3、于是分析业务请求流程及数据库操作的逻辑,当请求并发(或者说高频访问)的存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况
4、当前的sql写法为:update table1 set column=select count(1) from table2...
5、就拿A、B两次请求来说,A访问table1,所以A持有table1的行锁,然后企图访问table2,但是B访问table2进行count,所以B持有table2的表锁,又企图访问table1的同行记录进行更新,但是由于table1的行记录被A持有,A由于B锁住了table2,所以他要等B释放了table2才能继续,同样B也要等A释放了table1的行锁才能继续,这就产生了死锁。
解决问题:
找到问题的原因,我们需要通过控制业务的流程来规避死锁问题的产生
于是我们在每一条上来数据后,不每次都做一次count更新,改成设置一个Redis缓存计数器,当消费数量等于上传的数量时,更新一次,通过降低更新频次避免死锁问题
一切符合逻辑,于是乎在本地修改调试后又丢到测试环境去验证
引发第二个问题:
发现日志的确没有再报死锁的问题了,但是发现汇总表并没有级联更新
排查日志发现根本没有执行更新语句
于是在执行更新的上下文条件记录日志,发现消费数量小于上传数量。难道因为保存所以导致部分数据未消费?
查看并无相关的消费错误日志,且查看数据库数据,发现新增的数量与上传数量相等。这里更说明上传数据已经全部处理了。
那么为什么消费数量的值会更小呢?查看测试环境发现MQ会分发给两台服务同时进行消费,虽然代码中的计数器关键部位有加锁,但是仅对单台服务程序有效,
如果两台消费服务同时拿到缓存消费数量然后计数+1,这样就会少1,依次类推就会少更多的量
解决问题:
定位到问题后,我们就需要一个能够控制全局的变量来控制计数器的值。于是我们引入了分布式锁,选用RedisSon做分布式锁,说干就干。
再次本地调试后,提交到测试环境检测,发现网点汇总表的数量更新了,没有相关的日志错误,基本上到此问题已经处理完成。
推荐两篇死锁文章帮助理解: