URL
date
AI summary
slug
status
tags
summary
type
背景
最近有个需求需要通过公网从外部的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
版本的,都可以正常访问,所以感觉不像是适配的问题。抓包
于是我打算通过抓包来看看,总共抓了三次包:
- 之前可以成功连接的
5.7.30
版本MySQL的抓包(命令行)

- 本次成功连接MySQL实例的抓包(命令行)

- 通过datax未成功连接本次MySQL实例的抓包

再附上后面两次抓包信息里,Server Greeting的响应结果:

对比上面三次的抓包信息,我们找到了如下差异
- 之前成功连接的
5.7.30
版本MySQL,没有SSL/TLS握手的流程。而另外两次Server Greeting的响应来看,服务端都想要切换到SSL(Switch to SSL after handshake = 1
)。
- 没有SSL/TLS握手流程,客户端的身份认证请求会直接把账号密码带过去,而有SSL/TLS握手流程的,
Login Request
(也叫Handshake Response
)包里不会带认证信息,而是会等待SSL/TLS通道建立成功之后再传递认证信息
- 第二次连接,确实切换到SSL/TLS握手,并且也成功建立了连接,整个链路为Server Greeting、Login Request、Client Hello、Server Hello...
- 而异常的请求链路为:Server Greeting、Login Request、之后就发了一个FIN包去中断TCP链接
难道是Server Greeting里MySQL Server给的响应有差别导致了后续走了不同的链路?但是从上面的两次响应来看几乎一模一样。
不过通过这样的对比,我感觉应该是在切换到SSL/TLS加密连接的过程中出了问题。我们尝试在客户端声明禁用加密连接:
禁掉SSL/TLS之后,果然就正常了。不过还有几个疑问没有得到解答,我们来看看:
- 为什么第一次连接并没有默认切换到SSL/TLS?
- 为什么开启SSL/TLS就不能正常工作了?
为什么第一次连接并没有默认切换到SSL
是否启用加密连接应该是由客户端和服务端共同协商决定的,服务端首先要配置SSL/TLS相关的证书之类的东西,这样才满足使用SSL/TLS的基本条件,然后会在
Server Greeting
(Handshake
)包里下发标志位,并且客户端也要启用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.1
和TLSv1
。
从官网的roadmap也能看出来,从
jdk8u291
版本开始,移除了TLS 1.0和TLS 1.1。禁用的原因就是这两个版本的TLS已经被证明不太安全了。
那我们是不是可以指定要用的TLS协议版本?
发现如上的配置也可以正常工作,并且建立了SSL通道。当然也有办法来让jdk恢复支持TLS 1.0和TLS 1.1,但是个人觉得不优雅,这里就不展开讲了。
总结
MySQL客户端和服务端在建立连接的时候会协商是否需要升级到SSL/TLS通道,如果需要的话,会沟通好SSL/TLS的协议版本,启动SSL/TLS握手流程。此次我们的MySQL Server已经做好了SSL相关的配置,但是在客户端获取可用协议的时候,由于
jdk8u291
开始移除了TLS1.0和TLS1.1,导致没有可用协议。最终产生了问题。解决方案可以是:- 客户端显式配置不启用SSL
- 客户端显式配置需要使用的SSL/TLS的协议版本
参考
- 作者:黑微狗
- 链接:https://blog.hwgzhu.com/article/mysql-connection-problem
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章