Dubbo Mesh在闲鱼生产环境的落地实践

\u003cp\u003e作者曾在2018 QCon上海站以《\u003ca href=\"https://mp.weixin.qq.com/s/Q6kEgxdpcGdEEmQ44cFpQQ\"\u003eService Mesh的本质、价值和应用探索\u003c/a\u003e》为题做了一次分享,其中谈到了Dubbo Mesh的整体发展思路是“借力开源、反哺开源”,也讲到了Service Mesh在阿里巴巴的发路径将经历“撬动”、“做透价值渗透”和“实现技术换代”三大阶段。本文基于Dubbo Mesh在\u003ca href=\"https://2.taobao.com/\"\u003e闲鱼\u003c/a\u003e生产环境的落地,分享以多语言为撬动点的阶段性总结。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://static001.infoq.cn/resource/image/5c/83/5c48dd92d8d6ea0a746f96c46953dd83.jpg\" alt=\"\" /\u003e\u003c/p\u003e\n\u003ch2\u003e闲鱼场景的特点\u003c/h2\u003e\n\u003cp\u003e闲鱼采用的编程语言是Dart,思路是通过Flutter和Dart实现iOS、Android两个客户端以及Dart服务端,以“三端一体”的思路去探索多端融合的高效软件开发模式。更多细节请参考作者同事陈新新在2018 QCon上海站的主题分享《\u003ca href=\"https://mp.weixin.qq.com/s/Np03OpGbTQUajn_78z3fUg\"\u003eFlutter \u0026amp; Dart三端一体化开发\u003c/a\u003e》。本文将关注三端中的Dart服务端上运用Dubbo Mesh去解耦Dubbo RPC框架的初步实践成果。\u003c/p\u003e\n\u003cp\u003eDart服务端是一个服务调用胶水层,收到来自接入网关发来的HTTP请求后,通过C++ SDK调用集团广泛提供的Dubbo服务完成业务逻辑处理后返回结果。然而,C++ SDK的功能与Java的存在一定的差距,比如缺失限流降级等对于保障大促稳定性很重要的功能。从长远发展的角度,闲鱼团队希望通过Dubbo Mesh能屏蔽或简化不同技术栈使用中间件(比如,RPC、限流降级、监控、配置管理等)的复杂性。这一诉求的由来,是闲鱼团队通过几个月的实践,发现在Dart语言中通过C++ SDK逐个接入不同中间件存在定制和维护成本高的问题。值得说明,所谓的“定制”是因为C++ SDK的能力弱于Java SDK而要做补齐所致。\u003c/p\u003e\n\u003cp\u003eDart服务端自身的业务逻辑很轻且在一些场景下需要调用20多次Dubbo服务,这对于Dubbo Mesh的技术挑战会显得更大。在Dubbo Mesh还没在生产环境落地过而缺乏第一手数据的情形下,其性能是否完全满足业务的要求是大家普遍关心的。\u003c/p\u003e\n\u003ch2\u003e架构与实现\u003c/h2\u003e\n\u003cp\u003e\u003cimg src=\"https://static001.infoq.cn/resource/image/36/f2/36ebe56254fc7437a698b8853ba951f2.jpg\" alt=\"\" /\u003e\u003c/p\u003e\n\u003ccenter\u003eDubbo Mesh 架构图(监控部分未表达)\u003c/center\u003e\n\u003cp\u003e图中的虚框代表了一个\u003ca href=\"https://github.com/alibaba/pouch\"\u003ePouch\u003c/a\u003e容器(也可以是一台物理机或虚拟机)。左边两个容器部署了Dubbo Mesh,剩下最右边的则没有。目前Dubbo Mesh主要包含Bonder、Pilot、Envoy三个进程,以及被轻量化的Thin SDK。其中:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eEnvoy承担了数据平面的角色,所有mesh流量将由它完成服务发现与路由而中转。Envoy由Lyft初创且目前成为了CNCF的毕业项目,我们在之上增加了对Dubbo协议的支持,并将之反哺到了开源社区(还有不少代码在等待社区review通过后才能进到GitHub的代码仓库)。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ePilot和Bonder共同承担控制平面的角色,实现服务注册、进程拉起与保活、集群信息和配置推送等功能。Pilot进程的代码源于开源Istio的pilot-discovery组件,我们针对阿里巴巴集团环境做了一定的改造(比如,与\u003ca href=\"https://github.com/alibaba/nacos\"\u003eNacos\u003c/a\u003e进行适配去访问服务注册中心),且采用下沉到应用机器的方式进行部署,这一点与开源的集群化部署很不一样。背后的思考是,Pilot的集群化部署对于大规模集群信息的同步是非常大的一个挑战,今天开源的Istio并不具备这一能力,未来需要Nacos团队对之进行增强,在没有完全准备好前通过下沉部署的方式能加速Service Mesh的探索历程。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eThin SDK是Fat SDK经过裁剪后只保留了对Dubbo协议进行编解码的能力。为了容灾,当Thin SDK位于Consumer侧时增加了一条容灾通道,细节将在文后做进一步展开。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e数据链路全部采用单条TCP长连接,这一点与非mesh场景是一致的。Pilot与Envoy两进程间采用的是gRPC/xDS协议进行通讯。\u003c/p\u003e\n\u003cp\u003e图中同时示例了mesh下的Consumer能同时调用mesh下的服务(图中以www.mesh.com域名做示例)和非mesh下的服务(图中以www.non-mesh.com域名做示例)。闲鱼落地的场景为了避免对20多个依赖服务进行改造,流量走的是mesh下的Consumer调用非mesh下的Provider这一形式,读者可以理解为图中最左边的容器部署的是Dart服务端,它将调用图中最右边容器所提供的服务去实现业务逻辑。\u003c/p\u003e\n\u003ch2\u003e容灾\u003c/h2\u003e\n\u003cp\u003e从Dubbo Mesh下的Provider角度,由于通常是集群化部署的,当一个Provider出现问题(无论是mesh组件引起的,还是Provider自身导致的)而使服务无法调通时,Consumer侧的Envoy所实现的重试机制会将服务请求转发到其他Provider。换句话说,集群化部署的Provider天然具备一定的容灾能力,在mesh场景下无需特别处理。\u003c/p\u003e\n\u003cp\u003e站在Dubbo Mesh的Consumer立场,如果完全依赖mesh链路去调用Provider,当mesh链路出现问题时则会导致所有服务都调不通,这往往会引发业务可用性问题。为此,Thin SDK中提供了一个直连Provider的机制,只不过实现方式比Fat SDK轻量了许多。Thin SDK会定期从Envoy的Admin接口获取所依赖服务的Provider的IP列表,以备检测到mesh链路存在问题时用于直连。比如,针对每一个依赖的服务获取最多10个Provider的IP地址,当mesh链路不通时以round robin算法向这些Provider直接发起调用。由于容灾是针对mesh链路的短暂失败而准备的,所以IP地址的多少并不是一个非常关键的点。Thin SDK检测mesh链路的异常大致有如下场景:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e与Envoy的长连接出现中断,这是Envoy发生crash所致。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e所发起的服务调用收到No Route Found、No Healthy Upstream等错误响应。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2\u003e优化\u003c/h2\u003e\n\u003cp\u003e在闲鱼落地Dubbo Mesh的初期我们走了一个“弯路”。具体说来,最开始为了快速落地而采用了Dubbo over HTTP 1.1/2的模式,也即,将Dubbo协议封装在HTTP 1.1/2的消息体中完成服务调用。这一方案虽然能很好地享受Envoy已完整支持HTTP 1.1/2协议而带来的开发工作量少的好处,但性能测试表明其资源开销并不符合大家的预期。体现于,不仅Consumer侧使用mesh后带来更高的CPU开销,Provider侧也因为要提供通过HTTP 1.1/2进行调用的能力而导致多出20%的CPU开销且存在改造工作。最终,我们回到让Envoy原生支持Dubbo协议的道路上来。\u003c/p\u003e\n\u003cp\u003eEnvoy支持Dubbo协议经历了两大阶段。第一个阶段Envoy与上游的通讯并没有采用单条长连接,使得Provider的CPU开销因为多连接而存在不可忽视的递增。第二个阶段则完全采用单条长连接,通过多路复用的模式去除了前一阶段给Provider所带去的额外CPU开销。\u003c/p\u003e\n\u003cp\u003eDubbo Mesh在闲鱼预发环境上线进行性能与功能验证时,我们意外地发现,Istio原生Pilot的实现会将全量集群信息都推送给处于Consumer侧的Envoy(Provider侧没有这一问题),导致Pilot自身的CPU开销过大,而频繁的全量集群信息推送也使得Envoy不时会出现CPU负荷毛刺并遭受没有必要的内存开销。为此,我们针对这一问题做了集群信息按需加载的重大改造,这一优化对于更大规模与范围下运用Dubbo Mesh具有非常重要的意义。优化的大致思路是:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eThin SDK提供一个API供Consumer的应用在初始化时调用,周知其所需调用的服务列表。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eThin SDK通过HTTP API将所依赖的服务列表告诉Bonder,Bonder将之保存到本地文件。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eEnvoy启动时读取Bonder所保存的服务列表文件,将之当作元信息转给Pilot。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ePilot向Nacos只订阅服务列表中的集群信息更新消息且只将这些消息推送给Envoy。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2\u003e监控\u003c/h2\u003e\n\u003cp\u003e可观测性(observability)是Service Mesh非常重要的内容,在服务调用链路上插入了Envoy的情形下,愈加需要通过更强的监控措施去治理其上的所有微服务。Dubbo Mesh的监控方案并没有使用Istio/Mixer这样的设计,而是沿用了阿里巴巴集团内部的方式,即信息由各进程以日志的形式输出,然后通过日志采集程序将之送到指定的服务端进行后期加工并最终展示于控制台。目前Dubbo Mesh通过EagleEye去跟踪调用链,通过\u003ca href=\"https://www.aliyun.com/product/arms?spm=a2c4g.11174283.2.1.43163299CjDZYa\u0026amp;accounttraceid=4e9ad461-10c8-4197-aeab-7a241d349ab5\"\u003eARMS\u003c/a\u003e去展示其他的监控信息。\u003c/p\u003e\n\u003ch2\u003e性能评估\u003c/h2\u003e\n\u003cp\u003e为了评估Dubbo Mesh的性能能否满足闲鱼业务的需要,我们设计了如下图所示的性能比对测试方案。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://static001.infoq.cn/resource/image/15/df/1539494d81aa9a6c4a85667309251adf.jpg\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cp\u003e其中:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e测试机器是阿里巴巴集团生产环境中的3台4核8G内存的Pouch容器。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e蓝色方框代表的是进程。测试数据全部从部署了DartServer和Envoy两进程的测试机2上获得。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e性能数据分别在非mesh(图中红色数据流)和mesh(图中蓝色数据流)两个场景下获得。显然,Mesh场景下的服务流量多了Envoy进程所带来的一跳。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eDartServer收到来自施压的Loader进程所发来的一个请求后,将发出21次到Provider进程的RPC调用。在评估Dubbo Mesh的性能时,这21次是串行发出的(下文列出的测试数据是在这一情形下收集的),实际闲鱼生产环境上线时考虑了进行并行发送去进一步降低整体调用时延(即便没有mesh时,闲鱼的业务也是这样实现的)。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eProvider进程端并没有部署Envoy进程。这省去了初期引入Dubbo Mesh对Provider端的改造成本,降低了落地的工作量和难度。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e设计测试方案时,我们与闲鱼的同学共创了如何回答打算运用Dubbo Mesh的业务方一定会问的问题,即“使用Dubbo Mesh后对RT(Response Time)和CPU负荷的影响有多大”。背后的动机是,业务方希望通过RT这一指标去了解Dubbo Mesh对用户体验的影响,基于CPU负荷的增长去掌握运用新技术所引发的成本。面对这一问题通常的回答是“在某某QPS下,RT增加了x%,CPU负荷增加了y%”,但这样的回答如果不进行具体测试是无法给出的(会出现“鸡和蛋的问题”)。因为每个业务的天然不同使得一个完整请求的RT会存在很大的差别(从几毫秒到几百毫秒),而实现业务逻辑所需的计算量又最终决定了机器的CPU负荷水平。基于此,我们设计的测试方案在于评估引入Dubbo Mesh后,每经过一跳Envoy所引入的RT和CPU增量。当这一数据出来后,业务方完全可以基于自己业务的现有数据去计算出引入Dubbo Mesh后的而掌握大致的影响情况。显然,背后的逻辑假设是“Envoy对于每个Dubbo服务调用的计算量是一样的”,事实也确实如此。\u003c/p\u003e\n\u003ch2\u003e测试数据\u003c/h2\u003e\n\u003cp\u003e以下是Loader发出的请求在并发度为100的情形下所采集的数据。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://static001.infoq.cn/resource/image/6c/b8/6c3e4623ed2bb649e66024581ad095b8.jpg\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cp\u003e表中:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eEnvoy的QPS是Loader的21倍,原因在上面测试方案部分有交代。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e“单跳”的数据是从“21跳合计”直接除以21所得,其严谨性值得商榷,但用于初步评估仍具参考价值(有数据比没有数据强)。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e“整机负荷”代表了在mesh场景下测试机器2上DartServer和Envoy两进程的CPU开销总和。测试表明,CPU负荷高时Envoy带来的单跳RT增幅更大(比如表中Loader的QPS是480时)。给出整机负荷是为了提醒读者关注引入mesh前业务的正常单机水位,以便更为客观地评估运用Dubbo Mesh将带来的潜在影响。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e“CPU负荷增幅”是指CPU增加的幅度。由于测试机是4核的,所以整机的CPU负荷是400。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e从表中数据来看,随着机器整体负荷的增加“CPU负荷增幅”在高段存在波动,这与RT在高段的持续增大存在相关,从RT在整体测试中完全符合线性增长来看整体数据合理。当然, 后面值得深入研究数据背后的隐藏技术细节以便深入优化。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2\u003e线上数据\u003c/h2\u003e\n\u003cp\u003eDubbo Mesh正式生产环境上线后,我们通过对上线前后的某接口的RT数据进行了全天的比对,以便大致掌握mesh化后的影响。2019-01-14该接口全面切成了走Dubbo Mesh,我们取的是2019-01-20日的数据。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://static001.infoq.cn/resource/image/c6/f0/c6d95270fc73cdfcd1b24c9fef5457f0.jpg\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cp\u003e图中蓝色是mesh化后的RT表现(RT均值3.3),而橙色是mesh化前的RT表现(RT均值3.27,取的是2019-01-13的数据)。由于线上每天的环境都有所不同,要做绝对的比较并不可能。但通过上面的比较不难看出,mesh化前后对于整体RT的影响相当的小。当整体RT小于5毫秒是如此,如果整体RT是几十、几百毫秒则影响就更小。\u003c/p\u003e\n\u003cp\u003e为了帮助更全面地看待业务流量的波动特点,下面分别列出了两天非mesh(2019-01-06和2019-01-13)和两天mesh(2019-01-20和2019-01-23)的比对数据。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://static001.infoq.cn/resource/image/1f/ad/1f2ac3233a6e21e73712b7e880be38ad.jpg\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://static001.infoq.cn/resource/image/a5/9a/a5763264d02b6384d57dd0ac1100ea9a.jpg\" alt=\"\" /\u003e\u003c/p\u003e\n\u003cp\u003e总之,生产环境上的数据表现与前面性能评估方案下所获得的测试数据能很好地吻合。\u003c/p\u003e\n\u003ch2\u003e洞见\u003c/h2\u003e\n\u003cp\u003eDubbo Mesh在闲鱼生产环境的落地实践让我们收获了如下的洞见:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e服务发现的时效性是Service Mesh技术的首要关键。 以集群方式提供服务的情形下(这是分布式应用的常态),因为应用发布而导致集群中机器状态的变更如何及时准确地推送到数据平面是极具挑战的问题。对于阿里巴巴集团来说,这是Nacos团队致力于解决的问题。开源版本的Istio能否在生产环境中运用于大规模分布式应用也首先取决于这一能力。频繁的集群信息推送,将给控制平面和数据平面都带去负荷扰动,如何通过技术手段控制好扰动是需要特别关注的,对于数据平面来说编程语言的“确定性”(比如,没有VM、没有GC)在其中将起到不可忽视的作用。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e数据平面的软件实现最大程度地减少内存分配与释放将显著地改善性能。有两大举措可以考虑:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e逻辑与数据相分离。 以在Envoy中实现Dubbo协议为例,Envoy每收到一个RPC请求都会动态地创建fitler去处理,一旦实现逻辑与数据相分离,filter的创建对于每一个worker线程有且只有一次,通过这一个filter去处理所有的RPC请求。\u003c/li\u003e\n\u003cli\u003e使用内存池。 Envoy的实现中基本没有用到内存池,如果采用内存池对分配出来的各种bufffer通过链表进行缓存,这将省去大量的内存分配与释放而改善性能。再则,对于处理一个RPC请求而多次分散分配的动作整合成集中一次性分配也是值得运用的优化技巧。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e数据平面的runtime profiling是关键技术。 Service Mesh虽然对业务代码没有侵入性,但对服务流量具有侵入性,如何在出现业务毛刺的情形下,快速地通过runtime profiling去发现问题或自证清白是非常值得关注的点。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2\u003e心得\u003c/h2\u003e\n\u003cp\u003e一年不到的探索旅程,让团队更加笃定“借力开源,反哺开源”的发展思路。随着对Istio和Envoy实现细节的更多掌握,团队很强列地感受到了走“站在巨人的肩膀上”发展的道路少走了很多弯路,除了快速跟进业界的发展步伐与思路,还将省下精力去做更有价值的事和创新。\u003c/p\u003e\n\u003cp\u003e此外,Istio和Envoy两个开源项目的工程质量都很高,单元测试等质量保证手段是日常开发工作中的基础环节,而我们也完全采纳了这些实践。比如,内部搭建了CI环境、每次代码提交将自动触发单元测试、代码经过code review并完成单元测试才能入库、自动化性能测试等。\u003c/p\u003e\n\u003ch2\u003e展望\u003c/h2\u003e\n\u003cp\u003e在2019年接下来的日子,我们将着手:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e与\u003ca href=\"https://github.com/alibaba/Sentinel\"\u003eSentinel\u003c/a\u003e团队形成合力,将Sentinel的能力纳入到Dubbo Mesh中补全对HTTP和Dubbo协议的限流、降级和熔断能力。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e在阿里巴巴集团大范围Kubernetes(Sigma 3.1)落地的背景下,与兄弟团队探索更加优雅的服务流量透明拦截技术方案。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e迎合Serverless的技术发展趋势,深化通过Dubbo Mesh更好地轻量化应用,以及基于Dubbo Mesh对服务流量的天然敏感性去更好地实现auto-scaling。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e在产品的易用性和工程效率方面踏实进取。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e未来,我们将及时与读者分享阿里巴巴集团在Service Mesh这一新技术领域的探索成果,也期待与大家有更多的互动交流。\u003c/p\u003e\n

你可能感兴趣的:(Dubbo Mesh在闲鱼生产环境的落地实践)