平台系统的微信支付服务突然不可用问题记录

背景

我们平台系统的微信支付突然不可用,用户点击支付都提示错误“系统繁忙”。

排查

查看日志,发现“支付聚合服务”调用“微信支付服务”的http请求返回read timeout,问题很显然出在“微信支付服务”。http请求报read timeout,说明能建立connection,应用没有死亡,只是响应慢。
一个应用响应慢,要么是请求流量大被“压死”,要么是依赖组件慢被“拖死”。
通过日志量分析,并没有突发的流量,那只有可能是被“拖死”了。
被“拖死”的情况,应用web容器线程会表现出所有线程都阻塞在某个操作。
我们马上通过jstack命令dump出应用的线程栈信息,发现一个问题:所有的web容器线程都阻塞在com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager.putMerchant方法

"XNIO-1 task-8" #298 prio=5 os_prio=0 tid=0x00007f33b0072800 nid=0x12b waiting for monitor entry [0x00007f342051a000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager.putMerchant(CertificatesManager.java:142)
        - waiting to lock <0x00000000da83be48> (a com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager)

原因分析

什么原因引起阻塞?

com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager属于wechatpay-apache-httpclient包,是微信支付开源的官方依赖包,项目地址:https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient

CertificatesManager.putMerchant是向微信请求证书,通过查看源码,发现CertificatesManager是个单例,putMerchant是synchronized同步方法,内部最终通过httpclient发起http请求从微信支付平台拉取证书。这个http请求没有设置超时时间,默认不超时,如果微信提供证书的服务稍微抖动不响应一下,这里就会阻塞住。
代码如下,

/**
 * 增加需要自动更新平台证书的商户信息
 *
 * @param merchantId 商户号
 * @param credentials 认证器
 * @param apiV3Key APIv3密钥
 * @throws IOException IO错误
 * @throws GeneralSecurityException 通用安全错误
 * @throws HttpCodeException HttpCode错误
 */
public synchronized void putMerchant(String merchantId, Credentials credentials, byte[] apiV3Key)
        throws IOException, GeneralSecurityException, HttpCodeException {
    ......
    initCertificates(merchantId, credentials, apiV3Key);
    ......
}
/**
 * 下载和更新平台证书
 *
 * @param merchantId 商户号
 * @param verifier 验签器
 * @param credentials 认证器
 * @param apiV3Key apiv3密钥
 * @throws HttpCodeException Http返回码异常
 * @throws IOException IO异常
 * @throws GeneralSecurityException 通用安全性异常
 */
private synchronized void downloadAndUpdateCert(String merchantId, Verifier verifier, Credentials credentials,
        byte[] apiV3Key) throws HttpCodeException, IOException, GeneralSecurityException {
    try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
            .withCredentials(credentials)
            .withValidator(verifier == null ? (response) -> true
                    : new WechatPay2Validator(verifier))
            .withProxy(proxy)
            .build()) {
        HttpGet httpGet = new HttpGet(CERT_DOWNLOAD_PATH);
        httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            int statusCode = response.getStatusLine().getStatusCode();
            String body = EntityUtils.toString(response.getEntity());
            if (statusCode == SC_OK) {
                Map<BigInteger, X509Certificate> newCertList = CertSerializeUtil.deserializeToCerts(apiV3Key, body);
                if (newCertList.isEmpty()) {
                    log.warn("Cert list is empty");
                    return;
                }
                ConcurrentHashMap<BigInteger, X509Certificate> merchantCertificates = certificates.get(merchantId);
                merchantCertificates.clear();
                merchantCertificates.putAll(newCertList);
            } else {
                log.error("Auto update cert failed, statusCode = {}, body = {}", statusCode, body);
                throw new HttpCodeException("下载平台证书返回状态码异常,状态码为:" + statusCode);
            }
        }
    }
}

为什么会所有线程都阻塞?

原因是我们使用CertificatesManager.putMerchant的用法错误
我们的代码直接抄了wechatpay-apache-httpclient包样例代码,其实是每次支付请求,都向微信获取了一次证书。在微信支付平台证书服务抖动的情况下,只要同时有足够的支付请求,就会把“微信支付服务”所有容器线程给阻塞住。微信样例代码如下图,我们的代码如下,
在这里插入图片描述

public static Verifier createVerifier(WechatPayMerchant wechatPayMerchant) {
    Objects.requireNonNull(wechatPayMerchant, "商户配置不能为空");
    try {
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                new ByteArrayInputStream(wechatPayMerchant.getMerchantPrivateKey().getBytes(StandardCharsets.UTF_8)));

        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(wechatPayMerchant.getPayUsedMchId(), new WechatPay2Credentials(wechatPayMerchant.getPayUsedMchId(),
                new PrivateKeySigner(wechatPayMerchant.getMerchantSerialNumber(), merchantPrivateKey)), wechatPayMerchant.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        Verifier verifier = certificatesManager.getVerifier(wechatPayMerchant.getPayUsedMchId());
        return verifier;
    } catch (Exception e) {
        log.error("createVerifier报错", e);
        throw new ServiceException("创建WechatPay Verifier出错");
    }
}

参考


issue链接

优化

调整代码,利用CertificatesManager的缓存和自动更新策略,只在第一次加载证书,之后依赖CertificatesManager每24小时的自动更新机制。
调整后代码如下,

public static Verifier createVerifier(WechatPayMerchant wechatPayMerchant) {
    Objects.requireNonNull(wechatPayMerchant, "商户配置不能为空");
    try {
        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        try{
            //先从缓存找证书
            Verifier verifier = certificatesManager.getVerifier(wechatPayMerchant.getPayUsedMchId());
            log.debug("从缓存获取证书:{}", wechatPayMerchant.getPayUsedMchId());
            return verifier;
        }catch (Exception e){
            log.warn("获取证书报错:{}, {}", wechatPayMerchant.getPayUsedMchId(), e.getMessage());
            if(e instanceof NotFoundException){
                // 证书不存在
                PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                        new ByteArrayInputStream(wechatPayMerchant.getMerchantPrivateKey().getBytes(StandardCharsets.UTF_8)));

                //向证书管理器增加需要自动更新平台证书的商户信息
                certificatesManager.putMerchant(wechatPayMerchant.getPayUsedMchId(), new WechatPay2Credentials(wechatPayMerchant.getPayUsedMchId(),
                        new PrivateKeySigner(wechatPayMerchant.getMerchantSerialNumber(), merchantPrivateKey)), wechatPayMerchant.getApiV3Key().getBytes(StandardCharsets.UTF_8));
                Verifier verifier = certificatesManager.getVerifier(wechatPayMerchant.getPayUsedMchId());
                log.info("实时获取一次证书:{}", wechatPayMerchant.getPayUsedMchId());
                return verifier;
            }else{
                throw e;
            }
        }

    } catch (Exception e) {
        log.error("createVerifier报错", e);
        throw new ServiceException("创建WechatPay Verifier出错");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/549434.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

全球顶级的低代码开发平台,你知道几个?

什么是低代码开发平台? 低码开发平台是一个应用程序,提供图形用户界面编程,从而以非常快的速度开发代码,减少了传统的编程工作。 这些工具有助于快速开发代码,最大限度地减少手工编码的努力。这些平台不仅有助于编码,而且还能快速安装和部署。 低码开发工具的好处 低代码平…

【JavaSE】你真的了解内部类吗?

前言 本篇会详细讲解内部类的四种形式&#xff0c;让你掌握内部类~ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 前言 内部类介绍 实例内部类 定义 调用 静态内部类 定义 调用 匿名内部类 定义和调用1 调用方法2 …

vs2019 - detected memory leak

文章目录 vs2019 - detected memory leak概述笔记vs2019 consolevs2019 MFC Dlg但是&#xff0c;工程大了之后&#xff0c;VS2019提示的就变了样整好的内存泄漏侦测头文件和实现my_debug_new_define.hmy_debug_new_define.cpp在所有.cpp文件入口处包含my_debug_new_define.h包含…

计算机系列之操作系统的系统

2、大话操作系统的启动 当按下开机键时&#xff0c;BIOS 就会开始执行 ​ BIOS 就是放在主板上 ROM 里面的一段程序。 ​ ROM Read Only Memory&#xff08;只能读取的内存&#xff09; ​ 所以 BIOS 在出厂的时候就可以直接写死在 ROM 里面。 ​ 每次开机的时候&#xff…

【数据结构与算法】之双向链表及其实现!

​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 数据结构与算法 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 目录 1、双向链表的结构及概念 2、双向链表的实现 2.1 要实现的接口…

Mac版2024 CleanMyMac X 4.15.2 核心功能详解 cleanmymac这个软件怎么样?cleanmymac到底好不好用?

近些年伴随着苹果生态的蓬勃发展&#xff0c;越来越多的用户开始尝试接触Mac电脑。然而很多人上手Mac后会发现&#xff0c;它的使用逻辑与Windows存在很多不同&#xff0c;而且随着使用时间的增加&#xff0c;一些奇奇怪怪的文件也会占据有限的磁盘空间&#xff0c;进而影响使用…

Android studio顶部‘app‘红叉- Moudle ‘XX.app’ dosen’t exist in project

Android studio顶部app红叉- Moudle ‘XX.app’ dosen’t exist in project 1、现象&#xff1a; 运行老项目或者有时候替换项目中的部分代码&#xff0c;明明没有错但是Android studio就编译报错了。 1.1 Android studio顶部app红叉。 1.2 点击Build没有clear菜单&#xff0…

软考 - 系统架构设计师 - 嵌入式真题

问题 1&#xff1a; &#xff08;1&#xff09;.HTML 静态化&#xff1a;可以实现对系统经常访问的页面进行静态化以提高系统访问的效率&#xff0c;但系统页面通常需要数据库中的用户信息和用户选择来动态显示&#xff0c;因此不适合采用。 HTML 静态化&#xff1a; 将动态生成…

windows下已经创建好了虚拟环境,但是切换不了的解决方法

用得多Ubuntu&#xff0c;今天用Windows重新更新anaconda出问题&#xff0c;重新安装之后&#xff0c;打开pycharm发现打开终端之后&#xff0c;刚开始是ps的状态&#xff0c;后面试了网上改cmd的方法&#xff0c;终端变成c盘开头了 切换到虚拟环境如下&#xff1a;目前的shell…

ON1 NoNoise AI 2024 for Mac/Win:智能降噪,重塑影像之美

在数字摄影领域&#xff0c;图片质量往往受到多种因素的影响&#xff0c;其中噪点问题尤为突出。ON1 NoNoise AI 2024作为一款专为Mac和Windows平台打造的AI图片降噪工具&#xff0c;凭借其卓越的降噪性能和智能化的操作体验&#xff0c;成为了摄影师和图像处理专家们的首选工具…

NL2SQL进阶系列(5):论文解读业界前沿方案(DIN-SQL、C3-SQL、DAIL-SQL、SQL-PaLM)、新一代数据集BIRD-SQL解读

NL2SQL进阶系列(5)&#xff1a;论文解读业界前沿方案&#xff08;DIN-SQL、C3-SQL、DAIL-SQL&#xff09;、新一代数据集BIRD-SQL解读 NL2SQL基础系列(1)&#xff1a;业界顶尖排行榜、权威测评数据集及LLM大模型&#xff08;Spider vs BIRD&#xff09;全面对比优劣分析[Text2…

基于Springboot+Vue的Java项目-免税商品优选购物商城系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

故障转移-redis

4.4.故障转移 集群初识状态是这样的&#xff1a; 其中7001、7002、7003都是master&#xff0c;我们计划让7002宕机。 4.4.1.自动故障转移 当集群中有一个master宕机会发生什么呢&#xff1f; 直接停止一个redis实例&#xff0c;例如7002&#xff1a; redis-cli -p 7002 sh…

Linux环境变量(一)

一.main参数 如果你仔细看过编程书籍就会发现&#xff0c;对于主函数main函数也是有参数的&#xff1a; 首先&#xff0c;我们先来认识两个参数&#xff1a; int main(int argc,char* argv[]) {return 0; } 对于这两个参数&#xff1a;第一个参数int类型表示为第二个的个数…

[C++][算法基础]判定二分图(染色法)

给定一个 n 个点 m 条边的无向图&#xff0c;图中可能存在重边和自环。 请你判断这个图是否是二分图。 输入格式 第一行包含两个整数 n 和 m。 接下来 m 行&#xff0c;每行包含两个整数 u 和 v&#xff0c;表示点 u 和点 v 之间存在一条边。 输出格式 如果给定图是二分图…

字体反爬知识积累2

一、os模块中函数的应用 如何获取当前文件中所有文件的路径方法 这段代码使用 os.walk()函数来遍历指定目录 imgs 下的所有子目录和文件。具体来说&#xff0c;os.walk()函数返回一个生成器&#xff0c;可以在每次迭代中获取目录树中的一个元组&#xff0c;元组包含当前目录的…

【算法】删除链表中重复元素

本题来源---《删除链表中重复元素》。 题目描述 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2]示例 2&#xff1a; 输入…

Delphi Xe 10.3 钉钉SDK开发——审批流接口(获取表单ProcessCode)

开发钉钉审批流时&#xff0c;需要用到钉钉表单的Processcode&#xff0c;有两种方法 &#xff1a; 一、手动获取&#xff1a; 管理员后台——审批——找到对应的表单&#xff1a;如图&#xff1a; ProcessCode后面就是了&#xff01; 二、接口获取&#xff1a;今天的重点&a…

Redis消息队列-基于Stream的消息队列-消费者组

7.5 Redis消息队列-基于Stream的消息队列-消费者组 消费者组&#xff08;Consumer Group&#xff09;&#xff1a;将多个消费者划分到一个组中&#xff0c;监听同一个队列。具备下列特点&#xff1a; 创建消费者组&#xff1a; key&#xff1a;队列名称 groupName&#xff1a…

SimpleImputer缺失数据处理报错解决方案

作者Toby&#xff0c;来源公众号&#xff1a;Python风控建模&#xff0c;SimpleImputer缺失数据处理报错解决方案 今天有学员反馈缺失值代码报错&#xff0c;由于sklearn缺失值处理的包升级&#xff0c;下面把官网最新的缺失值处理代码奉上。 参考https://scikit-learn.org/st…
最新文章