Proxyless Service Mesh在百度的实践与思考


Service Mesh已经在云原生界火了很多年,大家的探索热情依然不减。而最近一段时间Proxyless Service Mesh也开始进入大家的视野,比如:
  • Istio官宣支持gRPC Proxyless Service Mesh
  • Dubbo 3.0引入Proxyless Service Mesh架构


那么,什么是Proxyless Service Mesh?它和原来的Proxy Service Mesh有什么区别和优缺点?落地场景又有哪些呢?本文将结合Proxyless Service Mesh在百度的落地实践,带你一探究竟。

什么是Proxyless Service Mesh

先来看下Proxy Service Mesh,也就是最常见的Service Mesh架构,一般如下所示:
1.png

  • 每个App的Pod里面,有一个独立的Sidecar进程,App之间的通信都通过Sidecar进程转发。
  • 有一个全局的控制平面(最常见的实现是Istio),下发配置到每个Sidecar,控制具体请求的转发策略。


而Proxyless Service Mesh则是如下的架构:
2.png

  • 由联编到App进程的RPC框架负责服务之间的通信。
  • 控制平面下发配置到每个RPC框架,RPC框架按照配置进行具体请求的转发。(以上架构图是经过简化的,以目前主流的Proxyless实现,比如gRPC和Istio之间的通信是由Istio Agent来代理的,但这不影响后面的讨论)


Proxyless Service Mesh的优缺点

如果简单对比上述架构,不难得出Proxyless和Proxy模式的优缺点:
3.jpg

以上仅是一些直观的分析,但当真正落地Proxyless Service Mesh的时候,会发现情况并不是我们想的那么简单。

百度的Proxyless Service Mesh实践

Proxyless第一阶段

百度从2018年开始引入Service Mesh,一开始是Proxy模式。到了2020年,我们在落地一些业务线的时候,发现Proxy模式很难在整个业务线全面铺开:
  • 业务其实能够接受Proxy带来的额外资源开销,毕竟我们已经做了很多优化,比如将社区Both Side模式改成Client Side模式(即一次请求只过Client端的代理,不经过Server端的代理);比如将Envoy的流量转发内核替换成bRPC。我们能做到Sidecar占业务进程的cpu消耗在5%以内,有的业务甚至不到1%。
  • 但业务无法接受Proxy带来的延迟增长,即使我们已经把Proxy单次转发增加的延迟优化到0.2毫秒以内,但由于整个业务系统包含了很大的一个调用拓扑,每条边上增加一点点的延迟就能导致流量入口模块增加较大的延迟,进而对业务KPI造成影响。


因此我们开始引入如下的Proxyless Service Mesh模式:
4.png

  • Envoy从Istio拿到流量转发配置,并转化成bRPC能识别的配置
  • bRPC通过http接口从Envoy中拿到流量转发配置,并且按照该配置去调用其它服务


这种方式的好处是:
  • 业务接入Mesh,不会带来延迟增长,也不会增加明显的资源开销。(这里的Envoy仅处理配置,资源开销极小)
  • 业务可以享受Mesh的便利性,如集中管理配置、动态下发配置生效,不再需要改代码或改配置、上线、重启生效,极大提升了服务治理的效率。


Proxyless第二阶段

但是,第一阶段方案,存在一些明显的问题:
  • bRPC里支持的服务治理策略较少,仅支持Istio中的少量功能,这就意味着大部分Istio的能力无法通过Proxyless模式享受到。
  • 随着业务不断增加的需求,我们势必要在Service Mesh中增加更多的服务治理能力。对于Proxy模式,我们需要在Envoy中开发,而对于Proxyless模式,我们又需要在bRPC中开发,由于Envoy和bRPC的策略架构差异很大,这些代码很难复用,就意味着每一个需求都得重复开发两遍。


因此,到了2021年,我们设计并实现了一套Proxyless/Proxy统一架构的Service Mesh:
5.png

  • 左边是Proxyless模式,Envoy负责把xds配置转换成bRPC能识别的配置,bRPC联编到业务进程,从Envoy获取配置并按配置转发请求。
  • 右边是Proxy 模式,Envoy仍负责把xds配置转换成bRPC能识别的配置,bRPC联编到Envoy进程,从Envoy获取配置并按配置转发请求。


无论是哪种模式,Envoy都负责转换配置,bRPC负责配置执行,因此,所有的代码都可以在Proxy和Proxyless模式下复用。

从业务场景上:
  • C++服务,可以联编bRPC,使用Proxyless模式;
  • 延迟不敏感的非C++服务(如Go/Python/PHP/Java等),可以无侵入使用Proxy模式;
  • 延迟敏感的非C++服务:什么,既然延迟敏感了,为啥不用C++开发?(这里的延迟敏感指的是一毫秒必争的这种敏感程度)


服务治理能力提升:
  • 在这个架构的基础上,我们丰富了bRPC的服务治理能力,基本覆盖了我们用到的所有Istio的能力,如权重路由、基于请求内容的路由、实例子集路由、流量复制、错误注入、异常实例驱逐、自定义错误码重试等。
  • 由于采用了bRPC架构,我们可以将先前公司内基于bRPC架构实现的优秀服务治理策略都集成到Service Mesh,包括延迟感知的负载均衡、基于错误码的动态实例调权、基于请求优先级的分级调度、基于分位值的动态超时和Backup request、重试比例熔断,这些能力是原生Envoy所缺失的,但是对于降低延迟、提升服务稳定性至关重要。
  • 得益于bRPC多协议架构,上述所有的Istio服务治理能力、公司内部优秀服务治理策略,都可以支持bRPC中的所有协议。相比之下,在Envoy中只有HTTP协议是一等公民,其它协议的治理能力都相当薄弱。另外由于bRPC支持协议自动嗅探,我们无须扩展Istio下发协议类型,所有协议都按HTTP方式配置即可。


Proxyless第三阶段

6.png

  • bRPC直接支持和Istio通信,获取xds配置并进行转换;
  • Proxyless模式,bRPC联编到业务进程中进行请求转发;
  • Proxy模式,bRPC作为一个独立的Sidecar进程进行请求转发。


这个架构彻底解决了Envoy这个历史包袱,让Service Mesh轻装上阵。

让我们回过头看一下前面说的Proxyless模式的两个缺点是怎么被解决的:
  • 开发效率:我们仍然只需要开发一套策略,适用于所有语言(C++用Proxyless,其它语言用Proxy),不需要为每个语言开发SDK。
  • 升级效率:由于百度内部C++有depend on stable机制(类似于Google的depend on HEAD),基础库有一个stable发布分支,而其它模块都依赖于基础库的stable分支。这样,当我们升级了bRPC中的Service Mesh功能时,只要将代码合并到stable分支,所有的上游模块都会自动更新,不需要再一个一个推动业务升级。至于非C++语言,使用Proxy方式来保证升级效率。


似乎这两个缺点在我们的场景里都没有了:)

Proxyless 模式的真正优势

Proxyless模式的真正优势是性能吗?一开始我们都是这么认为的,但是随着我们落地Proxyless模式的过程,我们才逐渐发现Proxyless模式的更多优势。

让我们来看看一些场景吧:

请求级别控制参数的场景

比如我们要在Service Mesh中实现一致性哈希负载均衡,原来没有Service Mesh的时候,用户通过RPC框架提供的一个set_request_code()方法来设置请求级别哈希码,RPC框架可以确保同一个request_code的请求被调度到同一个后端实例。

在Service Mesh中如何实现这个需求呢?如果是Proxy模式,负载均衡是在Sidecar做的,Sidecar需要获取到这个request_code。总不能让用户改代码吧?那么只能修改RPC框架,让框架把request_code传给Sidecar。如图所示:
7.png

怎么传给Sidecar呢?得在协议里的某个字段中传过去,比如HTTP协议可以在header中传过去,那么其它协议呢?每个协议都得找一个地方来传这个字段,每个协议都得实现一遍这个逻辑。

所以在Proxy模式下,传参这个事的实现成本=(RPC框架发送参数+Sidecar接收参数)*协议数量。

那么在Proxyless模式下呢?这个参数本来RPC框架就能拿到,所以传参的实现成本=0。

除了一致性哈希,类似的场景还有很多,比如请求级别超时控制、请求级别路由参数等,在这些场景下,Proxyless模式完胜

框架回调业务代码的场景

比如用户想实现一个自定义重试策略,RPC框架在访问一次后端服务之后,调用用户代码来判断是否需要重试。一个典型的流程如下所求:
8.png

现在业务想要接入Service Mesh,如果用Proxy模式,该如何实现用户自定义重试策略呢?考虑以下方案:
  • 方案一:将业务代码中的自定义重试策略实现到Sidecar代码里。问题是,业务的自定义重试策略可能不具有通用性,放在Sidecar这种通用基础设施的代码里显然不合适。
  • 方案二:Sidecar提供一种扩展机制,比如WASM,用户将自定义重试策略实现为WASM,以配置方式下发给Sidecar去执行。问题是,将原有代码改造成WASM的成本可能较高,这会明显降低业务接入Mesh的意愿。而且WASM的性能也不高。
  • 方案三:业务进程暴露一个服务接口,Sidecar调用这个接口来决定是否要重试。问题是,这样增加了业务进程和Sidecar之间的交互次数,增加了延迟,也增加了问题出现的概率。


在Proxy模式下,无论采用哪种方案,问题都很大。

在Proxyless模式下呢?由于该功能RPC框架本来就支持,成本=0。

除了自定义重试策略,类似的场景还很多,比如自定义负载均衡策略、自定义NamingService等,在这些场景下,Proxyless模式完胜。

动态多分片的场景

先解释一下什么叫动态多分片。

多分片在搜索、推荐类业务是一个典型场景,也就是说一个服务的一个实例并不加载全量的数据,只加载一个分片的数据。客户端调用此类服务时,需要将请求同时发送给不同分片的后端服务实例,获取到每个分片的结果再进行汇总处理。

动态多分片是指多个分片的服务组成一个分组,每次请求可以动态地选择一组分片。之所以需要多个分组,一种场景是因为数据发生扩容时,分片数会发生变化,为了保证流量平滑迁移,会同时存在不同分片数量的分组;另一种场景是由于业务需要,不同的分组加载了不同业务属性的数据,需要根据请求来动态确定调用哪个分组的服务。

如下图所示,分组A有2个分片,分组B有3个分片,而k1、k2则代表了不同的业务属性。
9.png

在动态多分片场景下,业务代码和RPC框架的一个简化的交互流程如下:
10.png

之所以需要分两个阶段调用RPC框架,是因为业务需要根据最终选定的分组和分片数量来拼装业务的请求。

现在业务想要接入Service Mesh,让我们看看在Proxy模式下支持动态多分片的方案:
  • 方案一:……(此处省略300字),不行,这个方案性能太差
  • 方案二:……(此处省略500字),不行,这个方案成本太高
  • 方案三:抱歉,我想不出其它方案了……


现在,让我们看看Proxyless模式下支持动态多分片的方案:
  • 沿用原来的二阶段流程,在第一阶段,执行Service Mesh中的各种路由策略,确定最终的分组,以及决策是否做流量复制、流量复制目的分组。
  • 第二阶段,先按原来RPC框架逻辑进行多分片并行调用,具体到单个分片上,使用Service Mesh中的负载均衡、超时重试等策略。如需流量复制,则异步执行流量复制到另一个分组。


虽然这个方案也有一定复杂性,但实现成本也不是很高,比Proxy模式的实现成本低多了。

所以,在动态多分片场景,Proxyless模式完胜。

服务可观测场景

服务可观测是Service Mesh的重点场景,我们来看一下这个场景下的一些需求吧:
  1. 用户想实现分布式trace,需要将服务的入口流量和出口流量建立关联,比如在处理trace_id=x,span_id=y的入口流量过程中,发出的出口流量,其trace_id=x,parent_span_id=y。Sidecar:这事我干不了,得靠RPC框架。
  2. 用户想把业务日志和RPC日志进行串联,比如在处理trace_id=x的请求过程中,打印的业务日志中都包含trace_id=x字段,这样可以进行汇聚计算。Sidecar:这事我干不了,得靠RPC框架和日志库打通。
  3. 用户想监控请求处理过程的一些细化耗时,比如排队时间、序列化反序列化时间。Sidecar:这事我干不了,得靠RPC框架。
  4. 用户想实现流量染色,比如将入口请求打上k=v标签,然后由该请求触发的整个调用链的请求都会带上k=v标签。Sidecar:这事我干不了,得靠RPC框架。
  5. 用户想针对业务代码的耗时、cpu使用进行分析,找出瓶颈。Sidecar:这事我干不了……


为什么会出现这些情况呢?实现服务可观测的最好方法就是深入服务内部,对于Sidecar来说,服务就是个黑盒,当然不好实现了。

所以,对于服务可观测场景,Proxyless模式完胜。

重新思考Service Mesh的本质

Service Mesh的本质是Sidecar吗?

诚然,Sidecar是Service Mesh的一大卖点,比如方便支持多语言、和应用解耦,这可以让用户更方便地接入Service Mesh。但是我们更应该关注Service Mesh给用户带来了什么价值,如果用户用了Service Mesh没有收益,即使接入成本为0,用户也会不接入的。

从用户角度看,Service Mesh带来的是以下价值:
  • 服务可用性提升:比如各种超时重试、限流熔断策略带来的提升
  • 延迟降低:比如延迟感知的负载均衡策略可以实现服务整体延迟的降低
  • 服务可观测:比如链路黄金指标、调用链追踪能力可以提升服务可观测性
  • 流量调度灵活性:比如各种路由策略可以实现灰度发布、跨机房切流等
  • 安全性:比如TLS、各种认证鉴权机制可以提升服务间调用的安全性
  • 管理便利性:上述策略都可以通过控制平面集中管理,动态下发生效


因此我认为,Service Mesh就是能让服务间通信更可靠、更快、更透明、更灵活、更安全、更便于管理的基础设施。

至于这个基础设施是Proxyless还是Proxy模式,其实不是很重要。从实际业务场景出发,哪种模式更容易满足业务需求,就用哪种方案。两种模式各有自己的优势场景,结合起来可以提供更好的服务。

Service Mesh的本质是配置中心吗?

有一种观点认为,Proxyless模式不就是RPC框架+配置中心吗?Proxy模式不就是一个7层代理+配置中心吗?这玩意很多年前就有了。

但是,Service Mesh的控制平面,不能简单看作配置中心。配置中心仅仅是简单地将配置文件或者配置项进行下发,并不感知配置的实际含义。

以Istio为代表的控制平面,实际上是定义了Service Mesh的能力标准,比如各种路由、负载均衡策略等。如果只是做了一个简单的RPC框架,暴露了几个超时参数到配置中心来控制,那不叫Service Mesh,因为没有实现Service Mesh的标准能力。即使RPC框架把Service Mesh的标准能力都实现了,但是没有统一的协议和配置格式,不同的框架的配置方式五花八门,通信协议互相割裂,那也不能算Service Mesh。

所以说,Service Mesh是一组服务间通信的能力标准,实现了这些标准,就可以称之为Service Mesh。

Service Mesh未来展望

从目前趋势看来,Istio仍然会作为Service Mesh控制平面的首选。尽管Istio的CRD对用户也不是很友好,但是Istio定义的Service Mesh标准体系目前仍是最完整的,也获得了最广泛的数据平面的支持。

而数据平面,则可能出现百花齐放的场景,毕竟业务场景非常多样,有的看重灵活性,有的看重性能,有的看重安全,那么就能催生不同的数据平面实现方案。Envoy仍然会作为Proxy模式的主流选择,但各RPC框架也不甘于只做瘦客户端,将会继续发展自己的Proxyless方案,让Service Mesh能落地到更多的业务场景之中。
11.png

原文链接:https://mp.weixin.qq.com/s/G8vmlJyaimux_K-548kFbA

0 个评论

要回复文章请先登录注册