今天来了解一下performance_schema库中的 host_cache表。这个表记录了 host 的缓存内容,缓存信息包括主机名和 IP,用于避免 DNS 查找。这个表的记录,可以在需要的时候协助排查连接问题。
要开启这个功能,要保障参数 performance_schema的状态 是开启的。
另外,MySQL 启动时若带有参数–skip-name-resolve,或者配置文件里有参数skip-name-resolve,则,即使performance_schema=on,表 host_cache 也不会缓存主机信息。
前几天碰到一个问题,主要就是这个功能的基础导致的。
问题:
昨天开发同事在使用 MySQL 连接数据库的时候,客户端无法连接上数据库。当时数据库的时候报错如下:
Caused by: com.mysql.cj.exceptions.CJException: null, message from server: "Host ' xxx' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'"
Caused by: com.mysql.cj.exceptions.CJException: null, message from server: "Host 'xxx' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'" at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[?:1.8.0_66]
解决办法:
在调整 max_connect_erros 从 100 到 300以后,能连上数据库了:
mysql> show variables like '%max%connect%er%';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| max_connect_errors | 300 |
+--------------------+-------+
1 row in set (0.06 sec)
这是一个治标不治本的临时解决办法。
果然接着发生问题:
过了一段时间,开发同事表示应用服务器又连不上数据库了。报错如下:
Failed to initialize pool: Could not create connection to database server. Attempted reconnect 3 times. Giving up.
2019-10-21 10:26:03.021 main
ERROR o.s.b.SpringApplication 842 reportFailure - Application run failed org.springframework.context.
ApplicationContextException: Unable to start web server;
nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
……
Caused by: com.mysql.cj.exceptions.CJException: null, message from server: "Host '92.168.225.131' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'"
解决办法:
最终处理办法,方法有多种:
mysql> flush hosts;
## 或者
mysql> truncate Performance_chema.host_cache;
或者找到mysqladmin
[root@my571 ~]# whereis mysqladmin
mysqladmin: /usr/bin/mysqladmin /usr/share/man/man1/mysqladmin.1.gz
[root@my571 ~]# /usr/bin/mysqladmin flush-hosts #清除hosts缓存
或者调整参数:
root@localhost|performance_schema> set global host_cache_size=0;
原因:
当时应用报这种错误的原因,其实是应用到 MySQL 服务器的网络延迟太大:
64 bytes from xxx: icmp_seq=50 ttl=64 time=24031 ms
64 bytes from xxx: icmp_seq=51 ttl=64 time=23008 ms
64 bytes from xxx: icmp_seq=52 ttl=64 time=21984 ms
64 bytes from xxx: icmp_seq=53 ttl=64 time=20960 ms
64 bytes from xxx: icmp_seq=54 ttl=64 time=19936 ms
64 bytes from xxx: icmp_seq=55 ttl=64 time=18912 ms
64 bytes from xxx: icmp_seq=56 ttl=64 time=17888 ms
64 bytes from xxx: icmp_seq=57 ttl=64 time=16864 ms
64 bytes from xxx: icmp_seq=58 ttl=64 time=15840 ms
64 bytes from xxx: icmp_seq=59 ttl=64 time=14815 ms
跟这种情况的报错的相关参数:max_connect_errors & connect_timeout。
数据库的connect_timeout = 10,网络延迟基本在 14 秒以上。参数max_connect_errors控制同一个 IP连接连续错误次数多过参数max_connect_errors的值时,这之后的连接mysql会阻止。
# 原文参考地址:
The value of the max_connect_errors system variable determines how many successive interrupted connection requests are permitted.
After max_connect_errors failed requests without a successful connection, mysqld assumes that something is wrong (for example, that someone is trying to break in),
解决这种问题的方法通常有以下几种:
1、调大 max_connect_errors;
2、flush hosts 或 mysqladmin flush-hosts 或者 truncate host_cache 表;
3、变量 host_cache_size 置为零。
方法一:只是延迟了这个错误再触发的阈值,当此 IP 再次连接 MySQL 失败次数达到参数max_connect_errors的新值,又会报同样的错误了。
方法二:是清理掉 MySQL 中记录的对应的IP主机相关cache记录,从零开始,达到 max_connect_errors 值大小,依旧会报错。
方法三:是临时禁用了host 的缓存功能,只是不记录 host cache 了,每次客户端连接,都会执行 DNS 查找。
# host cache相关介绍官档连接
https://dev.mysql.com/doc/refman/5.7/en/host-cache.html
官方资料介绍,host_cache的字段SUM_CONNECT_ERRORS是统计被视为“阻塞”的连接错误的数量(根据max_connect_errors系统变量进行评估)。 只计算协议握手错误,并且仅用于通过验证的主机(HOST_VALIDATED = YES)。
MySQL客户端与数据库建立连接需要发起三次握手协议,正常情况下,这个时间非常短,但是一旦网络异常,网络超时等因素出现,就会导致这个握手协议无法完成,
MySQL有个参数connect_timeout,它是MySQL服务端进程mysqld等待连接建立完成的时间,单位为秒。如果超过connect_timeout时间范围内,仍然无法完成协议握手话,MySQL客户端会收到异常,异常消息类似于: Lost connection to MySQL server at 'XXX', system error: errno,该变量默认是10秒。
参数--skip-host-cache的功能类似于 设置参数 host_cache_size=0,但是 host_cache_size更灵活。 host_cache_size能在 MySQL运行时通过修改全局参数调整是否缓存、缓存的大小。
即使启动 MySQL 服务时,加上了--skip-host-cache参数,在数据库运行时也是可以如常修改参数host_cache_size的值,只不过修改不会生效。
以下来验证 host_cache 中 sum_connect_errors 列的值与 max_connect_errors 参数:
1、主机名my 571 的为 MySQL 服务器,IP:131;主机名为 my80的做应用客户端,IP:129。
构造网络延迟的场景验证:
[root@my80 ~]# tc qdisc add dev eth0 root netem delay 11000ms
2、此时从 my80 上 ping MySQL 服务器:
[root@my80 ~]# ping 192.168.225.131
PING 192.168.225.131 (192.168.225.131) 56(84) bytes of data.
64 bytes from 192.168.225.131: icmp_seq=1 ttl=64 time=11000 ms
64 bytes from 192.168.225.131: icmp_seq=2 ttl=64 time=11001 ms
64 bytes from 192.168.225.131: icmp_seq=3 ttl=64 time=11001 ms
64 bytes from 192.168.225.131: icmp_seq=4 ttl=64 time=11001 ms
64 bytes from 192.168.225.131: icmp_seq=6 ttl=64 time=11001 ms
64 bytes from 192.168.225.131: icmp_seq=7 ttl=64 time=11001 ms
64 bytes from 192.168.225.131: icmp_seq=5 ttl=64 time=11001 ms
64 bytes from 192.168.225.131: icmp_seq=6 ttl=64 time=11001 ms
延迟基本是 11 秒左右,目前 my571 的数据库的参数 connect_timeout =10,连接是会超时的。
3、my80 上连接 MySQL 服务器,仅执行一次:
[root@my80 ~]# mysql -h192.168.225.131 -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2026 (HY000): SSL connection error: error:00000000:lib(0):func(0):reason(0)
[root@my80 ~]#
4、查看此时的host_cache 表,结果如下:
此时:sum_connect_errors=1,count_handshake_errors=1。
5、此时继续在my80 上反复登录3次,报错如下:
[root@my80 ~]# mysql -h192.168.225.131 -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2026 (HY000): SSL connection error: error:00000000:lib(0):func(0):reason(0)
[root@my80 ~]#
[root@my80 ~]# mysql -h192.168.225.131 -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2026 (HY000): SSL connection error: error:00000000:lib(0):func(0):reason(0)
[root@my80 ~]#
[root@my80 ~]# mysql -h192.168.225.131 -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2026 (HY000): SSL connection error: error:00000000:lib(0):func(0):reason(0)
[root@my80 ~]#
虽然报错连不上,但是还未出现“ Host '192.168.225.129' is blocked ”。因为当前连续的失败连接数未草果参数 max_connect_errors 的值大小。
此时日志报错信息如下:
2019-10-22T07:51:30.427453Z 11 [Note] Got timeout reading communication packets
6、查看当前参数 max_connect_errors=1+3;当前对应的 host_cache 表记录,如下:
由于参数 connect_timeout=10,网络延时超过10秒,导致连接MySQL失败。
在新增一次失败连接后,MySQL服务器上查询host_cache表的话,就会看到SUM_CONNECT_ERRORS变大 了,在原来的值上加 1,COUNT_HANDSHAKE_ERRORS也变大了1。
7、继续执行,再进行 3 次:
可以看到这次的“三次”连接都报错:“ Host '192.168.225.129' is blocked ”了。
此时仍然,sum_connect_errors=4 && count_handshake_errors=4,均等于 max_connect_errors 参数的设定值,大于这个值的连接都会被拒绝掉,sum_connect_errors 不会变更大,后面三次被拒绝因此报错 “ Host '192.168.225.129' is blocked ”。
此报错的解决办法:
8、然后再在 my80 上尝试登录 my571 的 MySQL:
虽然由于延迟变大,超过 mysql571 的connect_timeout 的限制,但是并未再报错“host xxx is blocked” 了。解决延迟后,就可以继续正常连接数据库了。
9、测试完成后,记得解除延迟 10s 的设置:
[root@my80 ~]# tc qdisc del dev ens33 root netem delay 11000ms
额外,参数 count_authentication_errors。
另外,从上面测试案例中看到的 host_cache 表中有一个字段“count_authentication_errors”,这个字段跟 sum_connect_errors 有的人可能会弄混淆。count_authentication_errors 记录的是来自客户端的连接中、输入密码错误的次数。
开始测试连接 MySQL 数据库中 count_authentication_errors的变化:
1、先清空 host_cache 的记录:
2、在 my80 上,执行多次错入的密码的数据库连接命令:
3、在 my571 上进入数据库查看表 host_cache:
此时,count_authentication_errors=7,
4、此时数据库相对的错误日志如下:
2019-10-13T07:37:25.282600Z 3 [Warning] IP address '192.168.225.129' could not be resolved: Name or service not known
2019-10-13T07:37:25.308431Z 3 [Note] Access denied for user 'iris'@'192.168.225.129' (using password: YES)
2019-10-13T07:37:26.821914Z 4 [Note] Access denied for user 'iris'@'192.168.225.129' (using password: YES)
2019-10-13T07:37:29.060450Z 5 [Note] Access denied for user 'iris'@'192.168.225.129' (using password: YES)
2019-10-13T07:37:31.075948Z 6 [Note] Access denied for user 'iris'@'192.168.225.129' (using password: YES)
2019-10-13T07:37:32.626528Z 7 [Note] Access denied for user 'iris'@'192.168.225.129' (using password: YES)
2019-10-13T07:37:34.258874Z 8 [Note] Access denied for user 'iris'@'192.168.225.129' (using password: YES)
2019-10-13T07:37:36.540025Z 9 [Note] Access denied for user 'iris'@'192.168.225.129' (using password: YES)
总结:
本次我们环境的数据库报错,是因为网络环境不好,导致完成连接数据库的时间(即三次握手协议完成的时长)超过MySQL 中定义的参数的 connect_timeout 的值,应用服务器不断尝试连接,最终尝试次数超过了 MySQL 的参数 max_connect_errors 的大小,导致的报错。
参数max_connect_errors决定了 MySQL 允许的每个 IP 持续连接数据库时发生连接错误的次数,对应的是表 host_cache 中的字段 sum_connect_errors 的值的大小,同时也跟 count_handshake_errors 相关;
表host_cache中字段 count_authentication_errors 的值大小,表示连接数据库时使用了错误的密码的次数。目前好像没有参数,可以使用 MySQL 插件控制。