孔令圳,斗鱼首席架构师,全面负责斗鱼全站技术架构体系规划和建设,10 余年中大型互联网产品架构经验,擅长高并发、高可用场景下的架构与方案设计。
于竞,斗鱼技术保障运维专家,负责斗鱼高可用基础架构建设,擅长注册中心、监控体系等技术领域,同时也是斗鱼多活基础保障负责人。
唐聪,腾讯云资深工程师,极客时间专栏《etcd 实战课》作者,etcd 活跃贡献者,主要负责腾讯云大规模 k8s/etcd 平台、有状态服务容器化、在离线混部等产品研发设计工作。
陈鹏,腾讯云容器服务产品架构师,多年专注云原生领域,帮助了大量用户云原生容器化改造和生产落地,拥有丰富的一线实践经验,也发表了海量的云原生技术文章。
1 业务背景和痛点
斗鱼直播作为业界领先的游戏直播平台,每天为数以亿计的互联网用户提供优质的游戏直播观看、互动和娱乐等服务。
随着近年直播市场的火热,斗鱼直播平台作为业内口碑和体验俱佳的互联网公司,用户量也出现井喷式增长。海量用户给平台带来的稳定性技术挑战也越发强烈,斗鱼的老架构如下图所示,无论是业务支撑还是架构设计,均存在一定的风险和隐患。
斗鱼老架构
图一 斗鱼老架构
为了给用户带来更好的可用性体验,斗鱼急需解决单一数据中心的问题,将老架构从单数据中心升级到多数据中心。
多数据中心挑战
在实现单活升级为多活的过程中,为了确保无故障的迁移升级,我们面临一系列挑战,比如:
因单活升级到多活的过程中,涉及系统众多,本文将是斗鱼直播多活改造系列的第一篇,只聚焦于注册中心模块,因此我们先和你介绍下注册中心背后的 etcd 和 zookeeper。
zk/etcd 承担的角色
dubbo 通过注册中心来解决大规模集群下的服务注册与发现问题,以下是注册中心架构图:
dubbo 默认支持 zookeeper 注册中心,虽然新版也有 etcd 实现,但该实现尚缺乏大规模投产的先例,Java 技术栈采用 etcd 作为注册中心的案例也比较罕见。
当采用 zookeeper 作为 dubbo 注册中心时,其注册关系为树形结构,详细结构如下图所示:
因为 zookeeper 是基于类似文件系统的树形结构来存储数据,但 etcd 却是采用键值对存储,二者之间的差异会给注册关系同步带来较大困难。
此外,如果从 zookeeper 迁移到 etcd,则在整个迁移过程中:已有的线上服务不能受损,更不能停服;如果迁移失败,还要能回退到到 zookeeper。
同城双活与多活新架构
为了实现多活,我们通过跨数据中心的同步服务、服务依赖梳理与边界划分、可控变更等技术手段和运维理念,成功解决了以上挑战,设计了如下一套新的架构来实现多活,如下图所示:
图二 斗鱼多活新架构
在新的架构下,可以按域名甚至是 URL 来细粒度的调度流量,RPC 层面也具备了自动就近调用的能力,其中注册中心的局部架构图如下:
图三 斗鱼注册中心老架构
注册中心多活方案选型与目标
在注册中心多活改造过程中,我们面临多个方案,如下表所示:
由于历史原因,我们有 zookeeper(以下简称 zk)和 etcd 这 2 套注册中心,加上我们有 Java、Go、C++、PHP 这 4 个技术栈,因此在注册中心领域仍然一些不足,希望能统一到 etcd 来解决痛点问题,并达到以下目标:
降低维护成本:此前需要运维 zk+etcd 两套注册中心,更困难的是做多活解决方案时也需要适配 zk+etcd,这导致注册中心多活研发成本翻倍。由于 etcd 是 k8s 的一部分,运维 etcd 又不可避免,这是选择 etcd 的第 1 个原因。
拥抱更繁荣的生态:etcd 有云原生托管解决方案,有厂商通过 etcd 管理 10K node 级别的 k8s 集群,etcd 还自带 proxy、cache、mirror 等各种周边工具,java 侧 dubbo 也支持以 etcd 作为注册中心,etcd 相对于 zk 来说发展前景更好,这是选择 etcd 的第 2 个原因。
增强跨语言能力:etcd 可基于 http 或 grpc 协议通讯,并且支持长轮询,具有较强的跨语言能力。而 zk 需要引入专用客户端,除 java 客户端之外,其它语言客户端尚不成熟。而我们有 JAVA、Go、C++、PHP 等 4 种研发语言,这是选择 etcd 的第 3 个原因。
基于以上原因,我们选择了方案四,方案四大新架构如下图所示:
图四 斗鱼注册中心新架构
注册中心多活难点与挑战
为了实现新注册中心,达到我们期望的设计目标,注册中心在改造过程中,面临以下难点与挑战:
2 注册中心多活难点分析
迁移过程中如何保证新旧服务互通?
开发 zk2etcd
我们很多 java 开发的业务使用 dubbo 框架做服务治理,注册中心是 zookeeper,我们希望 java 和 go 开发的业务全部都统一使用 etcd 作为注册中心,也为跨语言调用的可能性做好铺垫。
由于业务众多,改造和迁移的周期会很长,预计持续 1~2 年,在此过程中我们需要将 zookeeper 中的注册数据同步到 etcd 中,实时同步,而且要保证数据一致性以及高可用,当前市面上没有找到满足我们需求的工具,于是我们和腾讯云 TKE 团队合作开发了一个 zk2etcd 来同步实现 zookeeper 数据到 etcd,并且已将其开源,整体方案落地篇我们将详细介绍。
如何实现 etcd 异地容灾?
通过 zk2etcd 同步服务,我们成功解决了 zookeeper 数据迁移问题,使得新老业务的注册中心数据都使用 etcd 来存储。
因此,etcd 的重要性不言而喻,它的可用性决定着我们的整体可用性,而斗鱼直播目前的部署架构又严重依赖某核心机房,一旦核心机房出现故障,将导致整体不可用。因此斗鱼直播下一个痛点就是提升 etcd 的可用性,期望实现 etcd 跨城容灾、异地容灾能力。
斗鱼直播理想中的 etcd 跨城同步服务应该具备如下特性:
那么有哪些方案呢?各个方案又有哪些优缺点呢?最终评估了如下几种方案:
单集群多地部署方案
单集群多地部署方案图如下:
在此方案中,etcd Leader 节点通过 Raft 协议将数据复制到各个地域的 Follower 节点。
此方案它的优点如下:
介绍完它的优点后,我们再看看它的缺点,如下所示:
etcd 社区 make-mirror 方案
介绍完单集群多地部署方案后,我们再看看 etcd 社区提供的 make-mirror 方案,它的原理图如下:
在此方案中,我们分别在不同城市部署了一套独立的 etcd 集群,通过 etcd 社区提供的 make-mirror 工具实现跨城数据复制。
make-mirror 工具原理如下:
此方案它的优点如下:
介绍完它的优点后,我们再看看它的缺点,如下所示:
etcd 社区 learner 方案
介绍完 etcd 社区的 make-mirror 方案后,我们再看看 etcd 社区提供的 learner 方案,它的原理图如下:
它的核心原理如下:
此方案它的优点如下:
介绍完它的优点后,我们再看看它的缺点,如下所示:
介绍完已有的几种方案后,我们发现它们都无法满足业务生产环境诉求,于是我们自研完成了生产环境可用的 etcd 同步服务落地,在整体方案落地章节将详细介绍。
如何确保 etcd 和 zk 同步服务的稳定性、可运维性?
为了确保 etcd、zk 同步服务的稳定性,模拟 5 类常见的故障,检验服务在这些典型故障场景下的自愈能力,详细测试方案如下。
故障场景
上述 5 种场景的实际触发原因有多种多样,只需要模拟出一种情况。
演练方案
另外针对可运维性问题,无论是 etcd 还是 zk 同步服务,都提供了详细的 metrics、日志,我们针对各个核心场景、异常场景都配置了可视化的观测视图,并配置了告警策略。
3整体方案落地
整体架构
说明
黑实线:正常情况下的专线访问
黑虚线:切公网方式访问
红实线:etcd 集群发生主备切换后的专线访问
红虚线:etcd 集群发生主备切换后的公网访问
zk同步服务工程化实践
zookeeper 与 etcd 存储结构不一致,加大了同步的实现难度。zookeeper 存储是树状结构,而 etcd v3 是扁平结构。zookeeper 无法像 etcd 一样按照 prefix 来 list 所有 key;etcd 无法像 zookeeper 一样通过 list chilren 来查询某个目录下的子节点,也加大了实现同步的难度。
如何感知 zookeeper 中的数据变化?zookeeper 的 watch 不像 etcd 一样可以简单的感知到任意 key 的新增,需要递归的 watch 所有的节点,收到 ChildrenChanged 事件后拿到该事件对应节点下的所有子节点,再与 etcd 中的数据进行比对,就可以得到新增的数据,并将其同步 put 到 etcd 中。类似的,可以用递归的方法 watch 所有节点的删除事件,并同步删除 etcd 中的数据。
另外 zookeeper 的 watch 有着先天性的缺陷,watch 是一次性的,所以每次收到事件后又必须重新 watch,两次 watch 之间理论上是可能丢事件的,主要是在同一个 key 连续多次变更的时候可能会发生。如果丢事件发生就会破坏了数据一致性,我们引入了自动 diff 和订正的能力,即计算 zookeeper 和 etcd 中数据存在的差异,每次都会经过两轮 diff 计算,因为在频繁变更数据的情况下,一轮 diff 计算往往存在一些因不是强一致性同步导致的"伪差异",当 diff 计算出了结果就会自动 fix 掉这些差异。
如何解决与 etcd2etcd 共存?当同一个路径下,即有 etcd2etcd 同步写入的数据,又有 zk2etcd 写入的数据,在 zk2etcd 的自动订正逻辑里面,会计算出差异并订正差异,但我们不希望因此而误删 etcd2etcd 写入的数据。我们通过为 zk2etcd 引入了 redis 来存储状态解决了这个问题,在 zk2etcd 往 etcd 中同步写入或删除数据时,也同步在 redis 中记录和删除:
然后 zk2etcd 在自动订正计算差异的时候,只考虑本工具写入过的数据,避免误删其它同步工具写入的数据。
etcd2etcd 工程化实践
为了解决 etcd 同步难题,我们调研了如下两种方案,接下来我们就详细介绍下它的原理:
etcd-syncer 之 mirror-plus 版
首先我们介绍下 etcd-syncer 的 mirror-plus 方案,顾名思义,它是 etcd 社区 make-mirror 的加强版。为了解决 make-mirror 的各种缺陷,它实现了以下特性、优点:
那么它的缺点是什么呢?因为它核心原理依然是依赖 etcd 的 mvcc+watch 特性,因此数据无法保证强一致性和只同步 key-value 数据。
etcd-syncer 之 Raft 版
为了解决所有类型的数据同步问题以及消除对 etcd mvcc 历史数据的依赖,腾讯云还可提供基于 Raft 日志的同步方案 etcd-syncer 之 raft 版本。
它的部署图如下所示,etcd-syncer 同步服务作为一个类似 learner 节点的身份,加入主 etcd 集群。
主 etcd 集群 Leader 将 Raft 日志数据通过 MsgApp/Snapshot 等消息同步给 etcd-syncer, etcd-syncer 解析 Raft 日志,将 Raft 日志条目对应的 Txn/Delete/Auth 等请求应用到目的 etcd 集群。
它具备如下优点:
完备的容灾测试
grpc-proxy
此方案引入了 grpc-proxy 代理服务,也是头一次使用。为了了解此代理服务的性能情况,我们使用 etcd 自带的 benchmark 进行了读和写的测试,另外手写了一个小工具做了一下 watch 测试。以下为部分测试内容。
写入测试
直接访问 etcd 服务的负载均衡入口
走 grpc-proxy 代理访问 etcd 服务的情况
读取测试
直接访问 etcd 服务的负载均衡入口
走 grpc-proxy 代理访问 etcd 服务的情况
watch 测试
根据我们自己写的一个 etcdwatcher 服务对 grpc-proxy 进行 watch 测试:可以设置总 watcher 数量,更新频率,以及测试时间,结束时打印出简报
./etcdwatch -num=100 -span=500 -duration=10 -endpoint=http://grpc-proxy-addr:23791test donetotal 100 task0 task failedcurrent revision is 631490least revision is 6314900 task is not synced
参数说明:
以上测试结果来看:failed 数为 0,watch 测试正常
zk2etcd
我们使用的是 1.2.5 版本,通过 k8s 的 deployment 方式部署
场景
现象
模拟操作
现象
3. 模拟etcd失联
模拟操作
现象
总结
etcd2etcd
etcd2etcd 的同步服务,我采用 deployment 双副本部署
期望
测试方案
结论
不论是增量同步还是全量同步过程中,主备切换都能正常工作(需要注意的是,当全量同步中发生主备切换后会变为增量同步,从而可能导致比对较慢)
期望
其实在第 1 部分,备节点切换为主后接管同步工作,fast_path 变为 1 也证明了断点续传能力,我们还额外补充几个验证场景:
(a) 短时间故障
故障场景
现象
(b) 长时间故障
故障场景
现象
分析
3. reset 触发全量同步
当同步发生重大差异(如,发生 dst miss)进行紧急修复的时候,通过配置 --reset-last-synced-rev 参数删除断点续传信息,来触发全量同步修复差异
现象
因某种异常,同步出现 dst miss(图中黄线实例)的情况。为了进行修复,新实例添加 --reset-last-synced-rev 参数后运行
分析
4. 网络故障
两 etcd 集群之间专线中断
测试方案
总结
往期精选推荐
点个“在看”每天学习最新技术