客户端远程TCP连接MySQL服务,提示Host ‘host_name’ is blocked because of many connection errors

今天用windows客户端登录虚拟机上的MySQL的时候,出现如下错误:

Host 'host_name' is blocked because of many connection errors.
Unblock with 'mysqladmin flush-hosts'

出现这个问题的原因是我改了登录用户的允许的IP地址,用图形界面客户端登录的时候,报出来这个错误。根据错误提示执行了‘mysqladmin flush-hosts’,然后就可以了。

如果这样就可以,那也就不会有这篇博客了。因为我每次用客户端连接的时候都会有这个错误,每次都要执行上面的语句,所以决定来研究一下问题的根本原因。

下面是手册对这个问题的解释:

出现上面的错误意味着mysqld接收到一些给出的host的连接请求,但是这些请求在中间被截断。

max_connect_errors这个系统变量的值决定多少连续被中断的请求是被允许的。在这么多次失败的连接之后仍然没有连接成功,mysqld会认为是什么东西错了(例如,某人试图打断它),并阻断host进一步的连接,直到你执行了FLUSH HOSTS语句或执行了mysqladmin flush-hosts命令。

默认的mysqld会在连接失败100次之后中断连接。你可以在启动的时候调整max_connect_errors的值:

shell> mysqld_safe --max_connect_errors=10000 &

也能在运行的时候调整:

mysql> SET GLOBAL max_connect_errors=10000;

在出现这个问题的时候,首先应确认TCP/IP连接是否有问题,如果用网络问题,增加这个变量个值是不合适的。

了解了上面的介绍,已经可以解决问题,但是这并不是问题的根本原因,问题的根本原因是MySQL使用了host缓存,上面执行的FLUSH HOSTS和mysqladmin flush-hosts其实都是清空了host缓存表。MySQL5.7的host缓存表为performance_schema数据库下的host_cache表,可以对他使用SELECT语句,能帮助你诊断连接问题。

下面是手册对host缓存的介绍:

MySQL服务维护了一个在内存中的host缓存,它包含客户端的:IP地址,主机名,错误信息。服务只对非本地的TCP连接使用缓存。对于使用回环地址(127.0.0.1和::1)或使用UNIX套接字和命名管道以及共享内存建立的连接不适用host缓存。(我说怎么在测试的时候缓存表一直是空的呢,家里电脑直接用的deepin linux系统,没有虚拟机,也就不存在远程的TCP连接了)

对于每一个新的客户端连接,服务使用客户端IP地址去检查是否客户端host已经在host缓存里,如果没有,服务会尝试解析host。首先,它解析IP地址到一个主机名然后解析这个主机名获取返回的IP,然后和原始的IP进行比对,确认他们是否一致。服务在host缓存中储存这些操作的结果。如果缓存满了,最近最少使用的将被丢弃。

服务这样处理host缓存:

  1. 当TCP客户端通过给定的IP地址第一次连接服务,一条新的纪录被创建,包含客户端IP,主机名,客户端查找验证标志。最开始,host被设置为NULL,验证标志设置为false。这条记录也被用作后续相同的IP连接使用。

  2. 如果对于客户端IP的验证标志是false,服务尝试去做IP到主机名的DNS解析,如果成功了,主机名会被更新成解析出来的主机名,验证标志会被设置为true;如果解析失败,他的行为取决于失败是永久的还是暂时的。对于永久性失败,主机名依然是NULL,验证标志设置为true;对于暂时性失败,主机名和验证标志保持不变。(下一次从这个IP发起的连接会启用另一个DNS解析)

  3. 当处理给定的IP传入连接时发生错误,服务会更新当前IP对应的错误记录的错误计数器。具体可查看host_cache表。

在系统支持的情况下,服务通过调用线程安全的gethostbyaddr_r()和gethostbyname_r()来解析主机名。否则,线程获取互斥锁并调用gethostbyaddr()和gethostbyname()来代替,在这种情况下,在持有互斥锁的线程释放它之前,没有其他线程能够解析不在host缓存中的主机名。

服务使用缓存的几个目的:

  • 通过缓存IP到主机名的查找结果,服务避免了每次客户端连接都做DNS查找。取而代之的是,对于给出的host,仅在这个host第一次连接的时候进行查找。

  • 缓存包含了连接过程中的错误信息,有些错误标记为‘阻断’。如果在一个主机上连续发生太多连接失败,服务会阻止这个host的进一步连接。max_connect_errors系统变量决定被阻断之前允许的连接数量。

可以通过执行FLUSH HOSTS语句或mysqladmin flush-hosts命令取消阻断连接,冲刷host缓存。

最后一次从被阻断的主机尝试连接后(这里想说明的意思是host缓存表已经满了),其他的主机发生连接,被阻断的主机可能会变成不被阻断的状态。这种情况能发生的原因是在host缓存已经满了的情况下会抛弃缓存中最近最少使用的条目把空间让出来给新插入的条目,即客户端IP不在缓存中。如果被丢弃的条目是被阻断的,那么现在他就成为不被阻断的。

host缓存默认是开启的,可以通过讲host_cache_size设置为0来禁止它,也可以在启动或运行的时候设定。

在启动的时候添加–skip-name-resolve选项来禁用DNS主机名查询。在这种情况下,服务仅使用IP地址不需要主机名来匹配MySQL权限表中的行。在这个表中,仅有指定IP地址的账户才能被使用。(确认指定的账户有IP地址,否则可能不能连接)

如果你的DNS非常慢,并且有很多主机,你可以通过–skip-name-resolve选项来禁用DNS查询来提高性能。通过增大host_cache_size来增大host缓存也可以达到这样的效果。

通过–skip-networking选项可以完全禁用TCP/IP连接。

有些错误与TCP连接无关,可能在连接过程的早期发生(甚至IP地址还没有被知晓),或者不针对任何IP地址的错误(例如内存不足的情况),关于这些错误可以查看Connect_errors_xxx状态变量。

了解了这么多相关的知识,上面问题的解决办法又多了一个:将host_cache_size设置为0,禁用host缓存。

(完)