前几天配置Mycat分库一执行带分片键的查询就直接抛java.net.NoRouteToHostException: 没有到主机的路由。当时我第一反应是schema.xml里的dataHost地址写错了翻来覆去检查了好几遍也没发现问题。后来沉下心来从网络层一步步排查才发现问题根本不在Mycat配置而在两台机器之间的网络路径上。这篇文章就把这次排查过程、背后原理和容易踩的坑完整写出来给正在做分库改造、或者已经上线Mycat但偶尔被后端连接异常困扰的同学做个参考。1. 先搞懂Mycat分库失败的链路再做排查1.1 Mycat在两套库之间到底做了什么Mycat本质上是数据库代理。业务应用连接的是MycatMycat再根据分片规则把SQL转发到后端的MySQL实例。对于业务方来说它始终只看到一个逻辑库但实际上查询可能落在好几台物理机上。我这次做的分库是把一张原本单库的单表按ID取模拆到两个MySQL实例上。schema.xml里配了两个dataHost每个dataHost下有一个writeHost分别对应两个MySQL节点。然后再通过rule.xml里的取模规则把数据分布到两个dataNode上。这个架构里有一条完整链路业务应用 - Mycat做路由和SQL改写 - 后端MySQL实例链路里任何一环出问题表现在应用层往往是数据库连接异常。而Mycat这类中间件会把底层的Socket异常直接向上抛所以在业务日志里看到的报错真实原因往往藏在后半段甚至网络层。这也是这类问题特别迷惑人的原因之一。1.2 配置分库时容易背锅的四个地方排查之前先把“配置本身可能出错”的点列一下这样后续排查不冤枉配置也能快速排除常见问题。我见过最多的问题集中在四个地方schema.xml里的dataHost地址或端口写错比如把MySQL实例IP写成了另一台机器或者端口写了3307。dataNode指向的database名称不一致实际库名和配置的dbName对不上读表时直接报表不存在。rule.xml里的分片规则字段和表字段不一致比如表主键是id规则却用了order_id。用户权限问题Mycat连接后端MySQL的用户没有远程访问权限。这四个问题里前三个和本次报错的表现都不一样。它们失败得更“靠近业务”错误信息通常是Table doesnt exist、Unknown database或者分片字段不存在的SQL语法错误而不是NoRouteToHostException这样的网络层异常。1.3 日志里看到NoRouteToHostException意味着链路断在哪一环日志里一旦出现NoRouteToHostException说明TCP连接在建立阶段就失败了。也就是说Mycat这台机器连后端MySQL的IP和端口时底层网络根本没有把包送过去。这个异常的特点是SQL还没到达MySQL就被拦住了。它不是MySQL返回的业务错误而是操作系统级别的网络错误。遇到它第一优先级不是打开schema.xml逐行看配置而是先确认“Mycat所在机器到后端MySQL主机”这段路通不通。网络不通配置写得再对也没用。2. NoRouteToHostException的真实含义它和连不上是两码事2.1 从内核和Java异常的关系说起java.net.NoRouteToHostException在Java里的中文提示是“没有到主机的路由”它对应操作系统层面的EHOSTUNREACH。这个错误码的意思是本机在尝试连接目标IP时判断出无法到达这个主机。这里有个容易被忽略的知识点“无法到达主机”和“无法连接端口”是两个完全不同的失败阶段。TCP连接必须先把报文从本机送出去经过路由器一层层转发到目标机器目标机器确认自己在线并响应然后双方才能继续建立连接。如果本机压根不知道该怎么把包送到目标IP或者沿途某个设备直接回了“这个主机不存在”的ICMP报文内核就会产生EHOSTUNREACH。Java拿到这个错误后Socket.connect就会抛出NoRouteToHostException。直观类比你要去拜访一个朋友结果到了小区门口发现整个小区根本不存在保安也查无此人你连门都进不去。这和“朋友在家但你敲门没人应”是完全不同的两件事。2.2 三种网络异常的判断对照实际排查中最常见的三种连接异常对应完全不同的原因。我做了一张对照表方便后续快速定位异常现象底层错误码典型场景排查方向NoRouteToHostException / 没有到主机的路由EHOSTUNREACH目标IP不存在、VIP未挂载、路由表缺失、中间设备返回主机不可达路由、ARP、VIP、ACLConnection refused / 拒绝连接ECONNREFUSED目标IP在线但端口没监听、防火墙返回异常服务状态、监听端口、防火墙策略Connection timed out / 连接超时ETIMEDOUT目标IP在线但包被静默丢弃、防火墙DROP、跨网段路由黑洞防火墙、抓包、路由转发看到NoRouteToHostException时不要先怀疑MySQL服务挂了。因为服务挂着但端口没起来通常报的是Connection refused。服务和网络同时出问题的概率远小于单纯网络问题。2.3 为什么这个问题经常被误判成“配置写错”Mycat这种代理层工具天然会让排查者把错误归到配置上。因为“分库失败”给人的第一联想就是规则写错、节点配错、SQL路由不对。但实际上只要链路中任何一个后端节点IP不可达Mycat都会在初始化连接池或者第一次查询时抛网络异常。我这次就是这样打开schema.xml反复核对dataHost、writeHost、URL还对比了rule.xml里的分片字段前前后后花了将近一个小时结果问题根本不在这些文件里。后面才醒悟过来既然报错是网络层异常就该先用网络工具把“Mycat到MySQL”这一段拔出来验证再回去看配置。中间件的报错要先回到底层链路验证而不是在应用配置里空转。3. 按层次排查从网络路径到Mycat配置的五步法3.1 第一层路由与ARP解析第一步永远是确认本机能不能到达目标IP。我在Mycat机器上先看路由表和ARP缓存ip route show ip neigh show | grep 10.0.0.21判断标准很简单如果目标IP和Mycat机器在同一子网就看ARP表里有没有对应条目。ARP条目状态是STALE或REACHABLE说明二层能通状态是FAILED或干脆没有条目说明这个IP在当前二层网络里没有主机响应。如果目标IP不在同一子网就要看默认路由或者指向目标网段的明细路由。没有对应路由内核会立刻判定Network is unreachable有路由但下一跳设备不可达则会表现为Host unreachable。顺着这条路很快就能缩小一半范围。3.2 第二层防火墙与ACL策略路由没问题接下来考虑中间设备拦截。这里要区分两个层次系统防火墙Mycat机器和MySQL机器上的firewalld或iptables。网络设备ACL交换机、路由器上的访问控制列表以及云平台的安全组规则。检查系统防火墙常用命令firewall-cmd --list-all iptables -L -n注意一个关键区别防火墙策略如果是REJECT对端会立即收到拒绝Java里往往表现为Connection refused。如果是DROP包会被静默丢弃客户端等不到任何回包表现通常是Connection timed out而不是NoRouteToHostException。所以一旦看到NoRouteToHostException优先考虑的是目标IP本身没人应答或者某个三层设备明确回了“主机不可达”的ICMP报文。这类策略一般藏在网络设备或云安全组里。3.3 第三层目标端口与服务状态网络层通了再看端口。用telnet或nc做一个快速三向握手探测telnet 10.0.0.21 3306 nc -vz -w 3 10.0.0.21 3306如果端口通会看到“Connected to”之类提示。不通则要分情况报Connection refused去目标机器上看MySQL是否在运行、监听地址是否包含Mycat访问的IP报超时往回检查防火墙或路由。有一个很容易忽略的场景MySQL多实例部署或者VIP漂移后监听端口虽然还在但监听地址可能只绑了某个网卡IP或socket文件没有监听在对外IP上。我在生产环境就遇到过MySQL绑了127.0.0.1Mycat却配了内网IP的情况。3.4 第四层Mycat自身的连接配置网络链路全部验证通过后才轮得到Mycat配置。要确认三件事dataHost里的URL、用户名、密码是否准确。dataNode对应的database是否在目标MySQL上真实存在。分片规则字段是否和表结构匹配。这一层排查不复杂但常常因为前面网络排查不彻底而被人忽略。我的习惯是先用MySQL客户端直接从Mycat机器连后端实例如果直连能通而Mycat不通那问题就在Mycat配置或Mycat所在进程的额外限制比如JVM网络栈、DNS解析上。4. 完整故障复盘一次分库查询报错到定位的路由问题4.1 现象与日志摘录某个内部项目做分库改造两个MySQL节点分别是10.0.0.21和10.0.0.22Mycat部署在10.0.0.11。配置完成后执行select * from t_order where id 10001应用层立刻报错。Mycat后端日志里出现这样的记录java.net.NoRouteToHostException: 没有到主机的路由 at sun.nio.ch.Net.connect0(Native Method) at sun.nio.ch.Net.connect(Native Method) at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:...) at io.mycat.backend.mysql.nio.MySQLConnection.connect(...)结合日志时间点Mycat在建立后端MySQL连接时失败。分片规则里这条记录应该落在10.0.0.21对应的dataNode上因此重点排查目标是Mycat到10.0.0.21:3306的链路。4.2 排查链路与关键命令我在Mycat机器上按顺序执行了下面这些命令# 1. 确认路由 ip route show # 结果10.0.0.0/24 dev eth0 scope link路由存在 # 2. 确认ARP条目 ip neigh show | grep 10.0.0.21 # 结果FAILED说明ARP请求无人应答二层就没有对应主机 # 3. 测试同一子网的另一台MySQL ping 10.0.0.22 # 结果通 # 4. 测试故障IP ping 10.0.0.21 # 结果Destination Host Unreachable立即失败到这一步基本可以判断10.0.0.21这个IP在二层广播域里没有主机认领。路由表没问题因为同一子网的10.0.0.22完全正常。问题聚焦到这台MySQL的IP是否真的存在于网卡上。登录MySQL所在主机查看本机IPip addr show果然网卡上只有10.0.0.22根本没有10.0.0.21。这个地址原本是一个VIP规划中应该动态挂在MySQL主机上但MySQL实例重启后VIP没有自动挂载回来于是整个网络里的人都在找一个不存在的IP。ARP广播无人应答内核判断Host UnreachableJava就把它翻译成了NoRouteToHostException。4.3 根因与修复修复动作本身不复杂。在MySQL主机上临时把VIP挂回网卡ip addr add 10.0.0.21/24 dev eth0之后在Mycat机器上重新执行探测ping 10.0.0.21 telnet 10.0.0.21 3306全部通过再跑之前的分片查询正常返回数据。但真正的教训在于VIP漂移机制本身没有正确执行Mycat只是第一个暴露问题的客户端。如果不修漂移机制下次MySQL一重启同样的问题还会出现。所以后续要找到负责维护VIP漂移的脚本确认开机自启动和异常恢复逻辑都没问题才算彻底闭环。5. 长得像网络故障的Mycat侧问题四个容易误判的坑有了这次经验后我又陆续处理过几个类似表象的问题有的是真网络故障有的则是Mycat侧或Java侧的问题但报错一模一样。这里挑四个典型的坑说一下。5.1 dataHost里写主机名解析到的IP不对MySQL节点如果通过主机名配置而Mycat机器上的/etc/hosts没同步或者DNS解析到了别的IP就会出现“看起来是网络不可达实际是解析错误”的情况。尤其在多套环境复制的配置里主机名相同但IP不同特别容易踩。排查命令getent hosts mysql-node-1 cat /etc/hosts更稳妥的做法是schema.xml里直接用IP地址。中间件配置里的网络标识能少一层依赖就少一层依赖主机名解析出现问题的时候往往还会带出IPv6优先级的连锁问题后面会专门说。5.2 IPv4/IPv6双栈环境下走了不存在的IPv6路由这个坑很隐蔽。如果schema.xml里用了域名而域名解析同时返回了IPv6地址AAAA记录Java默认会优先使用IPv6。但很多内网环境根本没有配置IPv6路由连接时就会报NoRouteToHostException。判断方法是在Mycat机器上看Java进程的解析结果和路由getent ahosts mysql-node-1 ip -6 route show如果确认走了IPv6但环境没有IPv6路由可以在Mycat启动脚本的JVM参数里强制优先IPv4-Djava.net.preferIPv4Stacktrue这是我处理过的真实案例应用连域名没问题但Mycat用同一域名连接后端却报“没有到主机的路由”折腾半天才发现是IPv6优先级的锅。5.3 防火墙DROP导致超时却被误读为NoRouteToHostException前面说过DROP通常表现为超时但有一种例外如果中间防火墙对某些主机返回了ICMPHost Unreachable客户端也会收到EHOSTUNREACH在Java里体现为NoRouteToHostException。换句话说不一定是路由表缺失也可能是防火墙明确告诉你“这个主机不存在”。排查时要同时看系统防火墙策略和网络设备上的ACL规则。我在云环境里遇到过安全组对某个源IP网段直接返回不可达的情况当时日志报的也是这个异常。所以看到NoRouteToHostException不能只盯着路由表要把防火墙、安全组、ARP三层都过一遍尤其是内网环境改动过ACL或者安全组的时候。5.4 连接池心跳参数与空闲连接回收配合不当Mycat每个dataHost里都有heartbeat配置连接池会周期性检查后端连接是否可用。如果MySQL端的wait_timeout设置得很短空闲连接被MySQL主动断开而Mycat这边心跳走了网络层却刚好因为瞬间网络抖动失败连接池可能把整个dataHost临时判断成不可用。这个场景下日志里偶尔也会带出网络异常但根因是连接生命周期参数不匹配。这类问题偏“偶发”不会像VIP丢失那样稳定必现。排查时要结合错误频率判断如果只是偶尔报一次并且很快自动恢复优先怀疑心跳周期、空闲超时和网络抖动的组合而不是链路整体断掉。6. 把这次教训沉淀成运维习惯6.1 固定的诊断顺序与工具组合经历这次故障后我给自己定了一套固定的诊断顺序后面再遇到类似报错基本能在10分钟内定位到问题层# 第一步路由与ARP ip route show ip neigh show # 第二步连通性测试先用ping再用端口 ping -c 3 mysql-ip telnet mysql-ip port # 第三步系统防火墙策略 firewall-cmd --list-all iptables -L -n # 第四步抓包确认包是否到达目标网络 tcpdump -i eth0 host mysql-ip and port 3306 -nn关键是坚持“先网络后配置”的顺序。中间件报网络异常先想到链路不要一上来就翻配置文件。配置文件的错误有自己特有的错误信息和网络异常长得很不一样。6.2 变更前做连通性基线检查分库上线前我会先写一个简单脚本从Mycat机器出发依次探测所有后端节点的端口连通性并把结果留档for host in 10.0.0.21 10.0.0.22; do nc -vz -w 3 $host 3306 echo $host:OK || echo $host:FAIL done这个脚本虽然简单但在变更运维中非常实用。比如MySQL实例重启、VIP漂移、防火墙规则调整之后先跑一遍基线检查再启动Mycat或执行分片查询可以大大减少故障窗口。6.3 日志、抓包与告警的配合日志负责告诉我们“什么时候、什么现象”抓包负责告诉我们“包到底到没到”。Mycat的日志通常比较粗糙只会记录连接失败的最终状态。要想还原过程最有效的方式是在Mycat机器和目标MySQL机器上同时抓包tcpdump -i any host mycat-ip -nn -w /tmp/mycat_trace.pcap抓完后看三点Mycat有没有发SYN包目标机器有没有收到目标机器有没有回SYN-ACK。包根本没发出去问题在Mycat本机包发出去了但目标没收到问题在网络链路目标收到了但没回包问题在目标机器防火墙或服务状态。另外把这类探测做成定时任务或监控脚本每隔几分钟探测一次后端端口一旦失败就告警。中间件和后端数据库之间的连通性值得和业务监控一样被认真对待。最后再分享一个个人体会。这次排查最耽误时间的不是技术而是“先入为主地认定配置错了”这个惯性。看到Mycat分库失败总想着规则或节点配置但Mycat只是个代理它自己不会生产数据也不会凭空产生网络错误。后端的机器、IP、路由、防火墙稍有风吹草动Mycat一定是第一个替你遭殃的组件。以后再遇到类似报错我第一句话都是先看网络再看配置。这个顺序能帮你省下太多不必要的熬夜排查时间。
阅读完成 · 觉得有帮助?