PAYATHON 2026

调用alipay.trade.query偶发Remote host terminated the handshake

支付小周

明确结论

Remote host terminated the handshake 表示连接在 TLS/SSL 握手阶段中断,并非 alipay.trade.query 返回的业务错误。只凭这条异常,无法判断支付宝服务器是否出现网络抖动。

这类问题偶发时,常见原因有:

  • 客户端与支付宝之间的网络链路瞬时中断。
  • 代理、防火墙、WAF、NAT 网关或负载均衡设备提前关闭连接。
  • 连接池复用了已被对端关闭的长连接。
  • JDK 8 的补丁版本较旧,TLS 实现、根证书或加密套件存在兼容性问题。
  • 运行环境发生 DNS 异常、丢包、连接重置或出口网络切换。
  • 支付宝网关或相关链路短暂异常,但这需要结合发生时间、请求日志和官方排查结果确认。

建议的排查步骤

1. 确认完整异常链

不要只记录 Remote host terminated the handshake,还要保留完整堆栈,重点确认其中是否包含:

javax.net.ssl.SSLHandshakeException
java.io.EOFException
java.net.SocketException: Connection reset
java.net.SocketTimeoutException

还需要记录:

  • 异常发生时间,精确到秒。
  • 调用的网关域名,不要只记录 IP。
  • 服务器出口 IP、机房和可用区。
  • JDK 完整版本,例如 1.8.0_202,不能只写 1.8。
  • SDK 版本和底层 HTTP 客户端配置。
  • 请求是否经过代理、防火墙、服务网格或 NAT 网关。
  • 同一时间其他 HTTPS 请求是否也出现异常。

TLS 握手失败通常发生在 HTTP 请求建立之前,所以异常中一般不会包含支付宝的业务响应码或 sub_code。

2. 检查 JDK 8 的具体版本

JDK 8 的不同更新版本在 TLS、证书库和加密套件方面存在差异。如果使用的是较早版本,建议先完成兼容性测试,再升级到仍受维护的较新补丁版本。

可以用下面的命令查看当前版本:

java -version

不要在未经验证的情况下直接修改或禁用安全算法。需要查看协议协商过程时,可以在测试环境临时打开 TLS 调试日志:

java -Djavax.net.debug=ssl,handshake -jar application.jar

这类日志数据量很大,还可能暴露证书、域名和连接细节,不适合在生产环境中长期启用。

如果排查发现客户端协商到了不合适的 TLS 协议,可以在充分测试后显式启用 TLS 1.2:

java -Djdk.tls.client.protocols=TLSv1.2 -jar application.jar

是否需要设置这个参数,要看当前 JDK 补丁版本和运行环境,不能只根据异常文本判断。

3. 排查中间网络设备

如果请求会经过公司代理、云防火墙、NAT 网关或服务网格,应重点检查:

  • 中间设备是否拦截 TLS 连接或重新签发证书。
  • 空闲连接超时时间是否短于客户端连接池的保活时间。
  • 是否存在连接数、端口数或并发数限制。
  • 异常发生时是否出现 TCP Reset、丢包或 SNAT 端口耗尽。
  • 多台应用服务器中,是否只有特定机器频繁报错。

如果异常集中在某个机房、出口 IP 或应用节点,应优先排查本地链路,不宜直接归因于支付宝网关。

4. 更新支付宝 SDK

当前使用的版本是 alipay-sdk-java-4.39.2.ALL。可以评估升级到支付宝官方目前支持的稳定版本,但升级前需要完成回归测试,特别是以下内容:

  • 签名和验签配置。
  • 证书模式或公钥模式。
  • 网关地址。
  • 请求超时和连接池行为。
  • JSON 序列化及业务模型兼容性。

升级 SDK 可以获得较新的依赖和修复,但不保证能解决所有网络层的握手异常。

为查询请求增加有限重试

alipay.trade.query 是查询操作。如果确认异常发生在网络或 TLS 层,并且没有收到业务响应,可以进行次数有限的退避重试。不要无限重试,也不要把所有业务错误都当作网络错误处理。

下面的示例只在异常链中存在 SSLHandshakeException 时重试,最多尝试 3 次:

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;

import javax.net.ssl.SSLHandshakeException;
import java.util.concurrent.ThreadLocalRandom;

public class AlipayQueryService {

    private final AlipayClient alipayClient;

    public AlipayQueryService(AlipayClient alipayClient) {
        this.alipayClient = alipayClient;
    }

    public AlipayTradeQueryResponse query(String outTradeNo)
            throws AlipayApiException {

        int maxAttempts = 3;

        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                AlipayTradeQueryRequest request =
                        new AlipayTradeQueryRequest();

                AlipayTradeQueryModel model =
                        new AlipayTradeQueryModel();
                model.setOutTradeNo(outTradeNo);
                request.setBizModel(model);

                return alipayClient.execute(request);
            } catch (AlipayApiException e) {
                boolean retryable = hasCause(
                        e, SSLHandshakeException.class);

                if (!retryable || attempt == maxAttempts) {
                    throw e;
                }

                long baseDelay = 200L * (1L << (attempt - 1));
                long jitter =
                        ThreadLocalRandom.current().nextLong(100L);
                long delay = baseDelay + jitter;

                try {
                    Thread.sleep(delay);
                } catch (InterruptedException interrupted) {
                    Thread.currentThread().interrupt();
                    throw e;
                }
            }
        }

        throw new IllegalStateException("Unexpected retry state");
    }

    private static boolean hasCause(
            Throwable throwable,
            Class<? extends Throwable> causeType) {

        Throwable current = throwable;
        while (current != null) {
            if (causeType.isInstance(current)) {
                return true;
            }
            current = current.getCause();
        }
        return false;
    }
}

实际项目中,也可以把 SocketTimeoutException 和特定的 SocketException 纳入重试条件。不过,需要根据调用阶段和异常原因判断,避免反复重试证书错误、协议不兼容等无法通过重试恢复的问题。

注意事项

  • 不要关闭证书校验,也不要使用“信任所有证书”的 TrustManager,否则会破坏 HTTPS 的身份验证能力。
  • 不要根据一次 TLS 异常就判断支付宝服务发生故障。
  • alipay.trade.query 可以有限重试,但支付、退款等可能改变业务状态的接口不能直接采用相同策略。
  • 收到正常的支付宝业务响应后,应根据 code、sub_code 和交易状态进行处理,不能再按网络异常重试。
  • 重试必须设置次数上限,并使用指数退避和随机抖动,防止瞬时故障造成请求洪峰。
  • 如果问题持续发生,应整理异常时间、出口 IP、完整堆栈、SDK 版本、JDK 完整版本和请求标识,通过支付宝官方技术支持渠道协查。只有结合服务端日志,才能确定是否由支付宝侧中断了连接。

备注:内容仅供参考。