尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

私有化IM系统可靠性设计:高可用、消息不丢与故障演练

私有化IM系统可靠性设计:高可用、消息不丢与故障演练 你这系统可靠吗这是我做私有化部署项目时被客户问得最多的一句话。说实话每次听到这个问题我都有点犯难——不是答不上来而是一句可靠根本说不清楚我们在设计上花了多少心思。发一条消息从用户点下发送键到对方看到消息中间要经过接入网关、消息路由、存储、推送、拉取、多端同步这么一大串环节任何一环出岔子谈可靠性就变成了纸上谈兵。私有化部署即时通讯IM系统的可靠性不是挂了三节点就不会丢消息这种单一维度的工程口号而是一套从接入层到存储层、从状态同步到降级策略的体系化设计。这篇文章想回答的核心问题就是标题里那句到底在设计什么。我希望用完整的篇幅把这个问题拆开、揉碎把设计时的关键决策、取舍逻辑和实际踩坑都摊在台面上讲清楚给正在做方案选型或已经进入实施阶段的朋友一份可以直接参考的索引。1. 可靠性不等于高可用先把账算清楚1.1 一句话说清可靠性到底是什么高可用通常指的是服务不中断业界喜欢用几个9来衡量——99.9%、99.99%算的是全年停机时间。但可靠性是另一回事它指的是系统在故障条件下依然能兑现核心承诺。拿即时通讯举例子。假设某台服务器半夜宕机了你的负载均衡把流量切到了另一台客户端自动重连成功用户甚至没感觉到异常——这是高可用在起作用。但如果宕机的那一瞬间一条消息恰好进到了这台机器的内存里还没来得及落库这条消息就永久消失了。用户A发了一条晚上一起吃饭用户B永远没收到。服务没中断SLA没破但消息丢了。这不是高可用能解决的问题这是数据可靠性问题。所以我做设计时的第一件事就是把可靠性拆成几个可验证的承诺而不是一个模糊的形容词。对IM系统来说通常有四条消息不丢无论进程崩溃、机器断电还是网络分区已经确认发送成功的消息必须能从持久化存储中恢复。消息可达采用至少一次投递语义消息最终能推送到目标端或被目标端主动拉取。状态收敛在线状态、已读回执、多端消息顺序最终能收敛到一致而不是永远裂开。故障可恢复故障发生后系统能通过重放、对账、补偿机制恢复服务而不是只能靠人工修数据。1.2 高可用是手段可靠性是目标业界经常用可用性来近似可靠性但这两者在IM场景下有一个巨大的鸿沟可用性的损失是能被感知的消息丢失往往是无感的。用户发现自己发不出消息会去找发送失败的提示但用户永远不知道有一条消息本应该到达而没到达——这种损失是不可感知、不可追溯的对信任的伤害却是实实在在的。我见过一个典型的反面案例。某项目为了追求高可用把消息服务做成了无状态集群挂掉一个节点后流量自动切换服务全程可用。但消息数据存在本地磁盘上节点挂了之后磁盘跟着一起失联消息层面的恢复完全没做。结果是服务一直可用但每隔一段时间就有一部分消息神秘消失根本查不到原因。这就是把可用性当可靠性做的典型。正确的关系是高可用是支撑可靠性的手段之一但可靠性还需要数据冗余、一致性和恢复机制来兜底。设计时我会先问这条数据在哪个环节可能丢再问这个节点挂了服务是否会中断顺序不能反。1.3 可靠性的成本约束延迟、资源与复杂度可靠性设计不是无代价的每加一层保障都要在延迟、成本和系统复杂度上付出相应代价。举几个典型的权衡点同步复制 vs 异步复制同步复制确保主备不丢数据但每一次写都要等备机确认延迟会变高异步复制延迟低但主机故障时可能丢最近一小段数据。每次写都落盘 vs 批量落盘fsync 次数越多越可靠但磁盘IO是硬瓶颈性能会下降明显。很多系统在刷盘上做文章比如保证消息先写WAL预写日志再异步批量刷盘。最终一致 vs 强一致在线状态这类数据对强一致要求不高消息顺序对强一致要求高。全部做成强一致性能和复杂度都吃不消。可靠性设计的本质是在这些矛盾里找到平衡点而不是无脑叠加机制。后面讲到的每个设计决策本质上都是一笔钱花得值不值的账。2. 无状态接入层连接管理才是真正的状态2.1 网关可以无状态连接一定有状态接入层网关通常被设计成无状态服务可以随便水平扩展前面挂负载均衡流量随便分配。这是对的。但这里有一个关键陷阱——应用层可以无状态长连接一定是状态。一个TCP或WebSocket连接建立之后它归属于某个具体的网关节点客户端所有的收发消息都依赖这条连接一旦这个网关节点宕机连接就断了客户端需要重连到别的节点。无状态接入层真正的含义是处理一条消息不依赖本机上的历史数据但连接本身的状态归属是绕不开的。设计时必须想清楚网关节点宕机之后存量连接从断开到恢复需要多久恢复期间的消息会不会丢这里有一个常见误解有人觉得只要用到 Redis 或消息队列把在线状态集中管理网关就完全无状态了。实际上在线状态只是连接的一个附属品真正的问题在于客户端如何感知我的连接断了该换台机器。这个感知和重建过程是有时间窗口的窗口内的消息就要靠补偿机制兜住。2.2 连接归属与服务发现节点故障后客户端该连谁网关层的最基本可靠性设计是服务发现和故障转移。私有化环境里常见两种做法客户端配置一个接入域名域名背后是负载均衡Nginx/haproxy负载均衡后面挂多个网关节点。节点故障后自动摘除客户端下次重连通过域名就能连到健康的节点。客户端通过配置中心/注册中心拿节点列表本地做健康检查主动避让故障节点。我的建议是两种结合。负载均衡解决统一入口的问题客户端侧的健康检查和重连策略解决快速感知和主动避让的问题。只有负载均衡没有客户端侧感知TCP层超时可能要几十秒才能暴露故障这个时间内用户的体验就是消息发不出去。一个很容易被忽视的细节是如果网关节点直接暴露了IP客户端通过IP直连那么节点故障后客户端必须依赖本地的IP列表去重试。这种方案在私有化部署里很常见因为它简单、不依赖额外组件但它非常脆弱一旦一个节点出问题客户端要遍历所有IP逐个尝试才能找到可用节点故障恢复时间随节点数线性增长。2.3 重连风暴节点故障后最大的隐性风险网关节点宕机后所有挂在它下面的连接几乎同时断开客户端会在同一时刻发起重连。如果不加控制几万个重连请求同时打在负载均衡和健康的网关节点上轻则把剩余节点打得CPU飙高重则引发雪崩——新节点也扛不住挂掉一个故障变成集群故障。这时候指数退避加随机抖动就显得至关重要。我的习惯是首次重连延迟控制在几百毫秒让用户体感上跟手不至于一断就等很久。后续按指数退避增长2秒、4秒、8秒……但上限封顶在30秒左右避免退避过久导致消息延迟太高。每次重连延迟加一个20%~30%的随机抖动打散重连请求避免所有客户端在同一时刻发起连接。另外一个实操细节是重连成功后客户端要主动向服务端请求重连期间错过的消息不能被动等待推送。因为重连期间服务端可能在推消息但连接没建立推不出去客户端重连后如果不主动拉这些消息就停在服务端形成送达但不被感知的缺口。这个拉取逻辑可以跟可靠性章节里的离线消息机制统一设计。3. 消息链路可靠性不丢不重不乱序的实现路径3.1 一条消息从发送到送达走完的完整路径先把一条消息从发送端到接收端的完整路径画出来不是时序图是逻辑链路发送端 - 接入网关 - 消息服务鉴权、路由、生成消息ID - 持久化存储 - 触发推送 - 接收端在线则实时推送不在线则走离线消息 - 接收端离线后上线主动拉取 - 接收端确认。可靠性设计要回答的就是这条链路的每一个环节如果此刻宕机了消息在哪里会不会丢怎么补偿。我把链路拆成四层来逐层加保险。3.2 第一层发送端本地确认不给服务端甩锅的机会很多IM系统只做服务端保障客户端发消息直接POST到服务端就以为成功了这是很冒险的。移动端网络本来就不可靠电梯里、隧道里、弱网环境中请求超时、连接断开时有发生。如果客户端不做本地缓存一旦请求失败消息就消失在用户的输入框里。可靠的客户端发送流程应该是这样的用户点击发送后消息先写入客户端本地存储本地数据库或文件状态标记为sending。客户端把消息发给服务端等待服务端确认。服务端落库成功后返回确认客户端把本地消息状态改为sent同时记录服务端分配的消息ID。如果服务端返回失败或超时客户端定时重试重试期间消息状态保持sending界面显示发送中。用户切换网络或App重启后客户端检查本地有没有发送中的消息继续重试。这一步本质上是把消息不丢的第一道保险放到了客户端。有人可能会觉得私有化部署的企业场景里客户端网络环境相对可控没必要这么折腾。但实测下来恰恰是企业内部Wi-Fi网络最容易出问题——AP漫游、认证过期、带宽争抢各种奇怪问题都有把保险放在客户端是最稳妥的。3.3 第二层服务端存储先落库再回ACK服务端收到消息后会做什么很多人觉得理所当然但这里面有一个非常关键的顺序问题必须等消息完成持久化之后再给发送端返回ACK。为什么假设服务端先返回了ACK客户端就会把本地消息标记为sent如果此时服务端进程崩溃、消息还停留在内存里没落盘这条消息就彻底丢了——客户端认为发出去了服务端没有任何记录。这是可靠消息链路里最危险的一种丢法双方都认为没有问题实际上问题已经发生。落库的具体方案上大多数成熟IM系统会采用消息先写预写日志WAL再更新业务数据的方式。WAL是顺序IO落盘性能远高于随机IO所以可以在每次写入时都等fsync确认保证机器即使立即断电WAL也还在。服务端重启后可以从WAL恢复内存状态。除此之外消息存储本身也需要冗余。生产环境一般要求存储层做主从复制主库挂了自动切备库。这里有一个细节主从切换时如果从库落后了主库一小部分数据这部分数据切完后会丢失。所以要么采用同步复制性能损失要么做定期的数据校验和补偿。对于IM这种对消息丢失零容忍的业务我倾向于对核心消息通道做同步复制或者至少引入WAL复制具体看客户的预算和性能要求。3.4 第三层推拉结合推送链路不是唯一的路推送push的延迟低体验好但它依赖长连接在线的假设。长连接可能超时、被网络设备切断、被手机操作系统回收哪怕服务端把消息推出来了客户端也不一定收得到。所以成熟IM系统的消息投递都是推拉结合的推送负责实时性。服务端收到消息后立刻通过长连接推送给在线接收端。拉取负责兜底。客户端在以下时机主动向服务端拉取增量消息重连成功后、App从后台切换回前台后、本地检测到消息空洞时。离线消息兜底。接收端完全离线时消息留在服务端存储里等接收端以后上线时按时间线增量拉取。推拉结合还有一个隐性问题推送的消息和拉取的消息可能发生重叠。比如说服务端已经推了一条消息但客户端还没来得及处理同时又发起了一轮拉取拉回来的增量消息里包含了这条。如果客户端不去重同一条消息就显示了两遍。这个问题的解法放在下面的第四层。3.5 第四层消费端幂等不重靠什么保证去重这件事服务端只能减小重复的概率真正最有效的去重发生在消费端客户端。客户端需要维护一个最近已处理消息ID的去重表无论消息来自推送还是拉取先查重已处理过的直接丢弃。去重表的设计有几个考量范围要足够大。至少要覆盖推送后拉取这个时间窗口内的所有消息一般用滑动窗口维护最近几千条消息ID即可。数据结构要高效。如果常规的消息ID是text类型直接判断比较慢建议转成数字或二进制再存。有些实现用布隆过滤器做去重省内存但会有极低的误判概率要结合业务容忍度选型。消息ID本身要全局唯一。设计上一般用分布式ID生成器雪花算法或同类方案保证ID全局唯一同时是趋势递增的这样客户端才能靠消息ID做排序。不重还有一个容易被忽略的场景是群聊。群聊中一条消息要投递给N个接收端接收端各自去重没问题但服务端在存储群消息时要设计好群消息存一份各成员游标独立的模型避免每条消息为每个成员各存一份存储爆炸也避免一个成员拉取时影响到其他成员的游标。这是另外一个涉及存储设计的话题这里不展开但要意识到群聊的可靠性模型和单聊有所不同。关于乱序消息ID趋势递增客户端拉取时按ID排序就能恢复正确顺序。但如果依赖接收时间排序就会出问题——网络传输波动、推送通道的延迟差异都可能导致先发出去的晚到。所以统一以服务端分配的消息ID为顺序基准这是IM系统的一个铁律。4. 状态类数据的一致性在线状态、已读回执为什么这么难4.1 在线状态的本质一个分布式状态机在线状态在界面上显示为在线/离线/忙碌但本质上它是一个分布式的状态机用户设备上的客户端维护连接接入网关维护连接在线状态服务中心维护最终状态。三个地方都对状态有发言权一旦不一致用户看到的就是明明在线却显示离线消息发出去显示未读但对方其实已经看了。设计在线状态时最核心的问题是谁有权利改变状态答案必须是唯一的——只有与该用户建立了真实连接的接入网关节点才有资格上报这个用户在线。其他节点靠从状态中心订阅变化。这样设计能避免多个节点同时上报同一个用户的冲突状态但也要求状态变更事件在网关节点间传播发生故障时状态更新可能会有延迟或丢失。下一步要处理的是状态如何验证。仅仅上报还不够还要有心跳机制持续续租。客户端周期性地发应用层心跳一般20~30秒网关刷新该用户在线状态的租约。如果超过超时时间比如90秒没有心跳网关判定用户离线上报状态中心。心跳时间不能设太长故障发现太慢也不能太短空耗资源。生产环境建议用20~30秒心跳90秒超时的组合经实测体验和资源占用都很均衡。4.2 多端同步时间线模型才是核心早期的IM设计经常用每用户一个消息列表的模型同步时直接把整个列表推过去简单粗暴。但多端登录场景下这种模型会乱套手机和电脑同时在线手机上已读的消息电脑上还显示未读手机上的本地消息顺序和电脑上的不同步。现代的IM系统普遍采用时间线Timeline模型每个会话单聊或群聊维护一条逻辑上的时间线服务端给每一条消息分配一个单调递增的序列号seq。客户端同步时不需要同步全量数据只需要按照本端已知的最大seq拉取增量。每端各自记录自己消费到的seq位置互不干扰。这个模型的可靠性设计要点是seq一旦分配就不能改变消息一旦写入时间线就不能修改只能追加。如果你想撤回一条消息不是删除而是追加一条撤回事件的操作记录。这样各个端即使同步速度不同最终收敛到的状态也必然一致。多端同步还有一个隐性需求各端要能感知哪条消息已经被我读了。已读位置read cursor也是以seq形式维护的手机读到seq1000电脑读到seq980两个cursor独立存储互不影响。这个设计避开了全局已读/未读这种需要强一致的状态把问题转移到更简单的各端各自记录自己的已读位置。4.3 已读回执一个看似简单但牵一发动全身的功能已读回执是另一个典型的看着容易设计起来全是坑的功能。单聊已读相对简单接收端上报我读到某条消息了即可。但群聊已读就复杂了——需要一个已读成员列表和未读成员数量而且这个状态是实时变化的每个人读一下列表就要更新一次。设计已读回执时需要想清楚的几个语义问题已读的粒度。是我已读到某条消息还是我打开了会话很多场景下用户打开了会话但没往下翻算不算已读这是产品语义问题但要由技术方案保障可配置。回执的时效性。已读回执允许延迟到达但不能允许丢失后不可恢复。用户离线期间的已读状态要能在重新连接后补偿上报。回执的一致性级别。已读回执不需要强一致最终一致即可。一个用户看了消息回执晚几秒到达发送端完全可接受。这也意味着已读回执可以走异步通道不必占用和消息同等强度的可靠性资源。实操中我建议把已读回执设计成幂等上报客户端上报的每条已读回执带上消息ID和用户ID服务端不管收到多少次计算结果都一样。这样即使客户端重复上报、网络重传也不会把未读数量算错。5. 故障域划分与降级设计哪些可以慢哪些必须快5.1 核心信道与非核心功能的边界可靠性设计如果对每个功能一视同仁最终结果就是所有功能都不可靠。所以设计时一定要做故障域划分明确哪些能力是死也不能降的哪些是可以暂时牺牲的。以IM系统为例我的划分思路是这样的第一优先级文本消息的收发、离线消息拉取、基础鉴权。这几个能力挂了IM就不是IM了一切降级方案都要优先保障这条链路。第二优先级语音通话、视频通话、大文件传输。这些功能对带宽和延迟敏感但它们挂了不影响发消息这个核心承诺。第三优先级消息搜索、历史记录迁移、表情包、机器人、组织架构同步。这些属于增强功能故障时可以暂缓。划分的依据是这个功能故障时用户是否还能完成IM最核心的沟通闭环。能完成就可以降级不能完成就必须用最高规格的可靠性方案去保障。5.2 降级不是砍功能而是隔离故障降级设计最容易被误解成故障时砍掉一些功能。其实降级的目标是隔离故障保障主链路。举个例子文件服务是私有化部署里经常会出问题的环节——磁盘写满、对象存储不可用、带宽被大文件占用导致文本消息延迟暴涨。我的做法是给文件传输设计带宽限制和优先级调度文本消息走独立的高优先级信道文件下载走低优先级信道文件服务故障时只影响文件收发不影响文本消息。另一个例子是在线状态服务降级。如果状态中心出现故障不能因为查不到在线状态就把消息发送也停掉。正确的降级策略是在线状态查不到时按对方在线处理消息照常投递最多接收端多收几条才能感知对方真的离线。这条原则很重要——宁可让用户多做一次无用投递也不能让消息发不出去。5.3 真实故障场景下的降级流程设计故障降级不能等到故障发生了才临时决策要提前预设好方案。下面的表格列出几个常见的私有化故障场景和降级策略故障场景影响范围降级策略消息数据库主库宕机消息无法写入自动切备库备库不可用时启用本地缓冲队列先收消息后异步补写文件存储/对象存储故障图片、文件收发异常消息通道不阻断文件显示加载失败并自动重试推送通道/长连接网关故障实时推送中断客户端降级为轮询拉取间隔加大保证消息最终可达状态中心故障在线状态无法同步发送逻辑不做强校验消息按可送达处理带宽被大流量占满全链路延迟升高按消息类型分级限流优先保障文本消息和信令降级流程的触发和恢复也要自动化。不能靠运维半夜爬起来手动切流量设计时要明确何种指标异常自动触发降级降级后如何快速恢复并且降级和恢复都要有完备的日志留痕事后可以复盘。6. 集群脑裂与选主为什么大多数IM选择主从模型6.1 主从模型 vs 去中心化模型IM的选择分布式系统的数据一致性方案大约分为两类一类是主从模型写入都走主节点主节点故障后选举新主另一类是去中心化模型比如CRDT每个副本都能写入通过合并来解决冲突。IM系统为什么普遍选主从核心原因是用户对消息顺序有极高的要求。想象一下两个用户同时修改群名称去中心化模型允许两端的修改都生效然后靠合并算法解决冲突——这在协作文档里还行但IM里用户看到的是为什么我改的群名和一个小时后另一个人的修改冲突了体验非常差。主从模型下所有写操作都经过主节点由主节点分配顺序从机制上保证了顺序的确定性逻辑简单也容易排查问题。6.2 选主机制的本质不是谁当老大而是谁有资格写入很多人把选主机制理解成选举一个leader其实选主的真正目的是收敛写入权。在分布式系统里同一时刻如果有两个节点都认为自己可以写入就会发生双写冲突数据各写各的最终无法合并。所以选主机制的可靠性设计核心是保证同一时刻有且只有一个写入者。实现上有几个关键设计点多数派仲裁选主时一个节点必须获得超过半数的投票才能成为主节点。三节点集群需要2票五节点集群需要3票。这样做的好处是网络分区时只有包含大多数节点的分区才能选出主节点少数派分区自动进入只读模式或等待恢复避免两个主同时存在。租约Lease机制当选成功后主节点获得一个租约租约到期前它可以放心地写入租约快过期时需续约。如果主节点与集群失联租约到期后其他节点才能发起新选举。租约时间的设计很关键太短会导致主节点频繁续约网络抖动就触发重选举太长会导致故障转移过慢。时钟漂移问题租约依赖节点间的时间同步但时钟漂移可能导致一个节点认为租约还有效另一个节点认为租约已过期。严谨的做法是不要在租约里使用绝对时间比较用单调时钟monotonic clock而非墙上时钟并且租约时间要留足余量给时钟漂移留空间。6.3 三节点集群的实际取舍与脑裂处理私有化部署最常见的形态是三节点集群加上少数派变成两个处于同一网络分区的场景。假设三节点A、B、C中间网络断开了A在一个分区B和C在另一个分区。此时如果不做脑裂防护A和B、C同时认为自己是主就会出现双写严重时消息互相覆盖。过半仲裁机制下的处理结果是B和C构成多数派2/3选举出新主并继续提供服务A只有1票不满足过半自动降级为只读或停止写服务。这个降级过程要提前设计好不能让A在失去仲裁资格后还硬撑着接写请求。实操中还有一个容易忽略的问题网络分区恢复后A如何重新加入集群并追平数据如果A在分区期间还继续处理了一些本地写请求尽管逻辑上不应该恢复后的数据合并就会很痛苦。所以从设计上就要堵住这个口子少数派节点在失去仲裁资格后必须停止一切写操作必要时停掉用户请求直接返回服务不可用也不能让用户在不知情的情况下写到一半。这个决定虽然短期影响可用性但从长远看保护了数据的一致性底线。此外主从模型的另一个可靠性要点是主备数据同步的实时性。如果主节点故障切换后新主的数据落后一大截用户就会看到消息回滚——已经显示发送成功的消息切换后消失了。所以做主从复制时不能只做异步复制一定要能配置同步复制即主库写完、至少一个从库确认后才返回成功至少也要对核心消息通道启用同步复制。代价是写延迟增加但这正是可靠性设计到底在设计什么的一个典型答案用可接受的性能代价换取数据不丢。7. 可观测性与故障演练可靠性设计的最后一公里7.1 三个必须盯的核心指标可靠性设计做得再好如果系统对外不可观测故障发生时你就只能靠猜然后等用户投诉。可观测性的第一个任务是定义指标。IM系统我从实践中筛出三个必须盯的核心指标端到端消息延迟从发送端发出到接收端收到之间的时间差。这是用户体感最直接的指标正常应该低于秒级。可以按P50/P95/P99分别统计P99过高说明有长尾问题。推送失败率/拉取失败率消息投递子系统的健康状况。失败率突然升高往往是网络、节点或存储出问题的前兆。消息堆积深度服务端等待投递的消息积压数量。堆积持续增长说明消费速度跟不上生产速度可能会引发延迟雪崩。这三个指标要有统一的看板并且要设置合理的告警阈值。告警阈值的设计也有讲究不要按单条消息失败来设告警而应该按单位时间内失败率超过X%来设。单点噪音会被放大引发告警疲劳按比率设置既准确又足够敏感。7.2 全链路追踪每一条消息都要有身份证故障排查最快的方式是顺藤摸瓜。每一条消息从发送端进入系统的那一刻起就应该生成一个全局唯一的traceId跟着消息走完整个链路——接入网关、消息服务、存储、推送、拉取、客户端到达。每一跳都记录耗时和状态汇总成一条完整的调用链。有了全链路追踪排查问题的效率能提升一个量级。比如用户投诉我发消息很慢如果没有trace你只能逐台服务器翻日志大海捞针。有了trace输入messageId或traceId就能看到消息到底卡在哪个环节是客户端上传慢还是服务端落库慢还是推送通道阻塞。实现全链路追踪时要注意日志的上下文关联。不能只记录收到消息发送成功这种孤立的日志每条日志必须带上会话ID、消息ID、用户ID、节点IP。这个细节做不好日志量再大也只是数据垃圾。通用的技术方案一般是集成OpenTelemetry或类似框架给IM系统做轻量级埋点。7.3 故障演练可靠性不是设计出来的是练出来的我在交付私有化项目时最后都会建议客户做一轮故障演练。因为可靠性设计在图纸上再完美不经过真实的故障场景检验谁都说不准哪里会翻车。演练不是走个过场要真刀真枪地制造故障杀一个网关节点观察存量连接恢复时间是否符合预期重连风暴是否被有效抑制。杀数据库主库验证自动切换流程检查切换期间消息是否出现丢失。模拟网络分区验证少数派节点是否正确降级恢复后能否自动追平数据。把磁盘写满观察系统是有序降级还是直接崩溃。我曾经在一个客户的演练中发现一个让人冷汗直冒的问题数据库主库自动切换逻辑本身是正常的但备用库上读的恢复脚本少了一段重要的索引重建操作结果切完之后消息查询性能暴跌10倍。这种问题在监控图上完全看不出来只有真做过一轮切换才能发现。演练结束后的复盘同样重要。我会要求把演练中发现的每个问题都写成行动项指定负责人和解决时间。可靠性的改善是一个持续迭代的过程而不是一次交付就锁定的静态结果。8. 落地清单可靠性设计评审时可以直接对着逐项打勾最后把前面所有内容浓缩成一张可以在设计评审或交付验收时直接使用的检查清单。这既是对文章内容的总结也是我实际做项目时用的自检表。架构层[ ] 消息存储是否做到了先持久化再ACK[ ] 数据库是否启用了同步复制或者WAL复制[ ] 网关集群节点故障后存量连接恢复时间是否在可接受范围内[ ] 是否设计了重连的指数退避和随机抖动避免重连风暴[ ] 消息投递是否实现了推拉结合离线消息是否有兜底拉取[ ] 客户端是否维护了消息发送队列和重试机制[ ] 接收端是否实现了基于消息ID的幂等去重[ ] 在线状态状态机是否由单一节点上报避免冲突[ ] 各端消息同步是否基于时间线模型和seq[ ] 已读回执是否支持幂等上报和离线补偿[ ] 主从切换时是否通过多数派仲裁和租约避免脑裂[ ] 少数派节点失去仲裁资格后是否严格停止写操作[ ] 是否有核心指标监控、全链路追踪和告警[ ] 是否做过至少一轮真实的故障演练场景推演三问这台机器现在断电哪些消息可能丢能丢多少恢复要多长时间消息数据库挂了1个小时用户还能不能继续发文本消息能发的话数据会不会丢消息量突然暴涨10倍系统的瓶颈在哪里是CPU、磁盘、带宽还是某个锁这张清单是术前面七章是道。做可靠性设计评审时逐条过一遍清单可以发现大部分遗漏但真正的判断力还是来自对每条设计到底在防什么故障这个故障发生的概率有多大解决它的成本值不值这些问题的理解。根据我个人的项目经验私有化部署环境下最常被低估的故障往往是网络分区和磁盘故障——客户现场的交换机老化、双网卡配置错误、防火墙规则冲突这类基础环境问题远比软件本身的问题更隐蔽、更难排查。所以做可靠性设计时一定要把部署环境可能很恶劣这个前提放进去宁可把方案设计得保守一点也不要在上线后追悔莫及。
返回列表