前言
呵呵 最近再一次 环境部署的过程中碰到了这样的一个问题
我基于 docker 启动了一个 mysql 服务, 然后 挂载出了 数据目录 和 配置目录, 没有手动复制配置目录出来, 所以配置目录是空的
然后 我基于 docker 启动了一个 nacos, 配置数据库设置为上面的这个 mysql
然后 启动 nacos, 启动日志中老是会发现 nacos 连接 mysql 出现问题, mysql server 没有返回数据包 给客户端 什么的, 呵呵 这个问题 还是挺有意思的, 继续往下看
然后 我调整了 nacos 的 socketTimeout 的配置, 更新为 20s, 重新启动 nacos 呵呵 效果还是一样
然后 在普通的客户端 比如 navicate, 或者 jdbcTemplate 中建立连接到查询 也还是需要差不多是 10s 左右的时间, 这个很固定
总结一下 这里面存在两个问题
1. 为什么我 nacos 连接配置了 socoketTimeout, 为什么 客户端 和 mysql 服务器交互还是失败了?
2. 普通的客户端 比如 navicate, 或者 jdbcTemplate 中建立连接到查询 也还是需要差不多是 10s 左右的时间, 这个很固定, 这个 10s 到底是什么? mysql 服务器到底在干嘛?
为什么我 nacos 连接配置了 socoketTimeout, 为什么 客户端 和 mysql 服务器交互还是失败了?
这个涉及到几个概念, connectionTimeout, socketTimeout, loginTimeout
在这个场景里面主要涉及到的是 socketTimeout, loginTimeout
ExternalDataSourceProperties 中设置了 connectionTimeout 为 3s, 进而设置了 DriverManager.loginTimeout 为 3s
而这个配置是写死的, 因此 除了修改 nacos 代码之外的办法, 就是处理 mysql 服务器出现连接超时的问题了
客户端 和 服务器 的交互流程大致如下
1. 客户端 和 服务器 建立 tcp 连接请求
2. 服务器 发送 ServerGreeting 给客户端
3. 客户端拿到 ServerGreeting 之后发送 认证请求给服务器
4. 服务端的认证, 以及之后的流程
我们这里客户端是在 流程2 这里出现的问题, 设置的读取超时时间为 socketTimeout 和 loginTimeout 二者的较小者, 然后服务器那边 由于一些因素一直没有发送 ServerGreeting 给客户端, 因此 导致的超时
build:89, ExternalDataSourceProperties (com.alibaba.nacos.config.server.service.datasource) 中写死的配置 connectionTimeout 为 3s
connectTimeout 设置的是 客户端 和 服务端 创建 tcp连接 的超时时间
socketTimeout 设置的是 客户端 和 服务端 tcp 数据交互的超时时间
DriverManager.loginTimeout 设置的是登录认证期间 客户端 和 服务端 tcp 数据交互的超时时间
登录认证期间 客户端 和 服务端 tcp 数据交互的超时时间 是 socketTimeout 和 (DriverManager.loginTimeout - connect本身会损失一部分时间) 的较小者
在 登录 tcp请求 之后, 将 客户端 和 服务端 tcp 数据交互的超时时间 设置为了 socketTimeout
创建 dataSource 的地方
获取连接的时候设置 loginTimeout 的地方
普通的客户端 比如 navicate, 或者 jdbcTemplate 中建立连接到查询 也还是需要差不多是 10s 左右的时间, 这个很固定, 这个 10s 到底是什么? mysql 服务器到底在干嘛?
本问题中服务器这边的处理, 客户端这边连接了之后, 服务端处理连接请求, 完整的 stacktrace 是 thd_prepare_connection - login_connection - check_connection - ip_to_hostname
是位于 客户端 和 服务端 建立 tcp 连接之后, 服务端 向 客户端 发送 ServerGreeting 之前
最终调用的 glibc 库函数 gethostbyaddr, 这个库函数根据 客户端ip 查询 客户端主机名 的时候, 向 dns 查询, 没有查询到 有 10s 的超时时间
#0 0x00007ff9a20fc819 in poll () from target:/lib/x86_64-linux-gnu/libc.so.6
#0 0x00007ff9a20fc819 in poll () from target:/lib/x86_64-linux-gnu/libc.so.6
#1 0x00007ff98001e207 in ?? () from target:/lib/x86_64-linux-gnu/libresolv.so.2
#2 0x00007ff98001bc43 in __res_context_query () from target:/lib/x86_64-linux-gnu/libresolv.so.2
#3 0x00007ff99800b536 in _nss_dns_gethostbyaddr2_r () from target:/lib/x86_64-linux-gnu/libnss_dns.so.2
#4 0x00007ff99800b823 in _nss_dns_gethostbyaddr_r () from target:/lib/x86_64-linux-gnu/libnss_dns.so.2
#5 0x00007ff9a2118ee2 in gethostbyaddr_r () from target:/lib/x86_64-linux-gnu/libc.so.6
#6 0x00007ff9a21217d5 in getnameinfo () from target:/lib/x86_64-linux-gnu/libc.so.6
#7 0x000056048ea2e800 in vio_getnameinfo ()
#8 0x000056048de9606f in ip_to_hostname(sockaddr_storage*, char const*, char**, unsigned int*) ()
#9 0x000056048e2c8569 in ?? ()
#10 0x000056048e2c95d3 in thd_prepare_connection(THD*) ()
#11 0x000056048e3c103e in handle_connection ()
#12 0x000056048e9cfcd7 in pfs_spawn_thread ()
#13 0x00007ff9a255ffa3 in start_thread () from target:/lib/x86_64-linux-gnu/libpthread.so.0
#14 0x00007ff9a21074cf in clone () from target:/lib/x86_64-linux-gnu/libc.so.6
新增测试用例, 在宿主机上面跑一下, 并且在 mysql 所在的容器跑一下
新增测试用例 Test09GetHostByAddr.c
/**
* Test09GetHostByAddr.c
*/
#include "stdio.h"
#include "netdb.h"
#include "stdlib.h"
#include "arpa/inet.h"
#include "string.h"
int main(int argc, char **argv) {
struct hostent *host;
const char *add = "10.60.50.16";
char p[30];
inet_pton(AF_INET, add, p);
host = gethostbyaddr(p, strlen(p), AF_INET);
printf("hostname : %s", host->h_name);
return 0;
}
新增 build 脚本, 并执行, 观察 执行情况
新增 build 的测试脚本
gcc -g -o Test09GetHostByAddr Test09GetHostByAddr.c
date
./Test09GetHostByAddr
date
执行 build 脚本
root@ubuntu:~/docker/mysql/GetHostByAddr# ./build.sh
Thu Feb 24 23:21:46 PST 2022
./build.sh: line 5: 39782 Segmentation fault ./Test09GetHostByAddr
Thu Feb 24 23:21:56 PST 2022
skip-name-resolve 的影响 - 添加于 2023.08.15
这里是服务器 在尝试根据 ip信息 解析客户端的 主机名 信息
核心的调用链如下, 关键的调用是 glibc 的 gethostbyaddr_r
然后 本文中提到的 config 目录中, 影响这个问题更关键的是 skip-name-resolve 的这个配置, 更新之后的配置有这个 skip-name-resolve, 然后 mysql 服务器解析 主机名信息的时候 就 hang 住了, 这里是 10s
root@ubuntu:~/docker/mysql# cat config/conf.d/docker.cnf
[mysqld]
skip-host-cache
skip-name-resolve
在 my.cnf 中增加了 skip_name_resolve=ON 之后情况如下
更新之后, 就不会解析 主机信息, 进而防止了 gethostbyaddr_r 的 hang 住
完