资讯动态

长连接心跳机制:参数推导、超时判定与重连实践

发布时间:2026/10/26 3:17:23 来源:尧图企业网站定制
1. 先把“心跳机制”这件事看清楚写过长连接的人都绕不开心跳机制。客户端和服务端之间挂着一条 TCP 连接数据能来回跑但这条连接到底“还活着”还是“早就断了只是没人告诉程序”在操作系统层面是拿不到确定答案的。心跳机制就是用来补这个信息缺口的一套约定双方按照固定的节奏互发一个极小的控制消息收到就说明链路和对方进程都还在收不到超过一定次数就判定连接不可用主动清理并重连。这套东西看起来简单——发个包、等个回包、超时断开——但真正落到线上环境参数怎么定、谁来发、超时算几次、弱网怎么办、会话表怎么管每一个点都能翻车。我见过一个长连接服务心跳间隔设成 5 分钟、超时 30 分钟结果用户端断网后要半小时才被清掉那半小时内所有下发消息都在往一个黑洞里写内存里堆了几十万个僵尸会话。也见过反过来的心跳 3 秒一次几万台设备一起报心跳服务端光处理 PING 就把 CPU 打满。这篇内容适合三类人看正在做 IM、推送、网关、设备接入这类长连接业务的同学做微服务治理、需要判断节点存活状态的研发以及单纯想把“为什么 TCP 有 keepalive 还要自己做心跳”这个问题想明白的学习者。我下面会从故障场景、方案选型、参数推导、代码实现、排查经验几个层面把心跳机制掰开讲一遍尽量给出能直接照抄的数值和能直接跑的代码也会说清楚哪些参数是拍脑袋的、哪些是有推导依据的。需要强调的是心跳机制不是一个孤立的组件它和超时、重连、熔断、读写分离、会话路由是耦合在一起的。你把心跳单独做好了但重连策略是 1 秒重试一次、无限重试那服务端在故障恢复瞬间仍然会被打垮。所以下面每一节我都会顺带讲清楚它和周边机制的关系。2. 心跳到底在兜什么底三类必须靠它兜住的故障2.1 TCP 已经“连着”为什么还要应用层心跳很多人第一反应是TCP 不是有 keepalive 吗内核不是会自己探测吗确实有Linux 上通过SO_KEEPALIVE打开默认tcp_keepalive_time是 7200 秒也就是空闲两个小时后才开始探测探测间隔 75 秒连续 9 次无响应才判定断开。这个默认值是为“长期空闲的可靠连接”设计的对在线业务来说完全不可用——两个小时的检测延迟业务侧早就雪崩了。更重要的是即使你把内核参数调到 30 秒它解决的也只是“对端主机是否可达”这一件事。它探测的原理是发一个不带数据的 ACK 探针只要对端内核协议栈还在就会回 ACK。这意味着什么意味着如果对端进程已经死锁、GC 卡住 30 秒、线程池耗尽、或者业务逻辑陷入死循环内核照样回 ACK连接在 TCP 层面依然是“健康”的但业务消息发过去根本没人处理。这种情况内核保活完全发现不了只有应用层心跳——由业务进程自己的线程去回一个 PONG——才能暴露。这里有个常见误区不少人以为“TCP 连接建立了服务就一定能收到消息”。实际上从发送缓冲区到对端应用读取之间有内核缓冲区、有协议栈、有对端事件循环、有业务线程池任何一环卡住都会导致消息堆积而不是报错。所以选择很清楚了内核保活可以作为第一层兜底成本低、不需业务参与但应用层心跳才是主判据。两者不是二选一而是分工。2.2 三类典型故障宕机、断链、静默回收我把需要心跳兜底的故障归纳成三类这三类的特征完全不同处理方式也不同。第一类对端进程崩溃或主机宕机。这种最好处理。进程崩溃时内核会发 FIN 或 RST正常情况下你的 socket 会立刻收到可读事件并返回 0 或者抛异常连接马上就能感知。主机断电宕机则不同不会有任何通知你的发送会一直成功写进本机缓冲区直到 TCP 重传超时——这个时间在 Linux 上是几分钟到十几分钟量级取决于重传次数。心跳的价值在这里体现为把这个检测时间从“分钟级”压缩到“秒级”。第二类链路中断。比如网线被拔、蜂窝网络切到飞行模式、Wi-Fi 断连。如果是中间链路断本机网卡还在发送照样成功入缓冲区TCP 进入重传同样要等重传耗尽。如果本机网卡直接 down会立刻收到错误这种情况反而快。所以最阴险的是“链路中间某段断了但两端网卡都正常”这种只能靠心跳超时发现。第三类中间设备静默回收连接。这是生产环境里最常见也最容易被忽略的一类。链路上有负载均衡、有防火墙、有运营商侧的会话表这些设备为了省资源会把长时间没有数据流动的连接从自己的会话表里删掉而且删的时候两边都不通知。表现出来就是客户端认为连接还在服务端也认为连接还在但数据发出去全部石沉大海。等客户端下一次真的发业务数据时才会发现写入成功但永远等不到响应。第三类是心跳间隔取值的最主要约束条件。很多中间设备的空闲连接回收阈值是 300 秒、900 秒或者 3600 秒具体值不公开、可配置、还可能被运维调整。你的心跳间隔必须显著小于这个阈值通常建议不超过它的三分之一。2.3 心跳的能力边界它能告诉你什么不能告诉你什么得说清楚心跳判定的语义。心跳超时只能得出一个结论“在最近这个时间窗口内我无法确认对端可服务”。它不等于对端一定死了——可能只是网络拥塞、可能只是对方 GC 停顿了 20 秒、可能只是你的心跳包在一个拥塞队列里排队。所以心跳超时触发的动作应该是“主动断开并重连”而不是“把对端标记为永久不可用”。反过来也成立心跳正常也不代表业务一定正常。心跳处理线程和业务处理线程如果是分开的心跳回得快业务可能已经堵死了。所以更严谨的做法是让心跳走完整的业务处理链路比如经过同一个事件循环、同一个线程池代价是心跳会受业务负载影响误判率上升或者做“分级心跳”轻量 PING 走快路径保连接重量级健康检查走慢路径验业务。这个取舍在网关类服务里尤其关键我后面在排查那节会展开。3. 方案选型几种心跳形态的取舍3.1 客户端主动、服务端主动与双向心跳谁发心跳直接决定了系统复杂度和资源消耗。客户端主动发是最常见的形态。好处是服务端压力可控——服务端只需要被动响应不需要维护大量定时器连接数上百万时服务端如果每个连接挂一个定时器内存和调度开销都很可观。坏处是服务端对“客户端是否还活着”的判断依赖客户端配合如果客户端实现有 bug 或者被恶意改造服务端就很被动。服务端主动发在设备接入场景里更常见尤其是下行指令为主的场景。好处是服务端掌握检测主动权可以随时调整探测节奏。坏处是定时器数量等于连接数服务端必须用时间轮这类高效结构来管理。双向心跳是两边都发各自独立判定。这种最稳但流量翻倍一般只在强一致要求高的场景用比如金融交易的长连接。我的经验是绝大多数场景选客户端主动发 服务端被动收 服务端侧做超时扫描。这个组合把定时器压力放在客户端分散把判定逻辑放在服务端集中实现简单也容易做灰度。3.2 传输层保活和应用层保活的组合用法前面说了内核 keepalive 检测慢、检不出进程假死。但它在一种场景下非常好用作为应用层心跳失效时的第二道防线。设想客户端应用层心跳线程因为某种原因挂了但进程还在内核 keepalive 能兜住至少把连接清掉。实操上我会这么配应用层心跳间隔 20~30 秒超时 2~3 个周期同时打开SO_KEEPALIVE把tcp_keepalive_time调到 120 秒、tcp_keepalive_intvl调到 30 秒、tcp_keepalive_probes调到 3。这样即使应用层完全失灵最迟 210 秒后内核也会把连接拆掉。# Linux 下调整内核保活参数的示例需要相应权限 sysctl -w net.ipv4.tcp_keepalive_time120 sysctl -w net.ipv4.tcp_keepalive_intvl30 sysctl -w net.ipv4.tcp_keepalive_probes3注意这些是全局参数会影响机器上所有 socket。如果你的机器上跑着多种业务建议在应用代码里用setsockopt对单个 socket 设置TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNT避免互相影响。3.3 消息格式越简单越好但别省掉关键字段心跳包的设计有个原则字段少但必须有能测 RTT 的东西。最小可用的心跳消息包含三样类型标识、时间戳、序列号。服务端收到 PING 后原样回显时间戳和序列号到 PONG 里客户端拿到后一减就得到 RTT序列号用来防止乱序和重复。为什么一定要测 RTT因为它是自适应调优的唯一依据。固定心跳间隔在弱网下必然要么太频繁费流量、费电要么太稀疏检测慢。有了 RTT 基线就可以在 RTT 变大时临时放宽超时阈值避免把“网络慢”误判成“连接死”。消息类型上建议用独立的控制帧类型不要和业务消息混用同一个 type。否则统计口径会乱服务质量监控里分不清哪些流量是心跳、哪些是业务。另外心跳包不要带业务字段不要试图在心跳里捎带“顺便拉一下配置”这种逻辑一旦心跳链路出问题配置逻辑也跟着挂故障面被放大。4. 参数怎么定从检测延迟倒推心跳间隔4.1 一条可以直接套用的推导公式心跳参数只有两个核心值间隔 T 和超时阈值 D。D 通常写成 N×TN 是允许连续丢失的次数。推导顺序应该是从业务能接受的最长检测延迟 D 倒推 T而不是反过来。假设你的业务要求“客户端断网后 60 秒内服务端必须感知到并清理会话”那 D 就定 60 秒。然后选 NN2 意味着只要连续两次心跳没回就断误判率偏高N3 是比较常用的折中N 越大越抗抖动但检测越慢。取 N3则 T 60/3 20 秒。业务场景可接受检测延迟 D建议 N推导出的 T备注移动端 IM90 秒330 秒兼顾耗电与流量网页长连接60 秒320 秒页面可见时才发设备接入网关180 秒360 秒设备数量大控制信令量交易类长连接15 秒35 秒代价是流量与电量内部服务健康探测9 秒33 秒通常用独立探测而非连接心跳这张表里的数值不是标准答案只是我在几类场景里验证过、跑得比较稳的起点。实际取值还要往下走两步校正。4.2 抖动余量为什么不能把 T 卡得太紧T 定下来之后必须留余量因为心跳包的往返时间不是常数。一次心跳从发出到收到回包要经过客户端事件循环调度、网络排队、服务端事件循环、服务端处理、再回来。任何一环出现几十毫秒到几秒的抖动都很正常。经验做法是T 至少要是 P99 RTT 的 10 倍以上。如果 P99 RTT 是 200 毫秒T 取 20 秒倍数是 100很安全。但如果你的服务端偶尔有 2 秒的 GC 停顿T20 秒的话单次心跳丢失的概率就不可忽略了再加上 N3 的容错勉强够用如果 GC 停顿能到 5 秒就必须把 N 提到 4 或者把 T 放宽。还有一个隐形杀手是客户端的定时器精度。移动端进入后台、系统省电模式、浏览器标签页被节流定时器间隔可能被拉长到几分钟甚至被完全暂停。这种情况在浏览器里尤其明显后台标签页的setTimeout最小间隔会被限制到 1000 毫秒甚至更长长时间后台还会被冻结。所以 Web 端的心跳必须结合页面可见性状态页面隐藏时降低频率或者暂停页面恢复时立刻补发一次并重置计时。4.3 和中间设备空闲回收阈值对齐这一条经常被漏掉。你的心跳间隔不仅要满足业务检测延迟还要小于链路上所有中间设备的空闲连接回收时间。实际排查时的做法是把已知的中间设备阈值列出来取最小值然后让 T 不超过它的三分之一。比如链路上一台负载均衡的空闲超时是 900 秒另一台防火墙是 300 秒那取 300 秒T 不超过 100 秒。如果按业务需求算出的 T 是 30 秒那自然满足不用管但如果业务允许 5 分钟检测延迟算出来 T100 秒就正好卡在边界上很危险应该主动压到 60 秒以内。这类阈值往往不在文档里也不保证不变。运维调整配置、网络改造、换机房都可能让某个设备的回收时间从 900 秒变成 300 秒。所以 T 的取值要留出足够的冗余不要为了省流量把心跳压到极限。5. 实现细节定时器、会话表与超时判定5.1 定时器选型别给每个连接挂一个 Timer连接数上万之后定时器的实现方式就决定了服务端的扩展性。每连接一个 Timer是最直观的写法Java 里ScheduledExecutorService或者 Go 里time.AfterFuncPython 里loop.call_later。连接数几千的时候没问题上到十万级定时器的堆维护和调度开销就很明显了而且大量定时器在同一时刻集中触发会造成明显的毛刺。时间轮是更合适的结构。把时间轴划分成固定数量的槽位每个槽位挂一个链表指针每 tick 前进一格走到哪一格就处理哪一格上的所有会话。插入和删除都是 O(1)内存占用固定特别适合“大量连接、统一周期”的场景。Netty 的HashedWheelTimer就是典型实现Go 生态里也有不少时间轮库。惰性检查 扫描是最省事的写法不维护每连接的定时器而是起一个固定周期的扫描协程每隔几秒遍历一遍会话表检查last_seen是否超时。缺点是精度受扫描周期限制超时判定会有最多一个扫描周期的误差优点是实现极简、内存开销小。连接数在十万以内、T 在 20 秒以上时我通常直接用这个方案扫描周期取 5 秒误差完全可接受。5.2 会话表的结构与并发安全会话表本质上是一个map[sessionID]*Session但有几个细节必须处理好。last_seen 的时间源要用单调时钟不能用系统时间。系统时间可能被 NTP 校正往前跳几秒就会导致大批会话被误判超时往后跳则导致长时间检测不到。Python 用time.monotonic()Go 用time.Now()配合time.SinceGo 的 Time 内部带单调时钟读数Java 用System.nanoTime()。读写锁的粒度要控制好。心跳处理是高频写扫描是低频读全表。如果用一把全局互斥锁心跳处理会被扫描阻塞。更好的做法是用分片锁sharded lock按 sessionID 哈希到 N 个分片每个分片一把锁或者干脆用sync.Map这类读多写少友好的结构。删除会话时要注意“检查-删除”的竞争。扫描协程判定某会话超时准备删除时可能正好有一个心跳包进来更新了last_seen。如果不加保护就会把一个刚复活的会话误删。做法是删除前二次校验在持锁状态下重新读一次last_seen再决定。5.3 弱网与移动端的自适应策略固定参数在弱网下表现很差。蜂窝网络切换、电梯、地铁、地下车库这些场景下 RTT 会突然从 50 毫秒飙到几秒。如果超时阈值是死的就会频繁误判断连重连而重连本身又要握手、要认证反而加重了网络负担和耗电。自适应的心跳我做过多轮比较有效的策略是基于 RTT 基线动态调整超时阈值维护一个滑动窗口的 RTT 样本比如最近 20 次算出中位数 M 和 P95 值 P。超时阈值取max(基础D, 4×P)并且在连续两次超时后才真正断开。检测到 RTT 明显上升P 3×M时临时把心跳间隔减半加快采样同时不下调超时阈值。RTT 恢复平稳后按指数退避缓慢恢复到基础间隔。这套逻辑的核心思想是把“网络变慢”和“连接真的断了”区分开。代价是实现复杂度上升需要维护状态机。如果业务对误判断连不敏感比如网页长连接重连成本很低就别搞自适应直接用固定参数简单可靠。6. 完整落地一套能跑起来的心跳保活实现6.1 协议约定先定协议。我用一个极简的 JSON 帧来演示实际生产里通常用二进制协议Protobuf、自定义 TLV以省带宽。PING: {t:ping,ts:1710000000123,seq:42} PONG: {t:pong,ts:1710000000123,seq:42}ts是客户端发送时刻的毫秒时间戳服务端原样回显seq是自增序列号。客户端收到 PONG 后用当前时间减去ts得到 RTT用seq匹配请求防止把迟到的旧 PONG 当成新请求的响应。6.2 服务端会话表与超时扫描Python asyncio 版本核心就是一个带单调时钟的会话表和一个扫描协程。import asyncio import time import json HEARTBEAT_TIMEOUT 60.0 # 秒对应 T20, N3 SWEEP_INTERVAL 5.0 # 扫描周期 class Session: __slots__ (sid, writer, last_seen, closed) def __init__(self, sid, writer): self.sid sid self.writer writer self.last_seen time.monotonic() self.closed False def touch(self): self.last_seen time.monotonic() def close(self, reason): if self.closed: return self.closed True try: self.writer.close() except Exception: pass print(fsession {self.sid} closed: {reason}) class SessionManager: def __init__(self): self.sessions {} def add(self, sid, writer): self.sessions[sid] Session(sid, writer) def touch(self, sid): s self.sessions.get(sid) if s: s.touch() def remove(self, sid, reasonnormal): s self.sessions.pop(sid, None) if s: s.close(reason) async def sweeper(self): while True: await asyncio.sleep(SWEEP_INTERVAL) now time.monotonic() for sid, s in list(self.sessions.items()): if now - s.last_seen HEARTBEAT_TIMEOUT: # 二次校验避免与刚到达的心跳竞争 if time.monotonic() - s.last_seen HEARTBEAT_TIMEOUT: self.remove(sid, heartbeat_timeout) async def handle_conn(reader, writer, mgr): sid fconn-{id(writer):x} mgr.add(sid, writer) try: while True: line await reader.readline() if not line: break msg json.loads(line) if msg.get(t) ping: mgr.touch(sid) pong {t: pong, ts: msg[ts], seq: msg[seq]} writer.write((json.dumps(pong) \n).encode()) await writer.drain() else: # 业务消息同样算作活跃信号 mgr.touch(sid) # ... 业务处理 except Exception: pass finally: mgr.remove(sid, peer_closed)这段代码有两个要点值得强调。第一mgr.touch(sid)在处理任何消息时都调用而不只是 PING——业务消息本身就是最强的活跃证据没必要让客户端在发了业务消息之后还额外发一次心跳。第二sweeper 里的二次校验是为了挡住“扫描判定超时”和“心跳刚好到达”之间的竞争窗口。6.3 客户端发送、RTT 统计与重连退避客户端侧的逻辑比服务端多一层——重连。重连策略写不好服务端重启时会被瞬间打爆。import asyncio import json import time import random T 20.0 TIMEOUT 60.0 class Client: def __init__(self, host, port): self.host, self.port host, port self.seq 0 self.rtt_samples [] self.backoff 1.0 def next_seq(self): self.seq 1 return self.seq async def run(self): while True: try: await self._connect_and_serve() except Exception as e: print(fconnection error: {e}) # 指数退避 抖动避免集群同时重连 wait min(self.backoff, 30.0) * (0.5 random.random()) print(freconnect in {wait:.1f}s) await asyncio.sleep(wait) self.backoff min(self.backoff * 2, 30.0) async def _connect_and_serve(self): reader, writer await asyncio.open_connection(self.host, self.port) self.backoff 1.0 # 连上就重置退避 hb_task asyncio.create_task(self._heartbeat(reader, writer)) try: while True: line await reader.readline() if not line: break msg json.loads(line) if msg.get(t) pong and msg.get(seq) self.seq: rtt time.time() * 1000 - msg[ts] self.rtt_samples.append(rtt) if len(self.rtt_samples) 20: self.rtt_samples.pop(0) finally: hb_task.cancel() writer.close() async def _heartbeat(self, reader, writer): while True: await asyncio.sleep(T) seq self.next_seq() payload {t: ping, ts: int(time.time() * 1000), seq: seq} writer.write((json.dumps(payload) \n).encode()) await writer.drain()backoff从 1 秒开始翻倍、封顶 30 秒、每次乘一个 0.5~1.5 的随机因子这套组合能有效防止“服务端重启后十万客户端同时重连”的惊群问题。这个抖动因子经常被省略但它在故障恢复场景下价值极高我强烈建议加上。6.4 参数验证别靠猜压一把参数定完必须验证。我的验证方法分三步。第一步单连接长跑。挂 24 小时统计心跳成功率和 RTT 分布。重点看 P99 和最大值——如果 P99 接近 T 的十分之一说明余量不够得放宽。第二步连接数阶梯压测。从 1000 起每次翻倍观察服务端的 CPU、内存、GC 频率和心跳处理的 P99 延迟。找到拐点在哪拐点之后说明服务端的定时器或锁结构撑不住了该换时间轮或者分片锁。第三步故障注入。这是最容易漏的一步。要模拟的场景包括直接kill -9服务端进程验证客户端重连和退避、用防火墙规则丢弃双向数据包验证心跳超时和会话清理、把一侧的网络延迟注入 3 秒验证误判率。第三步跑出来的问题往往比前两步加起来还多。7. 常见问题与排查实录7.1 典型现象与对应原因速查现象可能原因排查手段处置会话在服务端大量堆积超时判定没生效或阈值过大查会话表大小随时间变化曲线缩短 D检查 sweeper 是否在跑客户端频繁重连超时阈值小于实际 RTT 波动抓包看 PING/PONG 时间差分布放宽 D 或引入自适应心跳正常但业务无响应心跳走快路径、业务线程池堵死分开统计心跳与业务 P99让心跳经过业务链路或加健康检查服务端 CPU 突增心跳频率过高或集中触发看心跳 QPS 与时间分布加抖动、拉长 T、用时间轮心跳包被中间设备丢弃帧大小超过 MTU 或设备策略tcpdump 看是否单向可见缩小包体、改用独立端口移动端耗电异常后台频繁唤醒统计后台心跳次数结合前后台状态降频7.2 排查顺序从外到内从粗到细遇到“心跳莫名超时”这类问题我会按这个顺序走一遍。先看是不是真的断了。在两端同时抓包看 PING 有没有发出去、有没有到、PONG 有没有回来。这一步能快速把问题切成三类没发出客户端问题、发出了没到网络问题、到了没回服务端问题。再看时间戳和时钟。客户端和服务端的时钟不同步是排查时的经典干扰项。如果日志里用系统时间打点两端差了十几秒看起来就像心跳延迟巨大。所以日志里统计耗时时一定要用单调时钟跨机对比时再换回系统时间并注明时区。然后看事件循环的阻塞情况。asyncio 或 Netty 这类框架一旦某个处理函数里有同步阻塞调用读文件、同步数据库查询、长时间的 CPU 计算整个事件循环都会被卡住心跳处理自然延迟。Python 里可以开loop.set_debug(True)或加一个每秒执行的“延迟探测器”协程来度量事件循环延迟async def lag_monitor(): while True: t0 time.monotonic() await asyncio.sleep(0.1) lag time.monotonic() - t0 - 0.1 if lag 0.05: print(fevent loop lag: {lag*1000:.1f}ms)这个探测器我几乎在每个 asyncio 服务里都会加它对定位“莫名超时”极其有效——很多所谓的网络问题实际上是自己代码里藏了一个同步 IO。7.3 几个我实际踩过的坑坑一把心跳当成保活忘了业务侧的写超时。心跳正常只说明连接可写可读但服务端往客户端推消息时如果对端读取慢写缓冲区会堆积。这时候心跳可能还正常因为客户端还在回 PONG但业务消息已经在内存里堆了几十兆。解决办法是给每个连接设一个写缓冲上限超限就判定为“慢消费者”并主动断开不能只靠心跳。坑二重连时没有复用会话状态。有一次做设备接入客户端重连后服务端新建了一个会话但旧会话因为扫描周期没到还没被清理结果同一个设备对应两个会话下行指令发到了旧的那个。修复方式是引入设备唯一标识作为会话键新连接进来时先踢掉同标识的旧会话。坑三心跳日志打得太密。十万连接、20 秒一次心跳QPS 是 5000如果每次都打一行 INFO 日志磁盘 IO 直接成为瓶颈反过来拖慢心跳处理形成恶性循环。心跳日志一定要采样或者聚合统计只在异常时打详细日志。坑四以为心跳间隔越小检测越快。检测速度的上限是“超时阈值 D”D N×T。你把 T 从 20 秒降到 5 秒D 从 60 秒降到 15 秒代价是流量和耗电翻了 4 倍。但如果业务本来就能容忍 60 秒这 4 倍代价纯属浪费。参数永远是从业务需求倒推不是越小越好。坑五忽略服务端主动关闭时的礼貌。服务端因为超时准备断开一个会话时如果直接close()而不发任何通知客户端要等到自己下一次心跳超时才知道白白多等一个周期。更好的做法是先发一个BYE控制帧再关闭连接客户端收到后立即重连整体恢复时间能缩短一半以上。8. 最后说点个人体会心跳机制最容易被低估的地方是它看起来太简单了——不就是定时发个包吗。但真正跑在几十万连接、复杂网络环境、长时间运行的线上系统里它牵扯的东西非常多时钟源的选择、定时器的数据结构、锁的粒度、中间设备的行为、移动端系统的节流策略、重连的退避设计每一项单独拿出来都够写一篇。我个人的建议是先把“检测延迟 D”这个业务指标明确下来用它倒推所有参数然后再一个一个去验证别一上来就抄别人的 30 秒。另外如果你正在做的东西连接数还没到万级别过度设计。固定间隔 扫描式超时判定 指数退避重连这三样组合起来不到两百行代码能扛住绝大多数场景。等真的到了十万级、百万级再考虑时间轮、分片锁、自适应调优这些手段。技术选型最怕的不是不够先进是过早引入复杂度然后在排查问题时被自己的架构绕晕。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑