更新记录
- 2022-08-03 10:32:修改了代码片段getErrorOrder上面的注释
- 2022-08-03 13:52:增加数据库连接池配置
- 2022-08-04 09:27:修改orderService.save(prevSubPlanErrorOrder)为orderService.save(errorOrder)
- 2022-08-05 09:52:OrderEntity errorOrder = getErrorOrder(key, user) 上方增加重要注释!!!
问题描述
主要的问题如标题所述,就是发现在进行事务操作时,比如save、update时,进行5000次操作,可能会有几百1000次没有提交,也没报错的情况,而出现这种情况时,我们发现在数据库里面会存在,十几个sleep的长事务(持续几千秒)。
这个问题是在生产上出现的,在本地运行,或开发环境运行时无法复现。另外这套代码,我们独立部署了两套,两边的配置都是一模一样的,都是阿里云的ecs+rds mysql,并发也差不多,而现在只有一边出了问题,另一边正常。
环境及依赖描述
- 服务器:阿里云ECS 4核16G
- 数据库:阿里云 RDS Mysql 8.0(innoDb) 4核16G
- java版本:java8
- 项目框架:springboot+mybatis
代码片段
其实出问题的代码不止只一点,只是因为运行中,save订单的场景是最多的,所以就贴这里的代码,代码我经过了简化,只保留了关键的逻辑部分。
这个方法只会从controller进入,下面对代码逻辑进行描述,代码逻辑很简单:
1、查询用户信息
2、根据用户信息去redis查询之前的错误订单,如果查询到错误订单,就从第三方接口查询错误订单的状态,并保存,然后方法结束
3、如果没查询到之前的错误订单,就生成新订单,并提交到三方接口@Override @SneakyThrows @Transactional(rollbackFor = {Exception.class, RuntimeException.class}) public void submitOrder(OrderDTO dto) { //查询签约用户信息,这里只有查询操作 UserEntity user = userService.getUser(dto.getUserBizid()); if (user == null) { throw new BizException(Msg.MUST_BIND_CHANNEL); } // 从redis中取出之前提交失败的订单,重新查询该订单的状态,如果成功则不需要重新发起订单,直接保存该订单即可,如果失败才需要走后面的流程 //生成该订单的子计划的异常订单redis key,该方法只有字符串操作 String key = redisKeys.generateErrorOrderKey(); //getErrorOrder这个方法是本service中的方法,该方法声明为private,没有事务注解,该方法中会调用其他service的方法 //其他service的方法中有可能会存在编程事务,这些编程事务的事务传播为PROPAGATION_REQUIRES_NEW,这些编程事务在其方法退出前都保证commit或rollback了的 OrderEntity errorOrder = getErrorOrder(key, user); if (errorOrder != null) { orderService.save(errorOrder); //清除redis中的异常订单记录 redisComponent.delete(key); return; } //未查询到之前的失败订单,组装新的order(此处我省略了一些set操作,为了方便大家观看) OrderEntity orderEntity = new OrderEntity(); orderEntity.setDefaultClientRate(dto.getDefaultClientRate()); orderEntity.setDefaultClientTips(dto.getDefaultClientTips()); orderEntity.setRatePrice(dto.getRatePrice()); orderEntity.setTipsPrice(dto.getTipsPrice()); orderEntity.setDebitCardBankBizid(user.getBankBizid()); orderEntity.setLocation(dto.getLocation()); orderEntity.setBizid(idWorkerComponent.nextStringId()); //这里是调用的mapper自带的save方法 orderService.save(orderEntity); //获取调用第三方接口的service PayChannelService payChannelService = PayChannelFactory.getPayChannel(); //调用三方接口,trade方法里面会有save、select、和http的相关操作 ClientResultDTO resultDTO = payChannelService.trade(); if (!resultDTO.isSuccess()) { //如果三方接口调用失败,则抛出异常,由于方法头加了事务注解,所以理论上应该回滚 throw new BizException(Msg.BALANCE_REPAY_SUBMIT_ORDER_ERROR.getCode(), resultDTO.getResultMsg()); } }
现在的问题是,这个方法没报异常,但是有时候数据并没有save到库中
当出现问题的时候,我们在阿里云rds的性能监控界面,会看到很多挂起的会话,如图:
数据库连接池配置
# -------------------druid 配置-------------------
# 初始化时建立物理连接的个数
spring.datasource.druid.initial-size = 50
# 最大连接池数量
spring.datasource.druid.max-active = 5000
# 最小连接池数量
spring.datasource.druid.min-idle = 50
# 获取连接时最大等待时间,单位毫秒
spring.datasource.druid.max-wait = 10000
# 连接保活
spring.datasource.druid.keep-alive=true
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis = 60000
# 连接保持空闲而不被驱逐的最小时间
spring.datasource.druid.min-evictable-idle-time-millis = 300000
# 用来检测连接是否有效的sql,要求是一个查询语句
spring.datasource.druid.validation-query = SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle = true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-borrow = false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-return = false
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计
spring.datasource.druid.filters = stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties = druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 合并多个DruidDataSource的监控数据
spring.datasource.druid.use-global-data-source-stat = true
spring.datasource.druid.stat-view-servlet.enabled = true
spring.datasource.druid.web-stat-filter.enabled = true
我现在就是一脸懵逼,因为这个问题是偶发的,就像开头说,save 几千次,可能会出现几百次没入库的情况,没入库的原因应该就是事务没提交,但是不知道为什么没提交。希望有朋友能提供一下排查思路,或手把手指导一下,可以VX,由于CSDN上面的最高只能给到500,不过如果能定位和解决问题的话,我可以给到10倍。