文章整理:加米谷大数据
图 1 MNS 1.0 架构
从架构上看,MNS 1.0 主要分为三层:首先是嵌入业务内部的 SDK,用作业务自定义调用;然后是驻守在每个机器上的 SgAgent,以代理的方式将一些易变的、消耗性能的计算逻辑与业务进程分离开来,从而降低 SDK 对业务的侵入,减少策略变动对业务的干扰;远端是集中式的组件,包括健康检查模块 Scanner,鉴权缓存模块 MNSC,以及基于 ZooKeeper(以下简称 ZK)打造的一致性组件 MNS-ZK,作为通知和存储模块。在层级之间设立多级缓存,利用“边缘计算”思想拆分逻辑,简化数据,尽量将路由分配等工作均摊到端上,从而降低中心组件负载。
更多详情大家可参考《美团大规模微服务通信框架及治理体系 OCTO 核心组件开源》一文中的 OCTO-NS 部分。
在体量方面,MNS 1.0 已经接入了美团所有的在线应用,涉及上万项服务、数十万个节点,并覆盖了美团所有的业务线,日均调用达万亿级别,目前我们已将其开源。
总的来讲,作为公司级核心服务治理组件,MNS 1.0 在架构上带有明显的 CP 属性,在保持架构简洁、流程清晰的同时,还支持了业务大量迭代的需求,较好地帮助公司业务实现了服务化、标准化的目标。
图 2 近三年美团业务增长数据
但是随着美团业务的快速增长,公司的服务数、节点数、服务信息量、服务变动频次等维度都在快速增长,有些服务甚至呈现出跨数量级的增长。在这样的情况下,命名服务也面临着一些新的问题和挑战:
图 3 命名服务应该是 AP 系统
从可用性、扩展性、性能等三个方面,MNS 1.0 暴露出很多的问题,究其根源,原来的命名服务作为一个 CP 系统,为获得“数据一致性”而牺牲了部分情况下的可用性。其实,对于命名服务而言,一致性并没有这么重要,最多是调用方在一定时间内调多了或调漏了服务方而已,这个后果在不可靠网络的大前提下,即使命名服务没有出现问题,也可能经常会出现。借助端的自我检查调整机制,其影响可以说微乎其微。而另一方面,如果是一个机房或一个地域的调用方和服务方没有问题,但是因为这个区域和命名服务主集群断开了链接,从而导致本域内不能互调,这个就显得不太合理了。
所以总结一下,我们认为, 命名服务的本质是建立和增强服务本身的连通性,而不是主动去破坏连通性 。命名服务本质上应该是一个 AP 系统。
其实,业界对命名服务 AP/CP 模式都有相应实现和应用,很多企业使用 CP 模式,原因可能有以下几点:
另外,我们了解到,一些公司使用特殊的方式弱化了这个问题,比如将 CP 系统进一步拆分到更小的域中(比如一个 IDC),缩小分区的粒度,而全局有多个 CP 系统自治。当然,这个可能跟调用方服务方的跨度限制或者说调用配套部署有关系,但也有可能带来更复杂的问题(比如 CP 系统之间的数据同步),这里就不做详细的讨论了。
除去 MNS 1.0 本身的架构缺陷,我们还需要面临另一个问题,当初在项目启动时,云原生尚处于起步阶段,而如今,一些基于云原生理念兴起的网络基础设施,尤其是 Service Mesh 在美团快速发展,也需要 MNS 进行改造去适配新的流量通道和管控组件,这也是此次 MNS 2.0 演进的目标之一。
综上,我们以 AP 化、Mesh 化为主要目标,正式开始了从 MNS 1.0 向 MNS 2.0 的演进。
图 4 MNS 2.0 整体架构
MNS 2.0 的整体架构自上而下主要分为四层:
综上所述,MNS 2.0 整体架构在兼容 1.0 的前提下,重点变化是: 新增了控制服务层并对底层存储介质进行拆解 。下面,我们来看一下服务注册发现功能在 MNS 2.0 中的实现流程:
图 5 MNS 2.0 中的服务注册发现流程
接下来,我们一起来看下 MNS2.0 的主要演进成果。
流量洪峰对于不同领域而言有不同的时段。对于 O2O 领域比如美团来说,就是每天中午的外卖高峰期,然后每天晚上也会有酒旅入住的高峰等等。当然,基于其它的业务场景,也会有不同的高峰来源。比如通过“借势营销”等运营手段,带来的高峰量可能会远超预期,进而对服务造成巨大的压力。
图 6 流量洪峰
MNS 1.0 受制于 MNS-ZK 集群数量上限和强一致性的要求,无法做到快速、平行扩展。MNS 2.0 的数据存储层重心是保证数据安全读写和分布式协调,在扩展能力层面不应该对其有太多的要求。MNS 2.0 的平行扩展能力主要体现在控制服务层,其具体功能包括以下两个方面:
集群分片 :控制服务提供全量注册数据的 分片能力 ,解决命名服务单独进行大集群部署时不能进行业务线隔离的风险。MNS-Control 网关模块分为 Master 和 Shard 等两类角色,协作提供大集群分片能力。
图 7 控制服务数据分片示意图
网络分区可用 :MNS 1.0 阶段,网络分区对可用性的影响巨大,分区后 MNS-ZK 直接拒绝服务。新架构将存储迁移到 KV 系统后,在网络专线抖动等极端情况下,各区域依然能正常提供数据读取功能。同时,我们与公司存储团队共建 C++ SDK 的地域就近读写功能。一方面,提高跨域服务注册发现的性能,另一方面,实现了网络分区后,各区域内命名服务可读、可写的目标,提高了系统的可用性,整个命名服务对网络的敏感度降低。
图 8 命名服务的平行扩展
目前,控制服务层在平行扩缩容时间和集群原地恢复时间等方面,都达到了分钟级,集群也没有节点上限,从而能够有效应对突发的流量,防止服务出现“雪崩”。
另一个典型的场景是推送“风暴”,在服务集中发布、出现网络抖动等情况下,会导致命名服务出现 " 一横一纵 " 两种类型的放大效应:横向是“关注放大”,类似于社交网络中某大 V 消息需要分发给众多的粉丝,服务越核心,放大的效果越明显。纵向是“级联放大”,命名服务的上下游会逐级进行拷贝发送,甚至一级上下游会针对一个消息有多次的交互(Notify+Pull)。
图 9 命名服务领域的消息放大现象
“关注放大”和“级联放大”本身都是无法避免的,这是由系统属性决定,而我们能做的就是从两方面去平滑其带来的影响:
正面提升核心模块性能,增强吞吐、降低延迟
另外,包括前面提到的控制服务集群的平行扩展能力,其实也是整体性能提升的一种方式。
侧面疏通,区分冷热数据,降低推送的数据量,提高效能
自然界中普遍存在“80/20 法则”,命名服务也不例外。服务注册信息的结构体中元素,80% 的改动主要是针对 20% 的成员,比较典型的就是服务状态。因此,我们将单个整块的服务信息结构体,拆分为多个较小的结构体分离存储;当数据变动发生时,按需分发对应的新结构体,能够降低推送的数据量,有效减少网络带宽的占用,避免代理组件重复计算引起的 CPU 开销,数据结构变小后,内存开销也得到显著降低。
那是否需要做到完全的“按需更新”,仅推送数据结构中变动的元素呢?我们认为,完全的“按需更新”需要非常精细的架构设计,并会引入额外的计算存储开销。比如,我们需要将变动成员分开存储以实现细粒度 Watcher,或用专门服务识别变动元素然后进行推送。
同时,还要保证不同成员变动时,每次都要推送成功,否则就会出现不一致等问题。在组件计算逻辑所需的数据发生变化时,也会带来更多的改动。在命名服务这样的大型分布式系统中,“复杂”、“易变”就意味着稳定性的下降和风险的上升。所以根据“80/20 法则”,我们进行尺度合理的冷热数据分离,这是在方案有效性和风险性上进行权衡后的结果。
图 10 冷热数据分拆推送
经过改造,MNS 2.0 相比 MNS 1.0 的吞吐能力提升 8 倍以上,推送成功率从 96% 提升到 99%+,1K 大小服务列表服务发现的平均耗时,从 10s 降低到 1s,TP999 从 90s 下降到 10s,整体优化效果非常明显。
在 MNS 2.0 中,我们将代理服务 SgAgent 部分注册发现功能合并到了 Mesh 数据面,其流程如下图所示:
图 11 命名服务与 Service Mesh 的融合
关于美团服务治理功能与 Service Mesh 结合的技术细节,这部分的内容,我们今年会单独做一个专题来进行分享,感兴趣的同学可以关注“美团技术团队”微信公众号,敬请期待。
除了上面说到的 MNS 2.0 的这些重点演进成果之外,我们还想谈一下整个命名服务的迁移过程。像 MNS 这样涉及多个组件、部署在公司几十万个机器节点上、支撑数万业务系统的大规模分布式系统,如何进行平滑的数据迁移而不中断业务正常服务,甚至不让业务感知到,是 MNS 2.0 设计的一个重点。围绕业务服务无感知、具备快速回滚能力、新 / 老体系互备数据不丢失等要求,我们设计了如下图所示的迁移流程:
图 12 MNS 2.0 的无损迁移
MNS 2.0 整体以服务为粒度进行迁移操作,设置标志位说明服务所处的状态,由接入代理层组件识别该标志做出相应的处理。标志位包括:
上述可以总结为 :聚焦单个服务,阶段性迁移服务发现流量,从而达到类似系统新功能发布时“灰度上线”的能力。当然,这个策略还涉及一些细节在其中,比如,分开存储在双写时需要重点去保证异常情况下的最终一致性等等,鉴于篇幅原因,这里就不详细展开讨论了,而且业界针对这种情况都有一些成熟的做法。
另外,我们通过优化命名服务发布系统的发版形式,实现自动化的流量灰度策略,降低了人力成本,同时构建了自动化的迁移工具、巡检工具,高效地进行自动化的数据迁移工作,能够快速巡检迁移后的链路风险,保障新 MNS 2.0 的稳定上线。
在美团命名服务这样的大型分布式系统优化过程中,具体到架构和功能设计层面,我们也做了不少的权衡和取舍,前面我们也提到,放弃了增量式的精准变动信息推送方式。在考虑性能的前提下,也没有使用数据多维度营运能力更好的 MySQL 作为主要存取介质等等。取舍的核心原则是: 改造目标时强调业务收益,落地过程中减少业务感知 。
目前,MNS 2.0 主要成果如下:
命名服务本身作为基础的技术中台设施,在坚持“以客户为中心”,升级自身架构的同时,也从如下几个方面对美团的多个业务进行赋能。
单元化(SET 化)是业界比较流行的容灾扩展方案,关于美团单元化的详细内容,可参考 OCTO 团队在本次 ArchSummit 中的另一个专题分享《SET 化技术与美团点评实践解密》。这里主要是从命名服务对单元化支撑的角度去解答这个问题。
图 13 命名服务对单元化的支持
如上图所示,业务多种来源的外网流量在通过网关进入内网后,会借助命名服务提供的能力(SDK/Agent),并按照业务自定义的核心数据维度和机器属性,给流量打上单元化标签,然后路由到标签匹配的下一跳,从而实现了单元间流量隔离。一个单元内部,从服务节点到各种存储组件,都依赖于命名服务提供的单元识别和路由能力来完成隔离,所以命名服务在单元化中主要起底层支撑的作用。
目前单元化在美团的重点业务,比如外卖、配送已经建设的比较完备,通过一定的单元冗余度,能在一个单元出现问题时,切换到另一个可用的镜像单元,显著提高了业务整体可用性。
接下来,我们再来看一下泳道场景。目前泳道在美团这边主要用于业务做完代码 UT 之后的线下集成测试阶段,同时结合容器,实现一个即插即用的上下游调用环境去验证逻辑。命名服务在其中起到的作用与单元化类似,根据泳道发布平台对机器的配置,自动编排上下游的调用关系。如下图所示:
图 14 泳道流量示意图
当下一跳存在泳道节点时,测试流量进入泳道。反之,测试流量回流到主干。每个节点重复上述过程。
命名服务另外一个重要场景是服务的平滑发布,我们与发布平台配合,控制发布流量的自动摘除与恢复,实现服务发布操作的自动化与透明化。具体流程如下图所示:
图 15 命名服务支持平滑发布
早期的发布流程,存在异常调用的风险,上线过程中存在一段服务实例不可用的“时间窗口”,此时流量再进入可能导致调用失败,然后会报错。为保证发布的稳定性,需要操作人员手动进行流量排空,流程上效率低、易出错,浪费业务团队的时间。针对这项业务痛点,命名服务向发布系统提供针对服务实例的流量管控能力,实现进程重启前自动摘除并清空来源请求,升级完毕后,提供自动流量恢复的平滑发布功能。智能地解决发版过程中的异常调用问题,提高公司的服务上线效率,降低了业务团队的运维成本。
弹性伸缩是容器一个很大的卖点。但是在伸缩过程中有很多问题需要考虑,比如扩缩容策略,包括调用亲和度配置,路由均衡等等。命名服务和容器化合作,提供业务的上下游调用关系、分组设置信息、容器下线流量无损摘除等服务,同时保障伸缩服务注册及时、高效。如下图所示:
图 16 命名服务支持弹性伸缩
MNS 服务数据对业务的赋能主要分为两个部分,一是将自身运行状态以业务可理解的 SLA 暴露出来,方便业务评估命名服务健康状况。对于命名服务来说,SLA 指标主要有 推送成功率和 推送耗时 两种(其实,推送耗时也可以看做成功率的一个衡量维度,这里暂时不做太详细的区分)。
MNS 1.0 精准量化运行状况的困难在于,一方面 MNS 的发布 / 订阅机制重度依赖 ZK,受限于 ZK 的自身实现和链路上众多不同组件的异构性,及时获取完整链路的推送行为数据很困难。另一方面,由于节点数众多,全量采集服务发现数据,对公司的监控上报系统以及采集之后的计算也是很大的负担。
在 MNS 2.0 中,我们首先在架构上显著弱化了 ZK 的地位,它基本上只作为一致性通知组件。我们自研的 MNS-Control 可以充分 diy 埋点场景,保证了主要推送行为抓取的可控性。其次,我们采用了分级采样计算,在全面梳理了公司现有的服务节点数比例后,将典型的服务列表数划分为几个档次,比如 1000 个节点的服务为一档,100 个又为一档,并创建相应的非业务哨兵服务,然后在不同机房选取适量的采样机器定期注册 + 拉取来评估整体的运行情况。这样既做到了上报量的精简,又兼顾了上报数据的有效性。详细操作流程,如下图所示:
图 17 MNS 2.0 SLA 采集
命名服务存储的服务信息有很高的业务价值,从中可以知道服务的部署情况、发布频次以及上下游拓扑信息等等。所以“赋能”的第二个部分在于,借助这些信息,我们可以挖掘出业务服务部署运维上不合理的地方或风险点,不断推动优化,从来避免一些不必要的损失。目前已经在推动的项目包括:
在架构上,MNS 2.0 依赖 DB 和其它一些数仓介质,进行多种维度的数据上卷和下钻,并结合一些定制的风险策略逻辑去帮助业务发现和规避问题,目前这个事情也在进行中。
图 18 MNS 2.0 数据赋能
未来,美团命名服务主要会朝两个方向发展: