默认
打赏 发表评论 5
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)
阅读(182713) | 评论(5 收藏9 淘帖3 2
微信扫一扫关注!

1、引言


在本文的上篇《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》中介绍了微信的消息序列号生成器 seqsvr 的算法原理、架构核心思想,以及 seqsvr 随着业务量快速上涨所做的架构演变,本篇将会介绍 seqsvr 分布式容灾架构的演变

我们知道,互联网后台系统绝大部分情况下并没有一种唯一的、完美的解决方案,同样的需求在不同的环境背景下甚至有可能演化出两种截然不同的架构。既然架构是多变的,那纯粹讲架构的意义并不是特别大,这里系统地分享微信的消息序列号生成器 seqsvr 容灾设计时的一些思考和权衡,希望对大家有所帮助。

推荐:融云团队分享的消息ID生成文章也值得一读《融云技术分享:解密融云IM产品的聊天消息ID生成策略》,美团的这篇也不错《美团技术分享:深度解密美团的分布式ID生成算法》。

本文是“IM消息ID技术专题”系列文章的第2篇,专题总目录如下:


2、本文分篇


上篇:微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
下篇:微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》(本文

3、关于作者


曾钦松:微信高级工程师,负责过微信基础架构、微信翻译引擎、微信围棋PhoenixGo,致力于高可用高性能后台系统的设计与研发。2011年毕业于西安电子科技大学,早先曾在腾讯搜搜从事检索架构、分布式数据库方面的工作。

4、容灾方案的架构原理


接下来我们会介绍 seqsvr 的容灾架构。我们知道,后台系统绝大部分情况下并没有一种唯一的、完美的解决方案,同样的需求在不同的环境背景下甚至有可能演化出两种截然不同的架构。既然架构是多变的,那纯粹讲架构的意义并不是特别大,期间也会讲下 seqsvr 容灾设计时的一些思考和权衡,希望对大家有所帮助。

seqsvr 的容灾模型在五年中进行过一次比较大的重构,提升了可用性、机器利用率等方面。

其中不管是重构前还是重构后的架构,seqsvr 一直遵循着两条架构设计原则:

  • 1)保持自身架构简单;
  • 2)避免对外部模块的强依赖。

这两点都是基于 seqsvr 可靠性考虑的,毕竟 seqsvr 是一个与整个微信服务端正常运行息息相关的模块。按照我们对这个世界的认识,系统的复杂度往往是跟可靠性成反比的,想得到一个可靠的系统一个关键点就是要把它做简单。相信大家身边都有一些这样的例子,设计方案里有很多高大上、复杂的东西,同时也总能看到他们在默默地填一些高大上的坑。当然简单的系统不意味着粗制滥造,我们要做的是理出最核心的点,然后在满足这些核心点的基础上,针对性地提出一个足够简单的解决方案。

那么,seqsvr 最核心的点是什么呢?每个 uid 的 sequence 申请要递增不回退。这里我们发现,如果 seqsvr 满足这么一个约束:任意时刻任意 uid 有且仅有一台 AllocSvr 提供服务,就可以比较容易地实现 sequence 递增不回退的要求。

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)_1.jpg
▲ 图1:两台 AllocSvr 服务同个 uid 造成 sequence 回退。Client 读取到的 sequence 序列为101、201、102

但也由于这个约束,多台AllocSvr同时服务同一个号段的多主机模型在这里就不适用了。我们只能采用单点服务的模式,当某台 AllocSvr 发生服务不可用时,将该机服务的 uid 段切换到其它机器来实现容灾。这里需要引入一个仲裁服务,探测 AllocSvr 的服务状态,决定每个 uid 段由哪台 AllocSvr 加载。出于可靠性的考虑,仲裁模块并不直接操作 AllocSvr ,而是将加载配置写到 StoreSvr 持久化,然后 AllocSvr 定期访问 StoreSvr 读取最新的加载配置,决定自己的加载状态。

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)_2.jpg
▲ 图2:号段迁移示意。通过更新加载配置把0~2号段从 AllocSvrA 迁移到 AllocSvrB

同时,为了避免失联AllocSvr提供错误的服务,返回脏数据,AllocSvr需要跟StoreSvr保持租约。

这个租约机制由以下两个条件组成:

  • 1)租约失效:AllocSvr N秒内无法从StoreSvr读取加载配置时,AllocSvr停止服务;
  • 2)租约生效:AllocSvr读取到新的加载配置后,立即卸载需要卸载的号段,需要加载的新号段等待N秒后提供服务。

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)_3.jpg
▲ 图3:租约机制。AllocSvrB严格保证在AllocSvrA停止服务后提供服务

这两个条件保证了切换时,新 AllocSvr 肯定在旧 AllocSvr 下线后才开始提供服务。但这种租约机制也会造成切换的号段存在小段时间的不可服务,不过由于微信后台逻辑层存在重试机制及异步重试队列,小段时间的不可服务是用户无感知的,而且出现租约失效、切换是小概率事件,整体上是可以接受的。

到此讲了 AllocSvr 容灾切换的基本原理,接下来会介绍整个 seqsvr 架构容灾架构的演变。

5、容灾1.0架构:主备容灾


初版本的 seqsvr 采用了主机+冷备机容灾模式:全量的 uid 空间均匀分成N个 Section,连续的若干个 Section 组成了一个 Set,每个 Set 都有一主一备两台 AllocSvr 。正常情况下只有主机提供服务;在主机出故障时,仲裁服务切换主备,原来的主机下线变成备机,原备机变成主机后加载 uid 号段提供服务。

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)_1.jpg
▲ 图4:容灾1.0架构:主备容灾

可能看到前文的叙述,有些同学已经想到这种容灾架构。一主机一备机的模型设计简单,并且具有不错的可用性——毕竟主备两台机器同时不可用的概率极低,相信很多后台系统也采用了类似的容灾策略。

5.1设计权衡


主备容灾存在一些明显的缺陷,比如备机闲置导致有一半的空闲机器;比如主备切换的时候,备机在瞬间要接受主机所有的请求,容易导致备机过载。既然一主一备容灾存在这样的问题,为什么一开始还要采用这种容灾模型?事实上,架构的选择往往跟当时的背景有关,seqsvr 诞生于微信发展初期,也正是微信快速扩张的时候。

选择一主一备容灾模型是出于以下的考虑:

  • 1)架构简单,可以快速开发;
  • 2)机器数少,机器冗余不是主要问题;
  • 3)Client 端更新 AllocSvr 的路由状态很容易实现。

前两点好懂,人力、机器都不如时间宝贵。而第三点比较有意思,下面展开讲下:

微信后台绝大部分模块使用了一个自研的RPC框架,seqsvr也不例外。在这个RPC框架里,调用端读取本地机器的client配置文件,决定去哪台服务端调用。这种模型对于无状态的服务端,是很好用的,也很方便实现容灾。我们可以在client配置文件里面写“对于号段x,可以去SvrA、SvrB、SvrC三台机器的任意一台访问”,实现三主机容灾。

但在seqsvr里,AllocSvr是预分配中间层,并不是无状态的。而前面我们提到,AllocSvr加载哪些uid号段,是由保存在StoreSvr的加载配置决定的。那么这时候就尴尬了,业务想要申请某个uid的sequence,Client端其实并不清楚具体去哪台AllocSvr访问,client配置文件只会跟它说“AllocSvrA、AllocSvrB…这堆机器的某一台会有你想要的sequence”。换句话讲,原来负责提供服务的AllocSvrA故障,仲裁服务决定由AllocSvrC来替代AllocSvrA提供服务,Client要如何获知这个路由信息的变更?

这时候假如我们的AllocSvr采用了主备容灾模型的话,事情就变得简单多了。我们可以在client配置文件里写:对于某个uid号段,要么是AllocSvrA加载,要么是AllocSvrB加载。Client端发起请求时,尽管Client端并不清楚AllocSvrA和AllocSvrB哪一台真正加载了目标uid号段,但是Client端可以先尝试给其中任意一台AllocSvr发请求,就算这次请求了错误的AllocSvr,那么就知道另外一台是正确的AllocSvr,再发起一次请求即可。

也就是说,对于主备容灾模型,最多也只会浪费一次的试探请求来确定AllocSvr的服务状态,额外消耗少,编码也简单。可是,如果Svr端采用了其它复杂的容灾策略,那么基于静态配置的框架就很难去确定Svr端的服务状态:Svr发生状态变更,Client端无法确定应该向哪台Svr发起请求。这也是为什么一开始选择了主备容灾的原因之一。

5.2主备容灾的缺陷


在我们的实际运营中,容灾1.0架构存在两个重大的不足:

  • 1)扩容、缩容非常麻烦;
  • 2)一个 Set 的主备机都过载,无法使用其他 Set 的机器进行容灾。

在主备容灾中,Client 和 AllocSvr 需要使用完全一致的配置文件。变更这个配置文件的时候,由于无法实现在同一时间更新给所有的 Client 和 AllocSvr ,因此需要非常复杂的人工操作来保证变更的正确性(包括需要使用iptables来做请求转发,具体的详情这里不做展开)。

对于第二个问题,常见的方法是用一致性 Hash 算法替代主备,一个 Set 有多台机器,过载机器的请求被分摊到多台机器,容灾效果会更好。在 seqsvr 中使用类似一致性 Hash 的容灾策略也是可行的,只要 Client 端与仲裁服务都使用完全一样的一致性 Hash 算法,这样 Client 端可以启发式地去尝试,直到找到正确的 AllocSvr。

例如对于某个 uid,仲裁服务会优先把它分配到 AllocSvrA ,如果 AllocSvrA 挂掉则分配到 AllocSvrB ,再不行分配到 AllocSvrC。那么 Client 在访问 AllocSvr 时,按照 AllocSvrA -> AllocSvrB -> AllocSvrC 的顺序去访问,也能实现容灾的目的。但这种方法仍然没有克服前面主备容灾面临的配置文件变更的问题,运营起来也很麻烦。

6、容灾2.0架构:嵌入式路由表容灾


6.1基本原理


最后我们另辟蹊径,采用了一种不同的思路:既然 Client 端与 AllocSvr 存在路由状态不一致的问题,那么让 AllocSvr 把当前的路由状态传递给 Client 端,打破之前只能根据本地 Client 配置文件做路由决策的限制,从根本上解决这个问题。

所以在2.0架构中,我们把 AllocSvr 的路由状态嵌入到 Client 请求 sequence 的响应包中,在不带来额外的资源消耗的情况下,实现了 Client 端与 AllocSvr 之间的路由状态一致。具体实现方案如下:

seqsvr 所有模块使用了统一的路由表,描述了 uid 号段到 AllocSvr 的全映射。这份路由表由仲裁服务根据 AllocSvr 的服务状态生成,写到 StoreSvr 中,由 AllocSvr 当作租约读出,最后在业务返回包里旁路给 Client 端。

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)_2.jpg
▲ 图5:容灾2.0架构:动态号段迁移容灾

把路由表嵌入到请求响应包看似很简单的架构变动,却是整个 seqsvr 容灾架构的技术奇点。利用它解决了路由状态不一致的问题后,可以实现一些以前不容易实现的特性。例如灵活的容灾策略,让所有机器都互为备机,在机器故障时,把故障机上的号段均匀地迁移到其它可用的 AllocSvr 上;还可以根据 AllocSvr 的负载情况,进行负载均衡,有效缓解 AllocSvr 请求不均的问题,大幅提升机器使用率。

另外在运营上也得到了大幅简化。之前对机器进行运维操作有着繁杂的操作步骤,而新架构只需要更新路由即可轻松实现上线、下线、替换机器,不需要关心配置文件不一致的问题,避免了一些由于人工误操作引发的故障。

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)_3.jpg
▲ 图6:机器故障号段迁移

6.2路由同步优化


把路由表嵌入到取 sequence 的请求响应包中,那么会引入一个类似“先有鸡还是先有蛋”的哲学命题:没有路由表,怎么知道去哪台 AllocSvr 取路由表?另外,取 sequence 是一个超高频的请求,如何避免嵌入路由表带来的带宽消耗?

这里通过在 Client 端内存缓存路由表以及路由版本号来解决,请求步骤如下:

  • 1)Client 根据本地共享内存缓存的路由表,选择对应的AllocSvr;如果路由表不存在,随机选择一台AllocSvr;
  • 2)对选中的 AllocSvr 发起请求,请求带上本地路由表的版本号;
  • 3)AllocSvr 收到请求,除了处理 sequence 逻辑外,判断 Client 带上版本号是否最新,如果是旧版则在响应包中附上最新的路由表;
  • 4)Client收到响应包,除了处理 sequence 逻辑外,判断响应包是否带有新路由表。如果有,更新本地路由表,并决策是否返回第1步重试。

基于以上的请求步骤,在本地路由表失效的时候,使用少量的重试便可以拉到正确的路由,正常提供服务。

7、全文小结


经过上下两篇的讲解,到此把 seqsvr 的技术原理、架构设计和实际架构演变基本讲完了,正是如此简单优雅的模型,为微信的其它模块提供了一种简单可靠的一致性解决方案,支撑着微信五年来的高速发展,相信在可预见的未来仍然会发挥着重要的作用。

附录:更多QQ、微信团队原创技术文章


微信朋友圈千亿访问量背后的技术挑战和实践总结
腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)
腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)
微信团队分享:微信移动端的全文检索多音字问题解决方案
腾讯技术分享:Android版手机QQ的缓存监控与优化实践
微信团队分享:iOS版微信的高性能通用key-value组件技术实践
微信团队分享:iOS版微信是如何防止特殊字符导致的炸群、APP崩溃的?
腾讯技术分享:Android手Q的线程死锁监控系统技术实践
微信团队原创分享:iOS版微信的内存监控系统技术实践
让互联网更快:新一代QUIC协议在腾讯的技术实践分享
iOS后台唤醒实战:微信收款到账语音提醒技术总结
腾讯技术分享:社交网络图片的带宽压缩技术演进之路
微信团队分享:视频图像的超分辨率技术原理和应用场景
微信团队分享:微信每日亿次实时音视频聊天背后的技术解密
QQ音乐团队分享:Android中的图片压缩技术详解(上篇)
QQ音乐团队分享:Android中的图片压缩技术详解(下篇)
腾讯团队分享:手机QQ中的人脸识别酷炫动画效果实现详解
腾讯团队分享 :一次手Q聊天界面中图片显示bug的追踪过程分享
微信团队分享:微信Android版小视频编码填过的那些坑
微信手机端的本地数据全文检索优化之路
企业微信客户端中组织架构数据的同步更新方案优化实战
微信团队披露:微信界面卡死超级bug“15。。。。”的来龙去脉
QQ 18年:解密8亿月活的QQ后台服务接口隔离技术
月活8.89亿的超级IM微信是如何进行Android端兼容测试的
以手机QQ为例探讨移动端IM中的“轻应用”
一篇文章get微信开源移动端数据库组件WCDB的一切!
微信客户端团队负责人技术访谈:如何着手客户端性能监控和优化
微信后台基于时间序的海量数据冷热分级架构设计实践
微信团队原创分享:Android版微信的臃肿之困与模块化实践之路
微信后台团队:微信后台异步消息队列的优化升级实践分享
微信团队原创分享:微信客户端SQLite数据库损坏修复实践
腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率
腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(下篇)
腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(上篇)
微信Mars:微信内部正在使用的网络层封装库,即将开源
如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源
开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载]
微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解
微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
Android版微信从300KB到30MB的技术演进(PPT讲稿) [附件下载]
微信团队原创分享:Android版微信从300KB到30MB的技术演进
微信技术总监谈架构:微信之道——大道至简(演讲全文)
微信技术总监谈架构:微信之道——大道至简(PPT讲稿) [附件下载]
如何解读《微信技术总监谈架构:微信之道——大道至简》
微信海量用户背后的后台系统存储架构(视频+PPT) [附件下载]
微信异步化改造实践:8亿月活、单机千万连接背后的后台解决方案
微信朋友圈海量技术之道PPT [附件下载]
微信对网络影响的技术试验及分析(论文全文)
一份微信后台技术架构的总结性笔记
架构之道:3个程序员成就微信朋友圈日均10亿发布量[有视频]
快速裂变:见证微信强大后台架构从0到1的演进历程(一)
快速裂变:见证微信强大后台架构从0到1的演进历程(二)
微信团队原创分享:Android内存泄漏监控和优化技巧总结
全面总结iOS版微信升级iOS9遇到的各种“坑”
微信团队原创资源混淆工具:让你的APK立减1M
微信团队原创Android资源混淆工具:AndResGuard [有源码]
Android版微信安装包“减肥”实战记录
iOS版微信安装包“减肥”实战记录
移动端IM实践:iOS版微信界面卡顿监测方案
微信“红包照片”背后的技术难题
移动端IM实践:iOS版微信小视频功能技术方案实录
移动端IM实践:Android版微信如何大幅提升交互性能(一)
移动端IM实践:Android版微信如何大幅提升交互性能(二)
移动端IM实践:实现Android版微信的智能心跳机制
移动端IM实践:WhatsApp、Line、微信的心跳策略分析
移动端IM实践:谷歌消息推送服务(GCM)研究(来自微信)
移动端IM实践:iOS版微信的多设备字体适配方案探讨
信鸽团队原创:一起走过 iOS10 上消息推送(APNS)的坑
腾讯信鸽技术分享:百亿级实时消息推送的实战经验
IPv6技术详解:基本概念、应用现状、技术实践(上篇)
IPv6技术详解:基本概念、应用现状、技术实践(下篇)
腾讯TEG团队原创:基于MySQL的分布式数据库TDSQL十年锻造经验分享
微信多媒体团队访谈:音视频开发的学习、微信的音视频技术和挑战等
了解iOS消息推送一文就够:史上最全iOS Push技术详解
腾讯技术分享:微信小程序音视频技术背后的故事
腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面
微信多媒体团队梁俊斌访谈:聊一聊我所了解的音视频技术
腾讯音视频实验室:使用AI黑科技实现超低码率的高清实时视频聊天
腾讯技术分享:微信小程序音视频与WebRTC互通的技术思路和实践
手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)
微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)
微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)
>> 更多同类文章 ……

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)下一篇:一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

本帖已收录至以下技术专辑

推荐方案
评论 5
还有没有更多关于即时通讯容灾方案的介绍?
引用:小张 发表于 2021-10-27 16:35
还有没有更多关于即时通讯容灾方案的介绍?

这方面的经验一般只有大厂有,大厂在这方面的分享是比较少的
引用:JackJiang 发表于 2021-10-28 10:52
这方面的经验一般只有大厂有,大厂在这方面的分享是比较少的

额嗯
怎么有种redis cluster的味道。 client到alloc这块用一致性hash做LB也OK吧
感觉 节点故障,仲裁服务也没把立马进行迁移段号。感觉像哨兵那样要一小段时间。感觉这就是仿redis cluster模式。。。
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部