本文整理自ArchSummit全球架构师峰会演讲。
首先看一下,微服务架构的演进历程中我们更关注的是什么?一般微服务业务应用架构的演进历程无非都是单体应用 -> 服务化 -> 云原生 -> Serverless。这个过程中,每一次的进化,大体上可以认为都是通过拆分和抽象,伴随牺牲一定的性能,来达到最终提升人效的目的。所以可以得出一个结论:在演进式的架构中,越来越关注人效,而非绝对的性能。
那么问题来了,既然人效如此重要,那如何提升服务治理的人效呢?
服务治理的包含了非常多的能力,比如服务通讯、服务注册发现、负载均衡、路由、失败重试等等。在整个服务治理的历程中,我认为涌现出三种思潮。
第一类,中心式的治理。利用集中式的集群来完成治理。比如 HAProxy、Nginx、Tengine、Codis、Mycat 都可以认为是这类治理手段,他的好处就是能够跨语言,问题就是会有性能损耗以及链路单点问题。那如何解决这些问题呢?于是有了第二种思潮如下:
第二类,融合式的分散治理。采用和业务进程通过 SDK 方式彻底融合,来达到去中心化的分散治理的目的。这也是目前基本最主流的服务治理的样貌。比如 Dubbo、Ring-pop、Thrift、gRPC、Motan 的主流样貌。这样的好处即能达到性能的最优和最短链路,但是问题即与业务强耦合带来的跨语言成本和运维升级的高昂代价。到了这边,感觉有点无解,既然中心式部署不行,融合式的部署也不行,那怎么办呢?于是出现了第三种思潮:
第三类,贴合式分散治理。贴合相比于前面谈到的融合的区别,就在于离业务很近,但是不要作为一个 SDK 直接融入业务进程,既能最大程度降低对性能和稳定性影响的同时,同时解决业务耦合的问题。这种架构理念其实很早就被提出了,在 LinkedIn、Airbnb 的 smartstack 治理体系、携程 OSP 以及各种云原生服务治理方案 K8s、Marathon 中都可见到踪影。我们统称之为 Service Mesh — 服务网格。
服务网格在猫眼的落地理论架构
当然,Service Mesh 本身是有一些不成熟或者待商榷的地方。接下来,介绍下服务网格在猫眼的一个实际落地的理论架构。
首先,先明确终极目的:能够在牺牲一小部分绝对性能的前提下实现人效的大幅提升。
众所周知,要提升人效,最好的方式就是一切都自动化,这样就无需人工介入了。
而要实现自动化的前提则是完成标准化,非标的产品要实现自动化的成本、代价是非常之高的,往往即便实现了也容易出现这样那样的逻辑和非逻辑的坑。
而要完成标准化需要做什么呢?就需要进行职责分离,类似于前后端分离的理念,我们需要将业务和服务治理中间件能力分离,否则异构的业务必然会带来服务治理能力的非标准化。
所以进一步推演,可以发现 Service Mesh 其实就是处在推演起点的核心解决方案。在我们看来,Service Mesh 核心理念是两个:
其一,治理与应用分离,业务应用和治理能力的就近物理切割。通过部署本地代理的方式加上 Iptables 拦截的方式来完成这个切割。
其二,强调执行和控制的分离,也即他非常著名的控制平面和数据平面的切分。
然而,这样就足够了吗,理想照进现实的模样是什么样的呢?
Service Mesh 在生产环境应用中面临的难点和挑战部分如下:
控制平面的边界在哪?业内最出名的 Istio 的 Mixer 的 check 方法会带来很严重的性能挑战,即便加了本地 cache 仍然有严重的性能问题,而且可能会带来代理资源开销的指数级提升。Report 方法则会带来 2 倍的网络开销。这种非常暴力的一刀切的方式其实并不为业内所接纳,包括 Istio 本身也正在将 Mixer 能力整合到控制面板中。
新的单体应用困局如何破局?我们是否注意到一点,即我们把各种服务治理甚至其他中间件能力下沉,必然会带来在 PaaS 中间件领域新的单体应用问题。你的配置管理、限流、熔断、混沌工程、服务注册发现,以及各种存储、日志、监控报警的中间件能力都会集中到这个单体代理上,这根本是不可能 Work 的一个架构。针对这种问题,如何解决呢?
零侵入业务真的现实吗?Service Mesh 也很强调对业务进程的零侵入,希望将服务治理能力看待为协议栈的一部分。而我们实际使用中,我们需要或者已经有现成的 RPC 书写和调用的方式,这种方式可以让我们的业务之间的调用更规范、易于上手、不易出错。彻底抹杀掉这部分现实吗?
其他的就是 Service Mesh 本质上是寄生在业务资源中的一个大规模部署的形态,如何保障交付质量、如何降低性能和资源开销,如何最大程度保障可用性,都是需要探索的问题。
基于这些难点与挑战,猫眼摸索出的最终落地理论架构是:有节制、可插拔、半贴合式的无中心治理。
有节制指对于控制平面和控制平面的切割,不追求一刀切。
可插拔是为了应对新单体应用的问题,数据面板的各种能力应该是可以服务化,各种能力是可插拔的。
半贴合式即我们为了保护业务实际使用中的体验,不追求对业务进程完全无侵入,而采取尽可能低侵入的方式进行。
为了确保这个治理的高质量交付,技术团队在可用性、性能、交付质量上都做了充分的工作。下面会具体展开介绍。
在具体介绍之前,大家可能都会有这样一个疑问,“你们为什么不采用开源方案呢?” 国内开源最为活跃的即蚂蚁的 Sofa-Mosn,国外开源最主流的即为 Google 牵头的 Istio。我们主要对标的是这两款产品,没有直接使用主要出于这么几个原因:
在 2018 年启动的时候,国内主流开源方案的 Sofa-Mosn 也处于一个快速迭代,不稳定的状态。
由业务特性决定,不希望上来就和云原生绑定,这个和业内的主流解决方案不太相符。
Istio 本身开启 Mixer 之后存在较为严重的性能问题。而我们本身也希望在性能优化上能够有更多探索的空间。我们的一些性能优化的措施如自研的 IPC 框架和 Istio 本身架构整合成本就很大。
猫眼存在较多已经存在的服务治理中间件,比如前面的高可用治理中心就是其中之一。他们的功能其实和开源的内容存在很大 Gap,无法直接套用,而如果进行整合的话,成本非常之高。基本无法接受。
最后一个则是更加现实的问题,猫眼并没有专业的 C/C++的团队,所以我们不存在基于 Envoy 去开发的基本条件。
基于以上原因考虑,最终没有直接采用开源的产品,而是导向自研。但技术团队在整体自研过程中也充分借鉴了一些开源的优秀设计实现。
基于以上的推演,猫眼的下一代微服务治理体系也就呼之欲出了,代号盘古,为猫眼的服务治理中心。他是猫眼下一代的服务连接、注册、发现、中间件管理的一站式解决方案。
如图是盘古服务治理中心的系统架构。可以看到整体上,仍然是划分为上下两层,即控制平台和数据平面。有几个模块是被标红的
Portal 是数据平面。对标 Envoy、Mosn 这些数据平面。
Dolphin 是轻量级的 Mesh SDK,供业务方进行实际各种服务治理能力使用。
Pilot 是配置型控制平面的对接适配层。对接了注册中心、配置中心和各种元数据中心。
控制平面还包括了监控、链路追踪、流控、地址服务、一站式治理平台等服务。
这个是 Service Mesh 的数据平面架构。整体架构上抽象出 Server、Transport、Stream、Router、Cluster、Resource 几个核心层。
Server 层负责服务启动、可用性保障、指标收集以及 XDS 交互。
Transport 层负责底层通讯链路。
Stream 层负责协议解析、会话绑定/销毁,以及对 Transport 的连接和读写能力的封装。
Router 就是负责将请求路由至对应的 cluster 上。
Cluster 负责进行负载均衡、目标机器筛选、失败重试、连接管理。
Resource 负责对 Mesh 的资源进行管理,包括各类协程池、对象/字节池,以及 SPI 框架。
整体上,他和 Envoy 以及 Sofa-Mosn 的整体架构是非常类似的。
接下来看一下猫眼的 Service Mesh 的一些建设思路。首先,是对控制平面基进行有节制的切割和优化。
最先去做的架构选择,就是将遥测下沉,限流接入自建流控,取消 Mxier,解决性能问题。其次,将服务注册与发现整合,提供完整解决方案。改变了 Service Mesh 只关注服务发现而不关注服务注册的问题。
控制平面可以分为两类:
配置型的控制平面,主要用以下发指令和配置。如注册中心、配置中心、各种中间件的元数据中心。
数据型的控制平面,会有大量的实时数据的存储或分析,比如分布式链路追踪、比如监控报警、比如日志。
配置型平面以 MMCP(Maoyan-MCP)协议快速接入,协议提供 Watch、UnWatch、Push、PassThrough 四个通用能力接口,新的配置型平面只要按照这个协议来接入,就可以实现快速接入,整个过程 Pilot 零改造成本。
数据型控制平面和普通应用一致性对待,我们认为他们就是一个普通应用。也需要接入我们的 Sidecar 来做流量管控,所以我们这里也是对这类控制平面践行 Pet&Cow 理论,即你应该尽量少养宠物,多养奶牛,宠物生病需要治等等的特殊照顾,而奶牛生病直接杀了就行一视同仁。我们希望尽可能地对我们分布式拓扑下的节点一视同仁,这样能降低系统复杂度。
我们对于 Service Mesh 也进行了较多的性能优化的尝试。
采用了基于 Reactor+ 多级协程池的异步通讯模型
将协程进行多级的池化,对于字节和对象资源进行池化。来降低调度和内存分配所带来的资源开销
采用 Copy-On-Write 的方式来对核心配置进行无锁化替换。也采用了 CAS 的方式来对一些核心请求状态进行原子修改。整个过程实现了无锁化的设计。
在协议层面,将 Payload 后置到协议尾,同时反序列化时将 Header 和 Body 的 byte 内容进行缓存。以此来达到加速请求传输的性能。
我们有很多地方都存在着心跳,比如从调用方 Sidecar 到服务提供方的 Sidecar,当调用方依赖多个服务提供方,且服务提供方具有较多实例的时候,我们将不得不建立大量的心跳协程来检测健康状态,这很明显是不 OK 的。所以我们采用了时间轮的方式来进行心跳维持逻辑的优化。降低了资源开销。
同时也提供类似于 Java 的 SPI 机制,对可以单例化的一些对象,比如各种 Filter 进行了单例化处理。
最后, Service Mesh 为了保障一些高流量应用以及后续可能会延伸到的基础设施层的服务的性能,也进行了高流量下 IPC 优化的探索,基于 uds 和 mmap 自研了一个 RingBuffer,以 mmap 传递数据,以 uds 进行事件通知,进行了内存对齐、无锁化等等的优化。最后可以看到在高 QPS 下,其相比于 tcp/uds,最大性能可提升 30%。
经过了大量的性能测试,在压测环境下 RT 小于 0.1ms,在灰度场景下 RT 增加在 0.5ms 以内。稳定性达到了 5 个 9,CPU 消耗在 1%以下,内存占用在 30M 左右。以上是采用 TCP 来进行本地通讯的结果数据,而如果采用我们自研的通讯框架,通讯 RT 可进一步最高提升 30%。
从目前的情况来看,足以满足猫眼业务的要求。当然,在性能优化上,仍然会结合业务需要在合适的时候进行进一步的探索。
在可用性方面,猫眼 Service Mesh 面向猫眼业务,做了充分的保障。
为了方便起见,我们称服务调用方为 C,服务调用方的 Sidecar 为 CA,服务提供方为 P,服务提供方的 Sidecar 为 PA,那么来看下日常运维中可能碰到的一些主要场景:
第一,Mesh 在前期灰度和迭代期间,避免不了会进行经常性的发布。这个时候需要保障业务方流量无损。在当前阶段的做法是,基于状态机的流转,针对 PA 重启的情况,会将链路从 CA->PA 切换为 CA->P。针对 CA 重启的情况,会将链路由 C->CA->PA->P 直接切换为 C->P。等重启之后状态变更回正常了,这个时候再进行回切。后续针对 CA 不可用的场景,也会进行句柄热迁移的能力实现。
第二,业务应用发布需要能够平滑发布。采用通过对老注册中心的状态变更监听,来同步新注册中心对应的状态,这样就可以在不侵入老发布系统平滑发布全流程的时候完成应用的平滑发布。
第三,场景是 Mesh 宕机,首先会有对应的运维 Agent 进行 mesh 的保活,以及我们也有流量防御的机制,主动/被动探测到 mesh 不可用后会做快速的链路切换。最坏情况下,SDK 会自动切换为直连情况,彻底绕过 mesh。
第四,在 C->CA->PA->P 以及和 Pilot,注册中心的交互链路中,任意一个节点出故障,都有对应的被动感知和主动探测的方式来发现并进行主动的 failover。
第五,是实际推动业务试用的过程,必然需要考虑灰度的问题。能够进行服务、机器的多维度灰度,并可以在故障发生时一键回滚。
-第六, 注册中心方面,注册中心可能会出现网络分区的情况,这个时候可能会导致注册中心误判服务提供方不可用而将其剔除,进而引发业务问题。采用类似 Eureka 会引入自我保护的机制,对于突发性的大批量节点下线,我们会不信任注册中心的结果,而主要依赖主动心跳健康检查的判断。没有采用 Envoy 的服务发现注册中心和健康检查共同决定的策略,是因为我们发现这样的 case — 业务中有出现老注册中心显示机器已下线但是服务仍然短时间内可联通的情况。而这个时候如果仍然联通则是非常危险的。
第七,注册中心如果不可用的情况下,会有 Sidecar 内存和文件的多级别缓存来保障可用性。
通过以上手段,我们的可用性一直维持在 6 个 9 左右。很好地为业务提供了各种保障。
在 CI/CD 环节,我们完成了整个流程的闭环建设。在 Pipeline 中,我们进行了自动化的性能测试和混沌测试。我们整个性能测试过程中,需要模拟 5 * 5 * 5 一共 125 种的的场景、调度流量、探测容量上限、对过程中的各种指标进行采集,以及产出结果报告,正常做一轮下来,需要耗费大量的时间和精力,所以我们针对这种情况进行了自动化性能测试体系的搭建,将上述环节都进行了自动化处理,并集成进了 CI 流中,让我们的每一次发版都能够对于代码 Diff 带来的性能变化有更直观的了解。
另外,上线后的生产环境会有随机的各种场景出现,这些场景都可能会引发系统问题,所以在 CI 环节中引入了自动化的混沌测试。针对模拟的复杂拓扑去触发随机概率事件,包括请求响应的包体大小和流量规模也会随机产生,同时会去模拟服务伸缩、服务重启、机器宕机等等的 case。通过这种混沌式的沙盒测试,对系统进行更进一步的可用性和性能的探测。
最后,针对于前面提及的中间件能力的下沉所带来的新的单体应用的困局,技术团队采用的方式就是进行数据平面的“服务化”。
从上到下进行了三层切割:
第一层是中间件门面层,包括 KV、RPC、Trace、Redis 等的中间件底层都基于和 Mesh 交互的统一 SDK,上层由猫眼的脚手架工程统一封装。
第二层即有节制拆分的中间件服务化层,拆分出 RPC、监控、存储等四个 Mesh。这时候,可能就会产生一个问题,即我 RPC mesh 里面也需要监控能力,监控 Mesh 里面也需要 RPC 能力,这本身是一个相互依赖的关系,如何能够以更优雅可控的方式来进行拆解?我们的解决方案也就是构建服务化体系第三层 — 中间件模块化层。
第三层也是中间件 Mesh 服务化的基石。我们将最为通用的能力进行下沉,收敛出 Mesh Stone 这样的底层核心上,在这之上,提供了 RPC、Log、Trace 等等很多可插拔的模块,可以通过在 Mesh 初始化的时候自由组合拼装任意模块来快速完成一个 Mesh 的底座封装,并在这之上去实现自己独有的业务逻辑。如此一来,我们就可以在相互不影响的情况下来实现最大程度的复用。
可以看到,提炼的关键词即第一个,进行三层切割,分为门面、服务化、模块化三层,其次进行头尾合并,最底层有统一的模块化底座 Mesh Stone 来提供最通用的能力和整体框架。最上层有统一的门面封装来为业务方提供一致性的使用体验。最后,为了能够支撑起中间件服务化层的相互独立以及最大化复用,我们允许这种可插拔的模块之间的自由拼装组合。
通过这三点,可以实现数据平面的服务化。但需要特别警惕 Mesh 过渡拆分导致的 Agent 泛滥引发的运维问题,这块需要跟随着后期中间件的实际落地去把握里面的度,猫眼也是在一个探索的道路上。
未来规划与探索
前面提到了猫眼当前在提升系统稳定性和提升人效方面所做的一些探索。高可用治理中心目前已经在猫眼大规模铺开落地实践了,而猫眼基于 Service Mesh 的服务治理中心目前也已经在新业务中进行落地实践,整体上处于在生产环境中持续探索验证的阶段。未来,猫眼的服务治理主要会朝三个方向去探索与演进。
将治理能力 AIOps 化。比如基于更全面的健康度量体系来进行一些智能决策,比如治理策略无参化、智能报警、容量自动评估水位预警、故障诊断、异常探测、动态伸缩等等方面。
希望借由 Service Mesh 数据面板的服务化能力,将中间件进行网格化。以此将网格的红利从 RPC 延伸到我们越来越多的 PaaS 设施中。
建立在云原生的基础上进行 Serverless 的探索,目前的 Serverless 其实对于简单逻辑的应用比较友好,但是对于像基于 Java/Golang 之类的重度业务逻辑的应用来说,较难落地。所以我们也希望在未来探索 Serverless 如何真正意义上能够解放业务方的人力。
未来猫眼的服务治理会通过这三个方向的延伸,聚焦在解决人效、稳定性以及资源利用率的提升上。
评论