【分布式】【高并发】为什么同样的代码在本地运行正常,在生产环境就偶尔会出现错误?(行级锁的使用)

前言

项目上有一个基于消息队列的订单统计功能,每次在支付微服务完成一笔订单,就会通过广播的方式将该笔订单发送到数据中心微服务上,数据中心微服务接收后根据“支付完成时间”、“支付地点”、“项目Guid”进行统计。如果当前不存在该数据,则做新增动作,如果存在,则先读取当前数据的金额,再通过BigDecimal加上当前订单的金额。
这个功能在本地单机执行的时候没有任何问题,但是一到生产环境上就会时常出现统计金额不准确的问题。之前一直以为是因为统计时有部分订单仍卡在消息队列中,于是都通过直接修改生产环境的数据库解决,这次针对这个问题写了定时任务,发现每次执行都会出现不一样的结果。

一、原因分析

回看这部分代码,逻辑没有任何问题,修改的操作可以拆分为以下三个步骤:

(1)读取MySQL数据库中的统计数据;

(2)将数据缓存到JAVA中并计算统计结果;

(3)把结果重新写入MySQL数据库。

可以想到,在分布式系统中,消息队列广播发出后会由不同的机器接收,如此即会出现同一时间“支付完成时间”、“支付地点”以及“项目Guid”相同的多笔订单分配到不同的机器上,出现以下结果:

(1)机器1读取到MySQL数据库中的统计数据;

(2)机器1将数据缓存到JAVA中并计算统计结果;

(3)机器2读取到MySQL数据库中更新前的统计数据;

(4)机器1将结果重新写入MySQL数据库;

(5)机器2将数据缓存到JAVA中并计算统计结果;

(6)机器2将结果重新写入MySQL数据库。

所以每次执行都会出现不一样的结果。

二、解决方式

针对此类问题有以下几种解决方式:

1.线程休眠

1.1 简介

Thread.sleep()方法可以让当前线程暂停执行一段时间,单位是毫秒。

1.2 效果

该方法会使当前线程暂停执行,让出CPU资源给其他线程,但是该线程仍然持有锁。缺点是可能会影响程序的性能和响应性,因为其他线程可能需要等待该线程释放锁才能继续执行。除此之外,还可能会导致线程调度延迟,因为操作系统可能会在一段时间后才重新调度该线程。

try {
	Thread.sleep(100);
} catch( Exception error ) {
	logger.error(error);
}

在每次查询之前执行线程休眠,该方法治标不治本,只能解决部分问题且对性能影响极大,排除掉。

2.行级锁(ForUpdate)

2.1 简介

行级锁是一种数据库锁定机制,它可以在数据库中对单个数据行进行锁定,以保证并发访问时的数据一致性。当一个事务对某个数据行进行修改时,行级锁会将该数据行锁定,其他事务无法对该数据行进行修改,直到该事务释放锁定。行级锁可以提高并发访问的效率,减少锁定的粒度,从而提高数据库的并发性能。

2.2 效果

for update是一种行级锁。使用带行级锁的SQL语句时,其他机器在查询到同一条数据时会等待该行解锁,不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式锁表,一直到提交或复原该事务为止。

3.SQL语句

将查询步骤去除,使用SQL在查出数据时直接计算统计结果。

4.单机执行

将计算统计结果程序单独拿出来单机执行,繁琐低效,排除掉。

三、代码展示

本文选用的是行级锁的方式解决,在Example的查询方法中使用。

Example example1 = new Example(StatPayBankDay.class);
criteria1.andEqualTo("guid", payBankDays.get(0).getGuid());
example1.setForUpdate(true);

⚠️ 使用行级锁需要注意以下四点:

(1)锁的范围,避免锁定过多的行导致性能下降,For Update虽然是行级锁,但是并不所有的情况下都只锁行,某些情况下也会将整个表锁住。为避免整张表被锁住导致所有查询都无法执行,查询方法需要对该表的主键进行查询;

(2)事务的提交和回滚,避免出现死锁或长时间占用锁的情况。故该类需要加上事务回滚的注解@Transactional(rollbackFor = Exception.class);

(3)并发性,避免多个事务同时请求锁导致性能下降;

(4)事务隔离级别,确保事务隔离级别为可重复读或更高级别,否则可能会出现锁失效的情况。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布熬夜了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值