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