Skip to content

注册中心、负载均衡与一致性哈希 ​

Category ​

database / cache 子树。节点定位:注册发现、负载均衡、一致性哈希在游戏长连接场景的变形——「分配结果的生命周期」是这一层的判断核心。

Definition ​

服务数量一旦超过“一个域名后面挂几个实例”的规模,“谁在哪里、请求发给谁”就变成了独立的问题。注册中心回答前者,负载均衡回答后者,一致性哈希则在节点增减时守护分配结果的稳定。这三件事单看都是成熟组件,真正值得讨论的是它们在游戏长连接场景下的变形——很多来自 Web 服务的直觉在这里会失效。

Problem ​

服务数量超过「一个域名挂几个实例」后,「谁在哪里、请求发给谁」成为独立问题,而 Web 服务的直觉在这里会失效:负载均衡不是持续调节,只在建连那一刻分配;故障不是瞬间被发现,心跳窗口以十秒计;节点增减的代价不是全量缓存失效就是全量重连。混用任何一种时间尺度的直觉,都会在扩缩容那天付出代价。

注册中心解决的是“地址会变” ​

单机时代进程地址几乎不变,配置文件写死即可。服务化之后,实例随时扩容、缩容、重启、漂移,任何写死的地址都会在某个发布日过期。注册中心把“有哪些实例活着”变成一份动态事实:服务启动时上报,退出时注销,心跳断掉后被摘除,消费方每次拿到的是当下可用的实例列表。

它的定位通常有两条边界:

  • 它维护的是“谁活着、在哪里”这类路由事实,而不是业务配置的取值。
  • 它是路由信息的缓存,源头是各服务实例自身;注册中心短暂不可用时,现有调用往往还能靠本地缓存撑一段时间。

把业务参数、活动开关、灰度规则塞进注册中心是常见的越界用法。短期看省了一个配置系统,长期看变更频率完全不同的两类数据搅在一起,实例摘除和参数变更互相干扰,排障时连“刚才改了什么”都难以回答。

心跳与摘除:故障不是瞬间被发现的 ​

注册中心给了消费方一份可用列表,但这份列表有延迟。实例崩溃不会主动打招呼,要等心跳超时才被摘除;摘除之后,消费方的本地缓存还要等下一个刷新周期才更新。所以从实例死亡到流量完全离开它,中间往往隔着一段以十秒计的窗口。

这段窗口决定了几个设计取向:

  • 调用方要自己处理“打到已死实例”的失败,重试不能全指望注册中心。
  • 心跳间隔和摘除阈值是一对权衡:调快了网络抖动就误杀,调慢了故障窗口拉长。
  • 优雅下线比崩溃摘除重要得多,主动注销能把窗口从十秒压到接近零。

游戏场景里这段窗口更刺眼,因为长连接的断开玩家会立刻感知。发布前先从列表摘除、等连接自然排空,通常比依赖心跳超时体面得多。

长连接让负载均衡失去实时性 ​

Web 请求的负载均衡是每请求决策:这一笔给哪个实例、下一笔给哪个,随时可以重排。游戏长连接不是——玩家建连的那一刻,分配就已经固化,之后几个小时的所有流量都沿着这条连接走。均衡不再是持续调节的过程,而是一次性的分配结果。

这带来几个 Web 场景没有的现象:

  • 均衡只反映建连时刻的状态,之后实例间负载会自然漂移,晚高峰的分布和建连时完全不同。
  • 一批玩家集中涌入时,如果分配只看“当前连接数”,刚上线的实例会瞬间被灌满。
  • 想摘掉一个实例,不是从列表里删掉就行,要等挂在它上面的玩家自然离开或被主动迁移。

所以游戏基础设施的负载评估通常看“建连分布加逐出速度”这条曲线,而不是某个瞬间的连接数快照。开服洪峰下新实例的预热节奏,也属于同一个问题。

会话粘性为什么在游戏里几乎是默认项 ​

会话粘性指同一个玩家会话的请求尽量落在同一个实例上。有状态的游戏服里这不是优化,而是前提:玩家会话、房间状态、视野数据都在进程内存里,换一个实例等于丢状态。即使服务本身无状态,长连接场景下粘性也往往自然成立——连接在哪,请求就在哪。

粘性的代价也要认账:

  • 分配不均时出现热点实例,其他实例闲置。
  • 实例故障时,粘在上面的玩家要整体迁移或重连,影响是成块的而不是零散的。
  • 发布只能按实例粒度滚动,每个实例都要等玩家排空。

常见做法是让“粘”的范围尽量小:网关层保持无状态,粘性只落在具体的逻辑服或房间服上。这样故障和发布的迁移单位是一个房间或一个区,而不是整台网关上的全部连接。

一致性哈希到底解决什么问题 ​

普通哈希把 key 分到节点上,代价是节点数一变,几乎所有 key 的归属都会重排。对缓存来说,一次扩容等于一次全量缓存失效;对长连接网关来说,一次扩容等于一次全量重连。一致性哈希把“节点变化”的影响限制在相邻区段:增减一个节点,只有环上它和邻居之间的那部分 key 会移动,其余不动。

它适合的场景有几个共同特征:

  • key 的数量大且无法接受整体重排,比如会话、房间、分片数据。
  • 节点经常增减,弹性扩缩容是常态而不是一年一次的事故。
  • 分配结果需要长期稳定,同样的 key 反复计算也要落回同一个节点。

反过来,如果节点集合长期固定、key 也无状态,普通哈希或直接轮询更简单。一致性哈希属于额外的复杂度,值不值得引入,取决于“重排会砸到什么”。

虚拟节点与数据倾斜 ​

一致性哈希的原始形态有一个工程问题:节点少的时候,环上的区段划分接近随机,个别节点可能分到明显偏大的区段。虚拟节点是常见解法——每个物理节点在环上展开成几十上百个虚拟位置,区段分布趋于均匀;节点宕机时,它承载的 key 也分散给多个邻居,而不是全部压给单一节点。

使用时通常还要注意两点:

  • 哈希函数的质量直接决定分布,业务自造的简单哈希往往偏斜明显。
  • 节点规格不同时,虚拟节点数量可以按权重配比,让新硬件多分担一些。

再往后还有热点 key 的问题:一致性哈希只均衡 key 的数量,不均衡 key 的访问量。极热门的几个房间或几个大 R 玩家,可能在环上挤在同一个节点,这时要么在 key 层加随机后缀打散,要么接受热点并单独处理。

常见误区 ​

  • 把注册中心当配置中心用,实例摘除和参数变更互相干扰。
  • 以为负载均衡在持续调节流量,长连接场景下分配只在建连那一刻发生。
  • 扩容前没有评估重连代价,一次弹性伸缩变成一次全量掉线。
  • 心跳调得过快,网络抖动被当成节点故障,摘除风暴反过来打垮调用方。
  • 只看 key 数量的均匀,忽视热门 key 把流量重新集中到单个节点。

这一层基础设施的判断核心是“分配结果的生命周期”:请求级的均衡、连接级的粘性、数据级的哈希环,各自对应不同的时间尺度,混用任何一种直觉都会在扩缩容那天付出代价。

Used By ​

服务化后的注册发现选型与边界(路由事实 vs 业务配置);长连接网关的负载评估(建连分布 + 逐出速度曲线);会话粘性范围控制;一致性哈希与虚拟节点的引入判断。

关系链:注册发现(谁活着)→ 心跳摘除窗口 → 负载分配(建连时刻固化)→ 会话粘性 → 一致性哈希(数据级稳定)。上游:server 树的服务拆分(server/services)。下游:AOI(server 树,同类「重排代价」问题——可见集增减同样要控制抖动)。

Reference ​

一致性哈希原始论文:Karger et al.,《Consistent Hashing and Random Trees》(STOC 1997);本页其余为工程经验归纳。

游戏知识体系