最近一段时间,proxyless service mesh也逐步进入了大家的视野,比如gRpc、Dubbo都引入了proxyless service mesh方案。
那什么是proxyless service mesh呢?它又有什么价值呢?
我们结合一个具体case 的发展史了解下 Proxyless 的进化史。
一个典型的service mesh长这样:
每个App的Pod里面,有一个独立的Sidecar进程,App之间的通信都通过Sidecar进程转发。
整个架构中,有一个全局的控制面,常见的是Istio,下发配置到每个Sidecar,控制具体请求的转发策略。
而一个典型的Proxyless Service Mesh架构长这样:
由集成到App进程的rpc框架负责服务之间的通信。
控制面下发配置到每个rpc框架,rpc框架按照配置进行具体的请求转发。
比如 gRpc和Istio之间的通信是由Istio Agent来代理的。
通过以上两个典型架构对比,可以发现proxyless和proxy模式的区别。
架构是演进出来的。
目前越来越多的应用架构开始接受Proxy模式了,尽管看起来Proxy模式中间带来了额外的资源开销,但经过多年的优化,性能得到了很大的提升。
但如果一个非常复杂的拓扑,哪怕每次转发只消耗0.2ms的延迟,所有边上都增加一点点延迟,最终整体产生的延迟也是很大的,对系统的稳定性和性能产生影响。
于是架构开始了从Proxy模式向Proxyless模式演进。
Envoy从Istio拿到流量转发配置,并翻译成bRpc可以识别的配置。
bRpc通过http接口从Envoy中拿到流量转发配置,并按照这份流量配置去调用其他服务。
这种模式转变带来了一些好处。
首先,业务接入Mesh,不会带来延迟(因为原来的微服务架构就是基于bRpc实现的通信),也不会增加明显的资源开销(Envoy带了的开销可以忽略不计)。
其次,业务既可以享受Mesh的便利性,比如控制面的集中配置管理、动态下发配置、无需代码的修改和配置的修改上线重启,极大提升了服务治理的效率。
你会发现,bRpc可以用到哪些服务治理能力,决定因素在于Envoy和bRpc之间翻译出策略的多少。
也就是说,大部分Istio配置的能力其实是不能被bRpc直接使用的。
那随着服务治理能力诉求的越来越多,不仅需要在Envoy中迭代,还需要在bRpc中迭代,由于两种技术栈策略的不同,导致很多代码难以复用,就会进入重复开发的老路上来。
常见的服务治理能力包括:权重路由、基于请求内容的路由、实例子集路由、流量复制、错误注入、异常实例驱逐、自定义错误码重试等。
于是,mesh架构进化到Proxyless/Proxy统一的mesh架构上来。
左边是Proxyless模式,Envoy负责把xds配置转换成bRpc能识别的配置,bRpc宿主在业务进程,从Envoy获取配置,并依照配置做流量转发。
右边是Proxy模式,Envoy负责把xds配置转换成bRpc能识别的配置,bRpc宿主在Envoy进程,从Envoy获取配置,并按配置实现流量的转发。
无论哪种模式,Envoy都负责转换配置,bRpc负责执行配置转发,这样代码可以在Proxy和Proxyless两种模式下复用。
通过Proxy和Proxyless两种模式的复用,可以实现不同语言的支持。
比如C++服务,可以与bRpc联编,实现Proxyless模式。
其他的非C++服务,比如Python、Php、Java等,可以无侵入的方式使用Proxy模式。
由于在 Envoy中Http协议是一等公民,其他协议的治理能力都相当薄弱,而过去公司大规模使用rpc通信,已经围绕于rpc沉淀了很多服务治理能力,所以考虑下掉Envoy这一层,让rpc与Istio直接通信。
bRpc直接和Istio通信,获取到xds配置后进行转换。
Proxyless模式下,bRpc宿主在业务进程中,实现对请求的转发。
Proxy模式下,bRpc作为一个独立的Sidecar进程,对请求进行转发。
这样整个架构彻底甩掉了Envoy,让ServiceMesh可以轻装上阵。
这样,仍然只需要开发一套策略,通过Proxy模式和Proxyless模式适用到不同的开发语言,无需为每个语言开发对应的SDK(配置在Istio,bRpc只需要做规则的翻译和请求的转发,很是轻量级)。
至此,相信你已经了解了什么是Proxyless ServiceMesh了,也知道为什么在Sdk Agent和ServiceMesh之外会衍生出Proxyless模式了。
那Proxyless带来的优势是什么呢?
看几个例子。
比如要实现一致性哈希负载均衡。
在Rpc时代,可以在Rpc框架中的协议头追加一个请求码:requst_code,放置请求哈希码。这样Rpc框架可以根据这个request_code将请求调度到同一个后端。
在ServiceMesh模式下,负载均衡是在Sidecar中做的,Sidecar想要获取到这个请求的request_code,就需要修改Rpc框架,让框架从业务进程请求中截取request_code并透传给Sidecar。
不同协议传递request_code方式不一样,比如http协议可以在header传递,rpc协议还需要修改请求头协议。
这样每个协议都需要修改协议逻辑代码。
所以在Proxy模式下,传参的实现成本 = (Rpc框架发送参数 + Sidecar接收参数) * 协议数量。
而Proxyless模式下(就是bRpc和Istio直接通信的方式,无需Envoy这种Sidecar转发),参数本来在Rpc框架能拿到的,所以传参的成本趋近于0。
类似其他的服务治理场景,比如请求级别超时控制、请求级别路由参数等,Proxyless模式都完胜。
还有一种场景,就是基于rpc请求的结果,回调业务代码实现不同的干预策略。
比如用户想自定义重试策略,rpc框架在访问一次后端服务后,调用用户代码逻辑,判断是否需要重试。
大概长这个样子。
在Proxy模式下,会如何实现这个重试回调方案呢?
方案一:将业务代码中自定义重试策略放到Sidecar中,但每个业务的重试策略都不具备通用性,放在Sidecar里,这种通用的基础设施显然不合适。
方案二:Sidecar提供一种扩展机制,比如WASM,用户将自定义重试策略实现为WASM,以配置方式下发给Sidecar执行。但将原有代码改造成WASM的成本比较高,这会明显降低业务接入Mesh的意愿。
方案三:业务进程暴露一个服务接口,Sidecar调用这个接口,实现对是否重试的判断。这样是非常WatchDog的思想,不仅增加了业务进程和Sidecar之间的交互次数,增加了延迟,也增加了问题出现的概率。
总之,在Proxy模式下,以上几种方法都看起来是非常笨重的方法,把一件简单的事情复杂化了。
而在Proxyless模式下,由于回调重试能力,本身已经集成在了Rpc框架里了,所以对于这个场景需求的实现成本是0。
类似的自定义负载均衡策略、自定义NamingService策略等,Proxyless都完胜。
我们再看一个动态分片场景。
在搜索、推荐类业务中,多分片是很典型的一个场景,因为一个服务实例不足以承载全量数据,只能通过分片方式,每个实例承载一部分数据。
客户端在查询此类服务时,需要根据请求信息,将请求路由到持有不同分片数据的实例上,获取到分片数据后,再聚合推送到客户端。
有时由于请求的潮汐情况,分片会发生扩缩容,分片数就会发生变化,为了保证流量的平滑迁移,就会存在不同分片数量的分组。
由于不同数据属性特点不同,数据量级不同,所以每个数据分片之后分片数量也不同。
以上两种情况,都需要对请求做动态分组,以确定调用哪个分组的服务。
在动态分片场景下,rpc是以下这种方式交互的。
因为业务需要根据最终选定的分组和分片数拼接业务请求,所以rpc交互分为两个阶段。
如果是Proxy模式,基本上没有好的方法,因为需要在Sidecar做大量的策略定制,但这些又不通用,导致很难实现。
而Proxyless模式中,第二阶段是可以沿用的(就是获得拼接请求参数和请求结果的过程),第一阶段获取分组、分片策略是可以通过Istio做配置下发到rRpc的。
分布式服务可观测性实现。
在分布式场景下,通过Trace建立服务入口流量和出口流量之间的串联。
这种服务可观测性最好的方法就是深入服务内部,甚至代码逻辑调用时延,需要很强的业务代码入侵。
但如果有了sidecar,这种方式做起来反而不那么容易了,因为sidecar对业务代码来说是黑盒,很难侵入的定制埋点,他的粒度是粗的,是请求的request、response维度的,很难做到代码片段维度的。
之前的文章提到过,ServiceMesh是一种服务治理的理想态,但现在的ServiceMesh解决方案还没有达到理想态。
首先,ServiceMesh的本质肯定不是Sidecar,Sidecar确实解决了很多服务治理的痛点,比如支持多语言、应用解耦,这样可以让遗留系统快速变为一个ServiceMesh架构。
但需要考虑 ServiceMesh给用户带来了什么价值。
很简单,如果用户自己觉得没有价值,即使接入成本是0,大概率也不会接入。
那用户关注什么价值呢?
因此,ServiceMesh的价值,是让服务间通信更可靠、更快、更安全、更透明、更灵活的管理基础设施。
那具体是Proxy还是Proxyless模式不重要,需要从实际业务场景出发,满足业务需求。
再聊一下对于 ServiceMesh 认知上的一些误区。
ServiceMesh的本质是配置中心吗?
ServiceMesh概念出来之后,很明显的一点变化就是引入了配置中心,通过这种方式弱化了Sidecar的复杂度,降低了服务接入Mesh的成本。
很多人认为Proxyless模式不就是Rpc框架+配置中心吗?Proxy模式不就是7层代理+配置中心吗?
但ServiceMesh中的控制面不能简单的看做是配置中心,配置中心仅仅做到配置的下发,而不感知配置的实际含义。
是不是很难理解?
比如Istio代表的控制面,实际上是定义了ServiceMesh的能力标准,比如各种负载均衡策略。
即使Rpc的配置中心把所有ServiceMesh的控制面能力都实现了,但没有统一协议和配置格式的话,不同框架的配置方式就会变得五花八门,通信协议相互割裂,也就算不上ServiceMesh了。
所以ServiceMesh背后是一组服务间通信的能力标准,这些标准实现了,才称得上是ServiceMesh。
当前ServiceMesh架构,最初Envoy+Istio的组合是首选。
但随着大家的觉醒,或者发现自己原有的服务治理问题不能低成本迁移到Mesh上,或者Mesh不能解决自己所有的问题时,ServiceMesh 的发展就有了新的变化。
控制面Istio还是首选,但在数据面则是百花齐放,这主要是由于不同业务场景特点和过去的基建特点决定的。
有的业务重灵活性、有的重性能、有的重可靠性、有的重安全性,也就有了不同的数据面方案。
同时一些成熟的Rpc框架,也不甘于做一个瘦客户端,还需要继续发扬Proxyless精神,变为一个Proxyless ServiceMesh 方案,让自己的价值体现在更多的服务治理场景中。