记一次并发引起的问题及排查过程

简介: 聚合支付系统(第四方支付),协议支付模块一直有个小问题。商户调用协议支付接口,该模块会调用下层第三方支付渠道的协议支付服务,如果第三方支付渠道没有同步返回支付结果,则协议支付模块会通过定时任务向第三方支付渠道批量第查询支付结果(每查一笔订单就调一次第三方支付渠道,“批量”相当于并发调用第三方支付渠道)

问题背景


聚合支付系统(第四方支付),协议支付模块一直有个小问题。

商户调用协议支付接口,该模块会调用下层第三方支付渠道的协议支付服务,如果第三方支付渠道没有同步返回支付结果,则协议支付模块会通过定时任务向第三方支付渠道批量第查询支付结果(每查一笔订单就调一次第三方支付渠道,“批量”相当于并发调用第三方支付渠道),大致如下图:


109.png


在支付阶段,基本上没有问题,但是在定时任务批量查询支付结果时,如果查询的订单数量过多,就总会有几个查询请求报 “证书错误”(系统自定义异常,表示请求第三方支付的证书字符串有误)。


定时任务查询支付订单支付结果的逻辑大致是:每隔5秒钟查询当前支付结果是处理中(不是终态——成功或失败的状态)的订单列表,比如有100笔(这100笔订单中可能有一到多个商户发起的支付订单),然后开启100个线程分别调用第三方支付的查询接口进行查询。在调用第三方支付的时候,需要根据订单信息来获取证书字符串。


问题排查


在和第三方支付确认他们收到的证书字符串和对应请求中的证书字符串一致后,就回头看获取证书字符串的相关代码,大致代码如下:

生成证书字符串的工具类:

public class CertUtils {
    private static CertHandler certHandler;
    public CertUtils(String merchantInfo) {
        // 根据商户信息 merchantInfo 生成 certHandler
        // 略
    }
    public String getCertStr(){
        return certHandler.getCertStr();
    }
}

在项目中获取证书字符串(上面工具类的使用)

CertUtils certUtils=new CertUtils(merchantInfo); // ① 根据当前商户信息生成一个工具类的实例
String merchantCert=certUtils.getCertStr(); // ② 通过工具类的实例获得当前商户的证书字符串

不看不知道,一看吓一跳,这工具类用的有点奇葩啊~ 分析下奇葩的原因:


获得证书字符串是由certHandler的getCertStr()方法获得的,而certHandler是根据商户信息生成的,但这里却把certHandler定义为static的,所以即使用的时候new了100个CertUtils实例,CertUtils中引用的CertHandler实例却永远是一个,且指向的对象在不断变化,也就是说,CertUtils中引用的CertHandler实例是公共变量,在多线程环境下可能会出现线程安全的问题。


比如当线程1执行完上面的代码①,准备执行代码②之前,这时线程2也刚好执行完代码①,所以线程2就把线程1生成的certHandler给修改了,线程1执行代码②时获取到的证书字符串其实是线程2的。


分析完毕再具体看日志,正如所料,报“证书错误”的请求所携带的证书字符串,和与它并发发起的那些请求所携带的证书字符串一样:(两个线程发起的请求,红框是商户号不一样,黄色背景是证书字符串一样)


110.png

这就说明,报“证书错误”的请求跟上面分析的线程1情况一样,在执行完上面的代码①,准备执行代码②之前,被其他线程执行了代码①,把公共的CertHandler实例给改掉了,所以拿到的证书字符串也就是别人的。

解决方法:


1、使用上述工具类的时候,对代码①和代码②加锁,可以保证线程安全

2、直接把线程公有的变量certHandler变成线程私有变量,改成非静态的类型


相关文章
|
消息中间件 监控 Java
Kafka Producer异步发送消息技巧大揭秘
Kafka Producer异步发送消息技巧大揭秘
949 0
|
3月前
|
机器学习/深度学习 数据可视化 算法
数据分布不明确?5个方法识别数据分布,快速找到数据的真实规律
本文深入探讨了数据科学中分布识别的重要性及其实践方法。作为数据分析的基础环节,分布识别影响后续模型性能与分析可靠性。文章从直方图的可视化入手,介绍如何通过Python代码实现分布特征的初步观察,并系统化地讲解参数估计、统计检验及distfit库的应用。同时,针对离散数据、非参数方法和Bootstrap验证等专题展开讨论,强调业务逻辑与统计结果结合的重要性。最后指出,正确识别分布有助于异常检测、数据生成及预测分析等领域,为决策提供可靠依据。作者倡导在实践中平衡模型复杂度与实用性,重视对数据本质的理解。
254 3
数据分布不明确?5个方法识别数据分布,快速找到数据的真实规律
|
存储 人工智能 关系型数据库
数据库的深度探索:技术演进、应用领域与未来趋势
一、引言 数据库,作为信息技术领域中的关键组件,不仅为数据的存储、检索和管理提供了强有力的支持,而且随着技术的不断发展,其功能和应用领域也在不断扩展
|
消息中间件 安全 Shell
国货之光——jdchain1.6.5测试网络部署
国货之光——jdchain1.6.5测试网络部署
360 13
|
安全 Linux 网络安全
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
114618 0
|
存储 JSON 安全
Oauth2.0 + JWT 做权限认证
做过权限认证的朋友都清楚,SpringSecurity 的功能很强大,但是我们也都知道,它配置起来也着实让人头疼。N多个配置类还有N多个需要实现的接口,总是记不住和不知道为什么会有这么多,最近在学习这方面的东西,正好能够把学习到的东西分享出来给大家参考一下。
|
传感器 存储
手持读数仪连接传感器的类型
便携式手持设备面板的设计与其他工业设备的主要区别在于具有便携性和可操作性。通过人机进行操作能够对手握区域、外部接口区域、显示和操作区域进行接触,同时,手持检测设备的外观设计还要能够与人体结构相契合,达到手持的舒适度等。如手持振弦VH501TC,在工程监测振弦传感器时就很方便快捷,连接上传感器,立即读取到数据,每个测点检测时就能快速及时处理。
手持读数仪连接传感器的类型
|
存储 缓存 前端开发
如何提高网站的性能
网站的性能是影响用户体验和转化率的关键因素之一。本文将介绍一些优化网站性能的方法和技巧,包括减少HTTP请求、使用CDN、压缩和合并文件、使用浏览器缓存和优化图片等。
264 0
|
数据库
SAP Basis DEBUG改表数据权限角色设计
SAP Basis DEBUG改表数据权限角色设计
SAP Basis DEBUG改表数据权限角色设计
|
SQL PHP 数据库
ThinkPHP数据库查询之Db类深度解析(3)
ThinkPHP数据库查询之Db类深度解析
559 0
ThinkPHP数据库查询之Db类深度解析(3)