
跨机集群实战地址的三重语义与两台机器下无解的边界主题跨机通信的地址语义 两机复制的固有限局先说结论这次跨机我踩了两件事一件是连不上一件是连上了但不敢写。第一件单机时地址是一个值跨机后它变成三个语义—— 改错任意一个症状都一样服务在跑、端口在听、注册也有链路就是不通。① 监听地址进程绑哪个网卡收包 ← 绑 127.0.0.1 → 跨机【永远连不上】 ↓ ② 注册地址注册中心里别人看到谁 ← 注册容器 IP → 消费方【直连不可达】 ⭐ 本次根因 ↓ ③ 连接地址副本回连/上报时用谁 ← 上报容器 IP → 切换成功但【回不来】 贯穿的一条认知修正 公网 IP 是 NAT、不在网卡上 → 绑私网 IP 只是【纵深防御】真正的公网闸门只有安全组第二件给主从复制加防脑裂参数之后出现了一个反直觉后果 —— 从库一挂主库拒绝所有写读还能用写全挂。想明白之后我发现这是两台机器下的无解问题不是配置错了。⚠️ 一层边界副本必须分布在会独立故障的机器上 → 只有两台机器时某个节点挂了就不能写 与 允许静默丢数据 只能二选一所以排查第一步永远是问一句“这个地址是给谁看的”—— 而不是先去怀疑业务代码而在谈可用性之前先承认边界。一、问题跨机 Dubbo 全断报No provider available from registry把秒杀副本挪到第二台机器后副本调用商品/订单的 Dubbo 接口全部失败客户端超时。端口在听、进程在跑、注册中心里也有 provider就是不通。根因注册进注册中心的地址是消费方连不上的地址。因为链路是**“消费方订阅接口 → 注册中心返回 provider 地址列表 → 消费方直连该地址”而注册中心只做注册发现、不做流量代理**它不是网关不会替你转发—— 所以注册进去的地址必须是消费者能直连的地址。实测发现注册中心里 provider 注册的全是容器网段地址形如172.x.x.x:协议端口因为容器默认解析到的就是容器 IP服务器 B 副本 → 问注册中心要 provider 地址 → 拿到 容器网段 IP:协议端口服务器 A 的容器网段 → 服务器 B 路由表里没有这个网段 → No provider available from registry / client-side timeout维度修复前修复后注册地址容器网段 IP宿主内网 IP端口容器内端口未发布映射发布后的宿主端口跨机调用❌ 不可达✅ 通本机调用✅同网段碰巧通✅⚠️为什么一直没暴露单机内同网段互通容器 IP 直连也通。这类坑只要不跨机就永远不会自己浮出来—— 它不是新引入的 bug是一直存在、只是没被触发。二、方案注册层改写而不是网络层打通路径做法评价✅ 注册层改写选定provider 显式注册宿主内网 IP 映射后的宿主端口服务框架为容器/NAT 场景提供的官方环境变量声明式、官方机制、无网段变更即失效隐患网络层回退服务器 B 加静态路由打通服务器 A 的容器网段服务器 A 零改动但属手工版 overlay依赖对端网段、无自愈、单向可达非规范做法三个关键细节都是不写就静默失效的那种协议端口在本项目里配成了-1自动分配—— Dubbodubbo协议的默认端口是 20880-1是自动选空闲端口的显式写法跨机必须显式固定否则根本没法做端口映射。注册的端口要填映射后的宿主端口不是容器内的端口—— 两个 provider 的宿主端口必须错开否则第二个起不来。改动面按需收敛—— 扫过代码后确认跨机依赖只有秒杀副本 → 商品/订单的 Dubbo 调用秒杀侧没有任何 HTTP 声明式调用。所以服务器 A 只改 2 个服务其余 19 个容器零改动。分层归属为什么一个用 host 网络、一个用 bridge组件网络模式为什么Redis / 哨兵host它没有注册中心唯一要求是主/哨兵看到的地址可达 —— host 网络天然满足秒杀副本bridge 显式覆盖它必须在注册中心注册宿主 IP→ 需要端口映射 显式覆盖与服务器 A 同构 一句话规则有没有注册中心决定用不用 host 网络。# 错误只发布端口注册地址仍是容器 IP → 跨机不可达ports:[宿主端口:容器端口]# 正确显式覆盖注册用的 IP 与端口端口为映射后的宿主端口environment:DUBBO_IP_TO_REGISTRY:宿主内网 IPDUBBO_PORT_TO_REGISTRY:映射后的宿主端口DUBBO_PROTOCOL_PORT:容器内协议端口三、⭐ 更深一层的认知三层语义里注册地址是最容易骗过你的那一层监听地址错了会立刻痛连不上就是连不上telnet一试便知。注册地址错了最阴—— 进程在、端口在听、注册中心里也明明有那个实例ss -lnt看一切正常。所有本地自检全绿只有跨机调用不通。而这背后是一条更本质的结论注册中心不是网关。它是名片墙不是信箱。你把名片贴上去别人是照着名片直接找上门的 —— 所以名片上必须写别人真的能走到的地址。顺着这条线第三个语义就顺理成章了被降级的一方也要能回得来。Redis 主从切换后原主库被降级成从库此时它要向新主上报自己的地址如果上报的是容器 IP服务器 B 根本连不回来 →复制直接断链。这就是切换成功但回不来的隐形杀手 —— 只配好选主是不够的回连地址也必须是对端可达的。能选出新主 ≠ 拓扑能收敛。顺带一个同族的坑哨兵的配置必须能落盘。哨兵每次状态变化发现从库 / 发现其它哨兵 / 选出新主都会重写自己的配置文件写临时文件 → rename 覆盖用来在重启后恢复状态自身 id、已知副本、已知哨兵以及被改写的监控哪个主库地址。写不进去时哨兵内存里照常工作监控、投票、切换全都正常但一旦重启就退回监视旧主库地址—— 而旧主库此时已经是只读从库 →又回到切换成功但回不来。⚠️ 单文件挂载不能被 rename 覆盖容器挂载点的固有限制→必须挂目录。这类问题不重启、不演练根本发现不了所以必须修而不是先放着。最后一条认知修正公网闸门只有安全组。我原以为把端口绑到私网 IP 不对外暴露在云上不成立—— 因为公网 IP 是 NAT 映射、根本不在网卡上外部 → 公网 IP:端口 不在任何网卡上 ↓ 云平台 NAT 层 DNAT 私网 IP:端口 包到这里才被监听看见 ↓ 监听 0.0.0.0 的进程 ✅ 命中 / 监听 私网 IP 的进程 ✅ 也命中所以监听0.0.0.0与监听私网 IP 的公网可达性完全等价。实测口径一批端口公网全 blocked唯一一个被放行的 80 端口通 —— 与它们各自的绑定方式毫无关系。正确的边界模型是分层的层手段作用L1边界安全组 / 防火墙决定包能不能到网卡 ←唯一真正的公网边界L2 监听面bind/ports指定 IP决定本地哪些地址接受连接 ← 绑私网 IP 属这层纵深防御L3 身份密码 / ACL决定连上后能不能执行命令L4 传输TLS防窃听 / 中间人一句话判断法先问包怎么到网卡L1再问谁在监听L2最后才问连上能干什么L3。把 L2 当成 L1就是这套误解的根源。四、⭐ 第二件两台机器下的无解 —— 为什么从库一挂主库就不让写了链路通了、双实例跑起来了我以为收工了。结果在一次演练里冒出一个反直觉的现象(error) NOREPLICAS Not enough good replicas to write.业务表现下单失败、库存扣减失败、分布式锁拿不到 → 读还能用写全挂。4.1 先建立直觉两个人的记账小组你和搭档每人一本账本。规矩是你负责记账主库搭档负责抄一份从库你记完就完事不等搭档抄完这就是异步复制为了快。你会时不时喊一声在吗心跳。如果没人应答你分不清是哪种情况情况 A他请假了情况 B电话线断了他的账本还在只是没更新还在而且他老板可能已经让他当正本了你继续记账✅安全你是唯一的账本❌危险你记的会作废你该怎么做继续记立刻停笔你分不清 A 和 B就只能选一个动作—— 停笔只是麻烦记了却作废是灾难 → 所以选停笔。这就是那个参数的全部含义“我主库没法确认数据已经安全复制出去时宁可不写。”它不是从库挂了所以主库坏了而是主库主动选择不写以避免写了也白写。4.2 两个根因根因一复制是异步的。客户端写命令 → 主库写进自己的内存/AOF →立刻回复成功不等、不确认从库→ 异步发给从库。所以默认情况下主库是先干了再说它并不知道数据到底有没有到从库。根因二主库分不清两种失联。场景 A从库真的挂了场景 B只是网络断了主库看到的connected_slaves 0connected_slaves 0一模一样真相它是唯一副本继续写是安全的对面可能已经选出了新主主库写的注定会丢该不该写应该写绝对不能写主库能分辨吗不能← 这就是全部问题的根源—一个参数、两种情况、只能选一种行为→ 只能一刀切宁可误拒也不静默丢数据。4.3 它真正要防的场景网络分区时间线t0 两台机器网络断开两台机器都活着 t5s 服务器 B 上的三个哨兵判定主库连不上 → 客观下线 t6s 哨兵提升服务器 B 的从库 → B 成为新主 t6s 服务器 A 上的服务【仍然能连本机主库】→ 继续写 服务和主库在同一台机器上网络分区对它们完全无感 t愈合 服务器 A 被降级为从库 → 全量同步 → 【刚才写的数据全部丢弃】→ 这个参数让服务器 A 在t6s 就停止接受写把静默丢数据变成明确报错。关键认知分区时继续写不是写进了安全的地方而是写进了一个即将被格式化的磁盘 —— 分区恢复时哨兵按配置纪元收敛到新主旧主被降级并全量同步覆盖它刚才写的全部作废。为什么宁可报错报错 → 用户看到失败、运维立刻知道、可重试可处理静默丢数据 → 一切看起来正常几小时后才发现无法挽回。4.4 为什么把从库放到主库同一台机器是伪解原因说明保护会失效分区时主库能看到本机从库→ 参数满足 → 继续写 → 场景 B 重新出现不是真冗余同一台机器上的主从一起挂对主机宕机零防护参数检查的是有没有从库不区分从库在不在同一台机器上。4.5 拓扑对照加副本到底能解决什么拓扑服务器 B 宕机服务器 A 宕机网络分区1 主(A) 1 从(B)←本项目现状拒写 ❌可写 ✅拒写 ✅1 主 2 从都在 B拒写 ❌可写 ✅拒写 ✅1 主 1 从(B) 1 从(A 本地)可写 ✅可写 ✅保护失效 ❌1 主 2 从B 第三台可写 ✅可写 ✅拒写 ✅完美结论两台机器下无解—— 要么某个节点挂了就不能写要么允许静默丢数据。真正的解法是把第二个副本放到第三台机器上。 记住这句话同一台机器上的副本在这台机器挂掉的场景里等于没有副本。这就是再给同一台机器加一个从库没有意义的原因。4.6 我的取舍成本 vs 完整性项决策理由是否加第三台服务器❌不加学习项目两台已经在按量付费第三台的月费换来的只是消除一个罕见场景不划算参数开在哪✅只开主库侧覆盖真正的风险场景分区下旧主库继续被写从库侧开了会导致切换后新主库拒写计划内停机演练 / 维护演练开关先把它设成0恢复后设回1结果接受这个局限“已知、已量化、有开关” 比 “不知道” 强得多4.7 如果以后有条件正确的演进方向方案做法效果加第三台机器1 主 2 从跨 3 台机任一节点故障都不影响写入 分区保护 ✅只对关键写要求同步关键写后调用WAIT 1 100等至少 1 个从库确认超时 100ms把全局拒写改成只对那一次关键写要求冗余可用性影响小得多业务层兜底写失败重试 告警即使拒写也不至于完全瘫共同原则副本必须分布在会独立故障的机器上 —— 这和哨兵官方要求三个哨兵放在会独立故障的机器上是同一条原则。五、踩坑清单认知 / 坑说明⭐三重语义框架同一地址分监听 / 注册 / 连接排查入口永远是这个地址给谁看而不是先怀疑业务代码⭐注册中心只做注册发现不做流量代理它不是网关、不会替你转发 →注册进去的地址必须是消费者能直连的地址注册容器 IP 单机碰巧通、跨机必断⭐协议端口默认自动选-1是隐形地雷跨机必须显式固定协议端口否则没法做端口映射⭐注册的端口是映射后的宿主端口不是容器内端口多个 provider 的宿主端口必须错开⭐优先注册层改写而非网络层打通官方环境变量是声明式机制静态路由属手工版 overlay依赖对端网段、无自愈、单向可达⭐降级也要能回得来被降级的节点要上报对端可达的地址否则切换成功但回不来、复制断链⭐多数派机制必须奇数哨兵容忍度 哨兵数−1/2 → 3 哨兵容忍挂 1、2 哨兵容忍挂 0数据节点不需要奇数复制不是投票⚠️单文件挂载不能被 rename 覆盖哨兵重写配置是写临时文件 → rename→必须挂目录写不进去时内存照常工作、重启才暴露⚠️替换密码前必须先判空命令块里禁止exit批量替换把密码换成空值会静默破坏配置且服务照起守卫写exit 1粘进交互式会话会直接断连⚠️跨机铺路的坑几乎全在流程/环境而非技术配置属主/权限容器内进程 uid 与宿主不同、挂载点属 root、环境变量文件权限过严导致工具跑不起来 ——没有一个是算法或架构问题⭐公网闸门只有安全组公网 IP 是 NAT、不在网卡上 → 监听0.0.0.0与监听私网 IP 的公网可达性完全等价绑私网 IP 是纵深防御不是边界⭐跨机一律走内网内网不限速、不计流量费走公网 又慢又花钱固定带宽机型会成为瓶颈⭐按需改造、改动面最小谁真的跨机调用就改谁服务器 A 只动 2 个服务其余零改动改造会顺手带来验证机会注册层改写后服务器 A 同时看到2 个 provider→ 顺带就能验证服务框架层的负载均衡⭐“防脑裂参数的适用前提是至少 2 个从库”只有 1 个从库时任何一台机器故障都会让写入停摆—— 这不是配置错了是副本数量不足时的固有局限⭐副本必须分布在会独立故障的机器上同一台机器上的主从在这台机器挂掉的场景里等于没有副本再加一个从库到同一台机器是伪解还会让分区保护失效⭐分区时继续写 写进即将被格式化的磁盘恢复时哨兵按配置纪元收敛到新主旧主被降级并全量同步覆盖 → 刚才写的全部作废宁可报错不要静默丢数据计划内停机要有演练开关停从库做演练/维护前先把它设成0、恢复后设回1已知 已量化 有开关比不知道强得多六、一句总结同一份集群一半是地址问题一半是边界问题地址有三重语义监听决定谁能进、注册决定别人走去哪、连接决定降级后能不能回得来而公网那道门从来不由绑定地址决定边界则是一条更硬的规律 ——副本必须分布在会独立故障的机器上否则你只能在某节点挂了不能写和允许静默丢数据之间二选一。本文基于一个个人微服务电商演示项目的真实实施记录整理涉及的环境信息已做脱敏处理。