1. 问题出现的真实场景从分库配置到连接失败先交代一下背景。我们做了一个模拟项目X数据量涨得比较快单库单表的压力已经有点明显了所以想通过中间件把数据拆到多个MySQL实例上。选的方案是mycat版本是1.6系列。配置分库的时候逻辑库、逻辑表、dataNode、dataHost都按文档配好了启动也提示成功结果一执行查询就抛异常日志里反复出现这么一行java.net.NoRouteToHostException: 没有到主机的路由当时第一反应是网络不通但用mysql客户端直接连后端数据库是完全正常的ping也没问题这就让人很费解。后来把mycat的日志翻了个底朝天才发现问题并不在mycat本身而是它和后端MySQL之间的连接路由走不通。这里要说明一下本文所有实例、配置、工程名均采用脱敏虚构处理只保留技术细节方便你对照自己的环境排查。2. 理解mycat分库的连接机制mycat本身不存数据它只是一个代理层。你发给它的SQL它会解析出要访问哪些分片然后通过它自己的连接池去连后端真实的MySQL实例拿到结果再返回给你。所以mycat和后端数据库之间的网络连通性决定了分库能不能用。这个环节里有两个容易忽略的点mycat连接后端数据库时的IP是你配置的dataHost里的ip不是你在应用里连mycat的那个IP。mycat的heartbeat心跳会定期去连后端实例如果心跳都失败它会把该dataNode标记为不可用后续SQL直接拒绝路由。报NoRouteToHostException的时候通常意味着TCP层已经走到了“路由不可达”的状态。也就是说网络包发出去了但在某个网关或主机上被回绝没有一条可以抵达目标主机的路径。听上去像是网络问题对不对但实际情况里有很大一部分是mycat自身配置或运行环境导致的“假路由不可达”。3. 第一次排查网络真的通吗我当时的排查步骤是先按传统思路来ping后端MySQL主机能通。telnet后端MySQL端口能通。在mycat所在主机上用mysql命令行直接连后端数据库能连。mycat启动日志看起来正常。这就怪了一切正常为什么mycat就是连不上。后来发现mycat连接后端时使用的地址并不是我们以为的那个地址。为什么这么说因为我配置dataHost的时候写的是内网IP但mycat所在的主机是一台双网卡机器默认路由走的另一张网卡而那张网卡到后端MySQL的网段恰好没有路由。直接ping或telnet的时候命令本身走了应用层的系统路由表没走到mysqld那一步所以看起来是通的。而mycat的Java虚拟机在建立socket时如果系统路由表里没有那个目的地址的路由项就会抛NoRouteToHostException而不是简单的Connection refused。这台机器的路由表长这样default via 10.0.0.1 dev eth0 10.10.0.0/16 dev eth1 scope link后端MySQL的IP是10.20.0.10它既不在eth1直连网段也没有一条路由指向10.20.0.0/16default路由又只能从eth0出去。而eth0那一边的网络本身到不了10.20.0.10所以内核在发包时找不到合适的下一跳直接返回了No route to host。这种问题在物理机上不多见但在虚拟化环境、容器环境、多网卡服务器上非常常见。你用系统命令去测觉得通实际程序跑起来就是不通核心差别就在路由表。后来还是在mycat所在主机上执行了这句命令才发现的ip route get 10.20.0.10结果输出里明确写着 “network is unreachable”那一刻就知道问题出在哪了。4. 根源解析NoRouteToHostException到底是谁抛出来的很多人遇到这个异常第一反应是mycat配置问题其实这个异常是操作系统底层网络栈返回的。Java的socket connect操作在操作系统层面如果发现目标网络不可达会返回ENETUNREACHJava直接映射为NoRouteToHostException。这里要补充一个容易混淆的知识点Connection refused目标主机的端口没有服务在监听或者防火墙主动拒绝了这种是能到主机但到不了端口。No route to host网络层已经判定目标主机路径不存在通常是路由配置、网段隔离、或目标主机关机等。所以如果你看到的是NoRouteToHostException就不要再纠结端口和防火墙了先确认路由和网络拓扑。mycat里的具体表现是heartbeat日志里持续出现NoRouteToHostException。原本可以正常查询的SQL开始报“Cant connect to MySQL server on xxx (NoRouteToHostException)”或“Connection refused”。mycat的日志文件里堆了相同的堆栈信息。很多团队在容器里跑mycat时也会遇到类似问题因为容器默认的bridge网络和宿主机网络不在同一路由域mycat容器不能用宿主机IP去连后端数据库必须保证容器到后端数据库的路由可用。5. 排查过程复盘详细操作记录下面是我当时的完整排查过程你按这个顺序走一遍基本能定位80%的问题。5.1 第一步确认mycat连接的是哪个IP打开mycat的schema.xml找到类似这样的配置片段dataHost namehost1 maxCon10 minCon1 balance0 writeType0 dbTypemysql dbDriverjdbc switchType1 heartbeatselect user()/heartbeat writeHost hostnode1 url10.20.0.10:3306 usermycat_user passwordxxxxxx / /dataHost注意url里写的是10.20.0.10:3306这个就是mycat要连接的地址。请确认这个IP和端口确实是后端MySQL的地址而不是映射后的VIP、漂移IP之类容易被混淆的地址。5.2 第二步在mycat所在主机上验证路由这里不用ssh到mycat容器里去看直接在宿主机上跑ip route get 10.20.0.10如果结果是10.20.0.10 via 10.0.0.1 dev eth0 src 10.0.0.5说明有路由问题不一定是路由。如果结果是RTNETLINK answers: Network is unreachable那基本就是路由缺失尤其你要确认mycat进程实际运行在哪个netns里别用宿主机的路由去判断容器内的网络。还可以在mycat主机上执行route -n看有没有到10.20.0.0/24这类的路由条目。比如有10.20.0.0/24 10.0.0.1 0.0.0.0 UG 0 0 0 eth0这就是OK的。没有的话就需要手动加一条静态路由。5.3 第三步检查防火墙和iptables规则虽然NoRouteToHost不一定和防火墙直接相关但不能排除某些云环境里的安全组策略强制返回不可达。检查一下本机iptablesiptables -L -n尤其留意OUTPUT链和FORWARD链是否有REJECT规则。此外如果mycat跑在云服务器上看一下安全组里是否放行了3306端口以及是否限制了源IP。5.4 第四步看mycat日志mycat日志默认在logs目录下一般是mycat.log。搜索关键字route或者heartbeat可以看到类似ERROR java.sql.SQLException: NoRouteToHostException: 没有到主机的路由注意看堆栈里的IP能直接看出它尝试连接的地址是什么。5.5 第五步检查mycat所在环境是否有多网卡或路由策略这是最常见也最隐蔽的问题。如果mycat跑在一台多网卡的机器上比如一张网卡走办公网一张网卡走业务网而默认网关在办公网那么mycat连业务网段的数据库时很有可能从错误的网卡出去导致路由不可达。解决办法是添加一条更精确的静态路由指定目标网段走业务网卡ip route add 10.20.0.0/24 via 10.0.0.1 dev eth0或者针对单个IP添加ip route add 10.20.0.10/32 via 10.0.0.1 dev eth0这里需要注意这条命令只是临时生效重启网络后会消失。要持久化在CentOS环境可以写到/etc/sysconfig/network-scripts/route-eth0里内容格式为10.20.0.0/24 via 10.0.0.1 dev eth0在Ubuntu环境写到/etc/netplan/下的yaml配置文件里或者在/etc/network/interfaces里追加up命令。提示容器环境里的情况更特殊宿主机加路由不一定能进容器要在容器所在network namespace内操作或者用docker run的--network参数指定host网络模式。6. 验证与后续优化让mycat稳定运行路由改好之后先在mycat所在主机上重新验证ping 10.20.0.10 telnet 10.20.0.10 3306 ip route get 10.20.0.10这三步都能通过后重启mycat服务观察logs/mycat.log。心跳日志如果变成INFO heartbeat for host1 is healthy那就说明已经正常了。接着在mycat命令行下执行show datasource;看一下dataHost的状态字段如果是1或2表示SYN或IDLE如果是-1之类的负数还是有问题。我当时修复完路由后还顺手做了几个优化这里一并分享将mycat的JDBC连接池的initialSize和minCon调小避免启动时大量并发连接导致部分连接因网络抖动失败。将heartbeat的间隔适当调大减少因网络瞬断导致频繁切换。在schema.xml里给dataHost加上了connectionInitSql确保连接建立后先执行一次set names utf8mb4避免字符集问题干扰排查。把后端MySQL的max_connections调大并开启skip-name-resolve减少反向DNS解析造成的连接超时。这些优化不一定每个环境都适用但遇到连接类报错时确实能少踩很多坑。7. 避坑心得同样的报错完全不同的原因这里再补充一个容易误判的真实案例。我的另一个项目里mycat后端数据库的地址其实是通着的但依然报NoRouteToHostException。后来发现这台机器上配置了多块网卡而Java进程绑定了其中一块网卡的IP去连接数据库。也就是说connect的时候指定了local bind address而这个本地地址对应的网卡无法路由到目标网络。这个情况在代码里一般是这样Socket socket new Socket(); socket.bind(new InetSocketAddress(192.168.1.10, 0)); socket.connect(new InetSocketAddress(10.20.0.10, 3306), 3000);如果把bind的地址改成可达的网卡IP或者干脆不bind让内核自动选择问题就消失了。mycat本身没有暴露这个参数但如果你在mycat的JVM启动参数里加了类似-Djava.net.preferIPv4Stacktrue这种也可能影响网络栈行为。再有一个容易被忽略的点某些云平台的安全组会丢弃特定源IP的包并附带ICMP unreachable消息内核收到后也会报NoRouteToHostException。这种时候单纯看路由表是看不出来的需要在云控制台检查安全组双向规则以及在源主机用tcpdump抓包确认是否有ICMP错误回包。比如tcpdump -i eth0 host 10.20.0.10 and port 3306如果看到大量“icmp host unreachable”的输出基本可以确认对端或中间设备在主动拒绝而不是本地路由缺失。8. 常用排查命令速查把上面所有排查手段整理成一个表方便你直接复制使用。排查目的命令或操作预期结果查看目标路由ip route get 10.20.0.10显示via某网关查看全部路由route -n能看到目标网段条目排端口连通性telnet 10.20.0.10 3306能连接成功抓包确认ICMPtcpdump -i eth0 icmp and host 10.20.0.10无大量unreachable查看mycat数据源状态show datasource;状态为正数查看监听端口netstat -tlnp能看到mysqld监听3306查看JVM网络栈jstack -l mycat进程号看是否卡在Socket.connect当你的环境里有防火墙策略尽量用telnet或nc而不是ping来判断。很多场景里ICMP被禁但TCP通或反过来单一命令验证不全面。注意改完路由后一定要重启mycat不要只reload配置。某些连接异常状态在运行时不会被自动清理必须重启进程才能恢复。9. 从一次报错到一套方法论这次问题虽然定位到根因只花了半天时间但刚开始也走了一些弯路。复盘下来有一个很大的体会遇到NoRouteToHostException这类底层网络异常不要立刻去翻mycat的官方文档也不要怀疑分库分表逻辑有问题。先把网络栈的链路理清楚确认从进程所在的网络命名空间到目标IP是否存在一条可用的路径再回到应用层排查。现在我在配置mycat或者任何代理中间件时都会先做三件事确认进程实际运行环境的IP、路由、网关是什么。用和程序相同的方式不是命令行简单ping去测试目标地址连通性。把mycat的dataHost配置和实际网络拓扑画出来对照检查是否有跨网段、跨安全域的情况。这个方法不止适用于mycat也适用于所有依赖JDBC连接后端数据库的中间件比如各种数据库代理、数据同步工具等。希望这篇复盘能帮你在遇到同类报错时少走弯路用最短时间定位到问题真正所在。