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

背景

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

排查

查看日志,发现“支付聚合服务”调用“微信支付服务”的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.hqwc.cn/news/624205.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI宣布GPT-4-Turbo全面升级,GPT-4 Turbo 新增视觉理解能力,可同时处理文本和图像信息

OpenAI宣布GPT-4-Turbo全面升级&#xff0c;GPT-4 Turbo with Vision新增视觉理解能力&#xff0c;可同时处理文本和图像信息&#xff0c;极大简化了开发流程。 OpenAI宣布GPT-4 Turbo全面升级&#xff01;根据官方说法&#xff0c;这一波 GPT 的升级包括&#xff1a; 更长的上…

由于找不到krpt.dll,无法继续执行代码的5种解决方法

在正常使用电脑的过程中&#xff0c;当尝试启动某个应用程序或者执行特定功能时&#xff0c;系统突然弹出一个错误提示窗口&#xff0c;明确指出由于缺失关键性文件——krpt.dll&#xff0c;导致当前运行的软件无法正常读取并执行相应的程序代码&#xff0c;进而无法顺利完成预…

初级软件测试常见问题

1.JMeter &#xff08;1&#xff09;在http请求的时候&#xff0c;消息体数据中的数据需要用{}和“”标记起来&#xff0c;变量要用${}括起来。 &#xff08;2&#xff09;在响应断言的时候&#xff0c;要根据测试模式输出的内容来改变测试字段&#xff0c;假如输出错误可以把…

根据ELK官网指引部署ELK- ECK-Elastic-​ Kibana​-Learn-ELK-(一)

**Attention: 1、You need open the ELK official website and step by step to deploy . 2、If you copy my command ,you must check them if it not match your environment . 一、official website Elastic documentation | Elastic Check there. 二、 ECK简介…

Token2049主办方遭遇假门票风波,韩国罗马基金会Charles Lee损失50万美元

加密货币——遍地黄金&#xff1f;还是遍地陷阱&#xff1f; 尽管伊朗空袭以色列导致中东局势愈发紧张&#xff0c;但加密社区对当地市场的热情丝毫没有受到影响&#xff0c;不出意外的话&#xff0c;Token 2049这场全球最受瞩目的加密货币盛会将于4月18至19日在迪拜如期举行&…

Apifox接口测试教程(一)接口测试的原理与工具

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

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包含…

商家转账到零钱全攻略:开通、使用、区别与常见问题解答

商家转账到零钱是什么&#xff1f; 【商家转账到零钱】可以说是【企业付款到零钱】的升级版&#xff0c;商家转账到零钱可以为商户提供同时向多个用户微信零钱转账的能力&#xff0c;支持分销返佣、佣金报酬、企业报销、企业补贴、服务款项、采购货款等自动向用户转账的场景。…

ctf.show_web13

上传一句话木马 1.php文件&#xff0c;显示 再改后缀为.jpg&#xff0c;显示错误文件大小 用dirsearch扫一下 备份文件.bak 下载文件源码 <?php header("content-type:text/html;charsetutf-8");$filename $_FILES[file][name];$temp_name $_FILES[file][tm…

fork()的一道面试题

前言&#xff1a;题源 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> int main(void) {int i;for(i0; i<2; i){fork();printf("-");}wait(NULL);wait(NULL);return 0; }知道一点fork()这个系统…

C++:Static

1 在内存中所在的位置 回想C程序中内存的划分是什么呢&#xff1f;从上到下首先是内核空间&#xff0c;然后是栈内存&#xff0c;内存映射段&#xff0c;堆&#xff0c;数据段&#xff0c;和代码段。用一张图的解释就是&#xff1a; 在这里主要探讨static&#xff0c;因此主要…

C语言中原码,反码,补码与移位操作符

前言 我们现在学习一下C语言中移位操作符的使用&#xff0c;与原码、补码、反码的概念与使用&#xff0c;在原码补码反码中&#xff0c;正整数三个都相同&#xff0c;负数的话我们在下面详细讲解。 原码反码补码的概念&#xff1a;他们是整数的二进制表示的三种方式 正整数 在正…