type
status
date
slug
summary
tags
category
icon
password
AI summary

背景

最近有个需求需要通过公网从外部的MySQL拉取数据。在得到数据源信息之后,我们先通过命令行验证可以正常连接。但是配置到Datax上去跑任务拉数据的时候,却报错了:
Datax打印出的异常信息是com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure,并没有把原始错误打印出来,并且由于对公访问所以对方对我们的阿里云机房开了白名单,我们本地环境也连不上这个远程的MySQL实例,所以这块儿对问题的排查也增加了一定的困难。

分析

当时的怀疑是MySQL驱动的版本和MySQL Server的版本不适配:驱动用的是5.1.48的版本,算5.1.x里比较新的了。通过MySQL命令行连上后,查看了Server的版本是5.7.31。而前不久,我们刚刚接过一个5.7.30版本的,都可以正常访问,所以感觉不像是适配的问题。

抓包

于是我打算通过抓包来看看,总共抓了三次包:
  1. 之前可以成功连接的5.7.30版本MySQL的抓包(命令行)
    1. notion image
  1. 本次成功连接MySQL实例的抓包(命令行)
    1. notion image
  1. 通过datax未成功连接本次MySQL实例的抓包
    1. notion image
再附上后面两次抓包信息里,Server Greeting的响应结果:
notion image
对比上面三次的抓包信息,我们找到了如下差异
  1. 之前成功连接的5.7.30版本MySQL,没有SSL/TLS握手的流程。而另外两次Server Greeting的响应来看,服务端都想要切换到SSL(Switch to SSL after handshake = 1)。
  1. 没有SSL/TLS握手流程,客户端的身份认证请求会直接把账号密码带过去,而有SSL/TLS握手流程的,Login Request(也叫Handshake Response)包里不会带认证信息,而是会等待SSL/TLS通道建立成功之后再传递认证信息
  1. 第二次连接,确实切换到SSL/TLS握手,并且也成功建立了连接,整个链路为Server Greeting、Login Request、Client Hello、Server Hello...
  1. 而异常的请求链路为:Server Greeting、Login Request、之后就发了一个FIN包去中断TCP链接
难道是Server Greeting里MySQL Server给的响应有差别导致了后续走了不同的链路?但是从上面的两次响应来看几乎一模一样。
不过通过这样的对比,我感觉应该是在切换到SSL/TLS加密连接的过程中出了问题。我们尝试在客户端声明禁用加密连接:
禁掉SSL/TLS之后,果然就正常了。不过还有几个疑问没有得到解答,我们来看看:
  1. 为什么第一次连接并没有默认切换到SSL/TLS?
  1. 为什么开启SSL/TLS就不能正常工作了?

为什么第一次连接并没有默认切换到SSL

是否启用加密连接应该是由客户端和服务端共同协商决定的,服务端首先要配置SSL/TLS相关的证书之类的东西,这样才满足使用SSL/TLS的基本条件,然后会在Server GreetingHandshake)包里下发标志位,并且客户端也要启用SSL/TLS(8.0以上的客户端应该是默认启用)。我登录上去之后看了一下MySQL的配置,发现服务端确实配置了SSL:
而对于mysql-connector-java-5.1.48来说,当MySQL Server的版本大于5.7.0时,且Server配置了SSL/TLS时,就会默认启用加密连接:

为什么开启SSL/TLS就不能正常工作了

那么为什么开启SSL/TLS就不能正常工作了呢?还得先找到报错信息,Datax把异常吃掉了,我们自己用connector(这里用了8.0.16版本,没太大区别)创建连接的时候报了如下异常:
我们找到相关的代码看看
看起来应该是没有可用的协议,我们继续往上找,看看这个activeProtocols是怎么计算出来的
由于目标的MySQL Server版本是5.7.31-log,我们的JDK的版本是1.8.0_333,所以这里最终计算出来的enableProtocols[TLSv1_1, TLSv1]。并且这里取到的enableProtocols并不是所有的可用协议,还需要过滤:
里面配置了一个被禁用的算法列表,就包含了TLSv1.1TLSv1
notion image
官网的roadmap也能看出来,从jdk8u291版本开始,移除了TLS 1.0和TLS 1.1。禁用的原因就是这两个版本的TLS已经被证明不太安全了。
notion image
那我们是不是可以指定要用的TLS协议版本?
发现如上的配置也可以正常工作,并且建立了SSL通道。当然也有办法来让jdk恢复支持TLS 1.0和TLS 1.1,但是个人觉得不优雅,这里就不展开讲了。

总结

MySQL客户端和服务端在建立连接的时候会协商是否需要升级到SSL/TLS通道,如果需要的话,会沟通好SSL/TLS的协议版本,启动SSL/TLS握手流程。此次我们的MySQL Server已经做好了SSL相关的配置,但是在客户端获取可用协议的时候,由于jdk8u291开始移除了TLS1.0和TLS1.1,导致没有可用协议。最终产生了问题。解决方案可以是:
  1. 客户端显式配置不启用SSL
  1. 客户端显式配置需要使用的SSL/TLS的协议版本

参考

  1. 技术分享 | MySQL : SSL 连接浅析
  1. 故障分析 | Java 连接 MySQL 8.0 排错案例
  1. SSL/TLS协议运行机制的概述
  1. MySQL · 源码分析 · 鉴权过程
  1. Connection Phase
  1. why-can-java-not-connect-to-mysql-5-7-after-the-latest-jdk-update-and-how-should
  1. JDK8版本过高引起MySQL连接失败:javax.net.ssl.SSLHandshakeException: No appropriate protocol
  1. tlsv1-tlsv1-1-disabled-java
MySQL 连接阶段一文说透批量SQL
Loading...
黑微狗
黑微狗
一只普通的干饭汪🍚
Latest posts
browser-use 项目核心原理
2025-3-28
RocketMQ 4.6.0 Message Trace 功能异常排查
2025-3-28
关于怎么搭建一个这样的blog
2025-3-28
关于怎么给blog搞一个自定义的域名
2025-3-28
Excel导入需求升级——支持内嵌图片导入
2025-3-28
mysql流式查询中的一个坑
2025-3-28