本次分享,我将和大家分享以下三部分内容:服务网格简介、百度在服务网格的最佳实践、对服务网格的未来规划与展望。
从传统单体应用到微服务架构转变去落地时,基于 SDK 和服务框架这种微服务治理体系,这种传统的服务治理能力完全是由 SDK 以及服务框架来实现的。这其中,包含了很多服务治理、服务发现、服务路由、服务重试、熔断限流等微服务相关的一些能力。业务程序重度依赖 SDK 及其服务框架,并和语言、业务绑定在一起。
基于这样一个服务框架的服务治理体系,在由传统的单体到微服务的转变之后,将面临着很多种挑战:
具体来说,在语言绑定层面,传统的微服务治理体系可能要跟具体的语言绑定,因为它跟具体的业务逻辑是要绑定在一起的。
从整体来看,在多语言层面很难做到一个统一的技术,因为语言本身有各自的特性,还有各个语言的能力,都有不太一样的情况。
此外,采用传统的 SDK,在向前去演进的时候,很难去推动业务去做升级,SDK 和业务严重绑定了,这样会造成演进困难。
基于这些问题,我们将非业务逻辑相关的微服务治理,放在另外一个透明代理,即一个轻量级的网格代理,我们称它为 sidecar。在 sidecar 层面,我们做了很多的服务发现、负载均衡、服务路由等等。
这样服务的概念更加纯粹,不同的服务和微服务进来,只需要关心自己的业务逻辑实现就可以了,完全把这种非业务的实现逻辑,放在 sidecar 层面去做。
这种典型场景具备四个特点:它本身和业务无关,真正做到了从业务中剥离出来;由于它和业务在两个独立的进程里,所以不存在语言绑定的情况;它和业务逻辑没有强烈的耦合,独立演进,包括独立的升级和维护;透明升级的,就是我们基于 sidecar 这样一个能力的升级是不再依托于业务的升级,因为本身没有绑定,这是我们基于透明代理的服务治理体系。
这种基于透明代理的服务治理体系,可以简单称它为服务网格。我们先看一下其典型代表 Istio,Istio 是一个在服务网格领域深受大家喜爱,也深受开发者追捧的框架架构。
我们来看一下它的典型架构。
Service A 和 Service B 是我们的两个业务应用,Envoy Proxy 就是一个透明代理,它本身对业务是透明的。我们可以看到,有一个控制平面 Istio control plane,控制平面可以简单理解为,它会向着我们所有的 sidecar 去推送整体的一些配置、数据等等。通过这样一个典型的 Istio 架构,来引出我们这个服务网格的概念。
从整体视角上来看,所有的 sidecar 都是在一个网状结构里进行透明的流量转发和治理,组成了一个扁平的网状,这种由透明代理组成的一个网状结构,我们可以称它为服务网格,服务网格本身是基于透明代理去实现的。
接下来,我和大家分享下百度服务网格最佳实践,主要介绍服务网格在百度的整体实践,包括我们怎么去落地服务网格,以及带来的业务收益。
百度在云原生领域的发展历程还是比较早的,我们在 2012 年到 2013 年,内部就有很多这种容器平台,比如 Matrix。在 2013 到 2015 年,我们内部有很多业务线逐步地去落地和推广云原生理念。在 2013 年到 2017 年,我们有大量的品牌业务、广告业务、营销业务开始使用了微服务引擎,其中比较典型的是我们的 BFE,以及百度 RPC(brpc)。
2015 年到 2018 年,我们整体的业务线,包括文库、音乐、人工智能等等,也开始在开源的 Kubernetes 里去做一些实践,大概有几十万的容器使用了 Kubernetes 技术。在 2018 到 2019 年,我们整体做了很多的微服务化、应用平台、PaaS 平台,其中也包含了我们对 Springcloud 和 Service Mesh(服务网格)的探索。
2019 到 2022 年,我们在百度的地图和信息流里进行了服务网格的大规模落地,在服务网格这部分做了很多事情,产生了很大的价值收益,后面我们会逐个去介绍。
在业务线上,我们有很多核心业务线,比如百度 APP、信息流、地图,还有百度小程序。百度也有很多开发语言,比如 C++、Golang、PHP、Java 等等,形成了一个多语言的场景。在开发框架里,基于传统的微服务框架,我们也有很多内部实现的积累,比较有名的是 BRPC 框架,还有 Golang 的 GDP,PHP 的 ODP 都有一些实践,并且跟业界开源的 SpringCloud 也有一些落地实践。
在通讯协议方面,因为百度很多核心商业对性能有很高的要求,我们在大量的私有协议里有很多扩展,其中有大量的 L4 层协议。通过这样一个方式,百度的很多业务线已经完成了微服务的改造,它可能还是停留在传统的服务框架和 SDK 这样一个模式,形成了一个多开发语言、多框架和多通讯协议的复杂的异构系统。
基于传统的服务框架的模式,本身已经不能够再满足像百度这样体量的商业化系统的演进过程,同时也不再具备满足更高的服务治理的要求。基于这样一个背景,我们探索了服务网格的落地。
我们在落地服务网格时,有很多挑战和困难。
第一个比较典型的困难就是应用的类型比较多,因为日积月累,有大量商业化的服务框架应用涉及到服务网格的迁移。第二,像百度这种商业化体系,本身对时延等要求比较高。第三,我们对服务治理的需求比较多,在用户侧,当一个新的服务网格技术去落地时,用户也是比较希望这个全新的技术能够解决他在场景化的一些问题。最后就是技术理念的问题,因为服务网格是一个比较新的技术概念和理念,它涉及到的技术领域比较复杂,本身理念也比较新,开发者还是有一些接受的过程。
百度已经形成了很多微服务技术框架和架构,我们要去做一个新技术的推广,包括服务网格的落地,首先要去倾听业务的诉求。基于这些业务诉求,选择一些比较典型的核心业务,然后逐步向服务网格这样一个全新的微服务架构去转型。最后我们实现了比较大规模的业务落地,包括我们在大规模场景下一些稳定性相关的保障。
接下来,我们看一下百度服务网格大规模落地的步骤。
首先,我们需要解决流量接入的问题,流量怎么劫持到 sidecar,去做流量拦截和流量的治理。第二,解决对自有协议的兼容问题。百度有很多私有协议,要在 sidecar 层面去支撑我们所有的私有协议,最后要对老框架、老协议去做一些兼容。
解决完接入问题之后,再来看性能部分。因为我们是一个大规模的商业化场景,我们要重点解决刚才提到的 sidecar 的性能优化,还有控制面的性能优化,以及我们在整体代理架构的简化。
最后,业务接入服务网格之后,我们要来看一下这个服务网格最终有什么样的业务收益。我们在高级能力的策略还是比较丰富的,能够有效的保证业务的稳定性,同时在一些网格的特性,包括故障注入、容量探测、自动止损等方面也做了增强,同时对服务的可观测性也做了一些弥补和增强。
代理架构简化
接下来看下在代理架构简化这一层面,我们主要做了哪些事情。
先来看一下社区的方案。社区的方案是一个典型的双跳方案,每个业务程序都有一个边车,所有的流量都要经过两次边车处理,它有一个两倍的延时,包括两倍的资源开销。
我们对此做了一定的优化。我们提供了 client-side 这样一个模式,和社区的方案对比来看,业务之间在通讯的时候,业务本身不再依托于边车的流量管理,也就是说,我们相当于仅增加一倍的延迟,而不是社区的两倍延迟。同时在资源效能开销方面又降低了很多,这是因为我们发现大部分的服务治理的能力是可以在客户端来实现的。
控制面优化
在控制面优化上,我们整体是基于数据面加控制面的模式来做的。
第一步,利用框架的一些能力,采集服务的调用关系,把数据同步给我们的控制平面,控制平面会把服务所需的数据向边车去下发,达到一个按需下发的能力。同时,我们的边车也是主动地向对应的注册中心,查询获取到它的服务治理的能力。
在优化控制面的性能之后,我们也在服务网格的性能方面做了一些优化,包括控制面和数据面做了一些融合。我们引入了 BRPC 做了很多这种优化,因为在百度内部有很多 BRPC 的场景,同时我们也结合了 BRPC 和 Envoy 的一些优势,基于 Envoy 灵活的 filter 机制,做了丰富的策略,支持动态的 XDS 机制,同时也结合了 BRPC 的高性能的线程技术、Buffer 库、优秀的 IO 机制,最终达到的效果。
最终在 CPU 和平均延时这两个比较核心的指标层面,有很强的性能提升。
我们在探寻的时候发现,传统的业务逻辑加 SDK 的微服务架构,和应用加 sidecar 这样一个全新的 Service Mesh 架构,是有一个过渡态的。在这个过渡态里,我们需要异构应用的互访,也就是说,传统的微服务应用能够和我们这个 Service Mesh 的应用去做互通,Service Mesh 里面的应用程序,同样要去和这种传统的异构程序去互通,我们称它为过渡态。这是我们典型的一个应用场景,也是业务线比较关心的一个问题。
另外我们做了一个 Fallback 机制支持流量的一键回退。刚才提到,在典型的 Service Mesh 架构里,这种代理模式是比较好理解的,就是所有的请求都会经过 sidecar。这是一个典型的 Service Mesh 架构,我们可以称它为一个典型的代理模式。
另外一个方式,我们称它为直连模式,就是这个请求不再经过 sidecar 拦截,直接写入业务进程,和业务进程直接去通信,这个就是我们典型的这种业务之间通信方式。
业务在向这种典型的 Service Mesh 架构去迁移或者转变的时候,有个担心点是,传统的业务可能采用直连模式,换成这种代理模式之后,因为所有的流量都会经过 sidecar 拦截,一方面会增加时延,以及对稳定性的诉求;另外一方面,在极端的情况下,如果 sidecar 数据平面产生了一些故障,怎么快速帮业务止损。
我们基于定制化的一些能力,做了 Fallback 机制。Fallback 机制提供了一个产品化的能力,能够自由地在代理模式和直联模式间去灵活切换。这样,一个典型的应用场景就是在故障或者是业务止损方面,如果传统的 Service Mesh 架构发生了一些故障,我们能够快速地把流量切回到直联模式,这是我们在稳定性方面的一个保障,也是给业务信心的增强。
在针对一些传统的 Java 的微服务应用程序考量时,会发现一些传统的微服务应用已经有一些现有的微服务治理体系,那么,我们怎么去让它做这种服务网格的迁移?
我们采用了一种无侵入的实现方式,利用了 JavaAgent 这样一个技术,首先它能够做到对业务无侵入,因为它本身是基于字节码层面的一些修改。基于这些能力,我们实现了它在服务网格里面的服务治理的能力。
第一个比较典型的场景就是我们能够动态地开启和关闭传统微服务治理框架的治理能力,因为在传统的框架里面,它本身已经有了微服务的能力,在接入了服务网格之后,我们希望它和服务网格的基本能力能够做一个取舍,而不是一个相互叠加。
第二,可能熟悉服务网格的同学都对这点深有体会,虽然服务网格是由 sidecar 来代理流量的,但它本身需要在业务进行微服务监控的时候,能够主动地把 sidecar 产生的对应的 trace 信息做一些透穿。这部分,我们也是利用探针的能力,去实现了微服务调用关系的 trace 信息的透穿。
第三,我们也做了一些增强。我们发现,Istio 这样全量推送的架构里,可能会有一些性能的损失,我们会主动探测到应用程序里边需要访问的微服务调用关系,基于这个调用关系,我们把这些元数据做一些收集,再同步到我们的控制面,再到我们的 sidecar 层面去生效,这样保证我们在大规模场景下的性能优势。
接下来介绍下我们内部在提升 Service Mesh 性能方面的一些实践,典型实践就是 Service Mesh 的单跳方案。
Service Mesh 单跳方案主要是解决 sidecar 的非必要拦截。社区的双跳方案,业务请求先到某一个服务 A,服务 A 的 Sidecar 进行一个拦截,这个 sidecar 会把请求转发给服务 A ,同时服务 A 要向服务 B 发起请求,对外的请求也要过一次 sidecar。同样的,服务 B 的 sidecar 也要把这个请求再转发到服务 B,再出去的时候,再过 sidecar,我们看到这样的话,每次请求进出服务 A 和服务 B 都会产生这个 sidecar 的拦截。
我们发现,大多数服务治理的策略往往可以在服务 A,也就是 client 端来实现。基于此,我们做了一个单跳的方案。我们在入向和出向的时候,只有在第一次请求拿到服务 A,往出走的时候,我们再过服务 A 的 sidecar,同时在访问服务 B 的时候,我们这个请求是不需要再过服务 B 的 sidecar。基于这样的一个方案,我们节省了一部分 sidecar 的资源开销,还有时延的开销。
接下来,我们再来看一下一些内部的典型大规模应用场景。
在按需下发方面,我们的 XDS 只下发必需的数据。我们在控制面和数据面之间交互的时候,控制面因为往往不知道数据面需要哪些服务,它是一个全量下发的逻辑。举一个典型的例子,如果 Kubernetes 集群里有十个服务,我们的控制面 list watch 十个服务之后,就会把这十个服务的信息全量推给 sidecar,sidecar 接管业务可能只访问这十个服务里面的一两个,这样的话,就会造成了数据冗余。
在这样一个场景里,我们做了一些事情,就是 XDS 在推送的时候,我们只下发必要的数据。这样的话,我们在十条数据里面,只精准地把一条数据或者两条 sidecar 所需要的数据推下去,sidecar 就能够主动地把数据加到自己执行的内存里面去运行,显著减小 Envoy 的性能损耗。
另外,我们还有个架构升级,做了 XDS 的优化,EDS 从推送模式改为主动模式。数据面和控制面之间通讯的时候,完全是由控制面向数据面去进行数据的推送,但是我们发现在量比较大的时候,是有一定的性能损失和时延过长问题,因此在这部分,我们做了一些性能优化。
我们还对接了很多注册中心,包括 Consul、Eureka、Zookeeper,存量业务可能使用了多种注册中心,在向服务网格去迁移的时候,我们需要对接这些注册中心。
典型的 Service Mesh 接入过程中,本身是需要透穿 sidecar 所传递的 Trace 信息的,因为我们所有的进出流量都被 sidecar 所拦截,并且产生一个 Span ID 和一个 Trace ID。很多业务有些排斥,或者存在抵抗心理,因为他不太清楚业务为什么接入网格,本身是一个无侵入的方案,为什么要做这种 Trace 相关信息的透穿。
我们针对大量的存量 Java 业务,提供了一种 Java 无感知的接入方式,也是整体的基于 Java agent 技术来做的。所有的请求到达业务之后,利用 JavaAgent 做一个 Trace 信息的存储,业务往外发请求的时候,我们会把这个存储里面的元数据信息拿到,也就是刚才提到的这个 Trace ID 和 Span ID,达到业务无侵入、无感知的接入方式。
另外,因为传统的业务可能它本身也有自己的监控体系,可能还要监控自己这次请求经过了哪些方法、哪些函数的处理过程。因为在 Sidecar 或者是服务网格这样一个典型的架构里,所有的监控数据都是在 Sidecar 这一侧,也就是在数据面一侧去产生的,它对于内部业务程序的执行过程,确实不具备优势。而对于业务的内部的一些执行过程,往往可能还是业务比较关心的,比如哪个方法执行地比较慢,哪个方法出错了。
所以,在对一些业务监控时,往往通过业务引入对应的监控 SDK,然后去做打点,然后再汇报给监控系统。其实我们对于 Java 业务,也是利用 JavaAgent 的这种采集技术,把业务里面基于方法级别的具体实现过程,做了采集和汇报,同时,和 Sidecar 产生的 Trace 信息以及 Span 信息做一个连接。
这样的话,其实我们接入到网格之后的 Trace 体系跟业务原生的这种已有的 Trace 体系,基本上是打平的,从业务的真正的请求到 Sidecar,然后到业务真正的内部执行过程,然后到业务再向另外的服务器发起请求的时候,然后再经过 Sidecar,以及这样一个 Trace 的过程。
同时,我们也支持了很多组件,比如 Spring、Kafka、Redis 这样一些体系。我们把数据采集到之后,其实是要做一些存储和对接的,因为现在整体业界对于监控系统的支持还是比较多的,比较有名的包括 Jaeger,或者 Skywalking 等等。
本身基于 Service Mesh 这样一些架构,其实产生了很多数据,服务网格和 Service Mesh 把这些数据采集到之后,会把这些数据向外去发送。我们刚才提到外面的监控系统有很多,每一种监控系统可能都是不同的数据结构,所以我们在这块利用了 OpenTelemetry,能够把所有的基于业务和 Sidecar 产生的 Trace 信息和 Span 信息,向所有的 collector 去发送。
我们可以认为 collector 是一个标准,我们的 Sidecar 和业务产生的数据只满足这样一个标准就可以了,然后我们把所有的数据发给 OpenTelemetry。OpenTelemetry 更像一个中间代理,由它来屏蔽数据存储和监控体系的数据的差异性。
我们刚才提到了所有的数据都和 OpenTelemetr 做了一个数据的约束,由 OpenTelemetry 把数据进行一些转换和转变,转换成不同的这种监控体系所需要的这种数据格式,把这个数据再发送到不同的监控体系。避免我们后期频繁地变化这个监控体系之后,可能会对我们的数据格式产生一定影响。
接下来分享下百度服务网格的落地规模和收益。我们在核心的业务线包含手机百度、Feed、百度地图、小程序、好看视频,都有非常大规模的落地,也是我们比较典型的一个商业化场景。
收益方面,第一个比较典型的收益就是我们在跨语言层面真正地做到了统一的服务治理,因为服务网格是一个典型的基础服务治理能力和业务逻辑解绑的架构,真正地能够实现跨语言的统一服务治理。
另外,在服务的可用性方面,也做了很多的增强和提升,同时与典型的服务治理周期相比,我们实现由月级到分钟级别大幅度的降低,业务对于我们这种服务治理的有非常大的体验感的增强。最后,我们在基础设施层面能够统一技术栈,这是我们从业务侧和技术架构侧去归纳的收益。
规模方面,我们的实例有 10 万多个,真正地落地了我们的服务网格全新微服务架构。在处理流量方面,我们在核心的商业体系和商业系统有大量的线上流量都经过了服务网格,我们的服务网格也经受住了百亿级别的流量考验。
未来,我们还有很多增强点去做,比较典型的就是 eBPF 增强 Service Mesh。我们刚才提到在社区的方案里,有基于 iptables 的一些实现机制。我们发现其实基于 iptables 的话,它会带来一些性能损耗,包括频繁地在用户态和内核态去做这种切换,是有一些性能的损失和延迟的。
所以,我们未来在 eBPF 层面需要做很多增强。我们希望在内核态能够尽快地加速流量的转发,不再经过多次 Socket 的机制,尽快地把这流量传达给数据平面,从而节省服务到数据平面的延迟,给用户的感受也是更好的。
此前提到,社区是一个全量推送的方案。举个例子,我们有十个服务,也就是 Kubernetes 有十个 Kubernetes Service,那我们这个控制平面很有可能把这十个服务去全量推送,这样带来的一个问题就是 Envoy 的数据配置项,还有配置的数据都是比较冗余的。
基于这样的方式,社区提供了一种手动配置的方式,主要是基于 Sidecar CRD 的模式,让用户手动地在 Sidecar 层面来约束服务的调用关系。这样其实对用户的接入体验不是很好,因为往往用户可能不太清楚自己这个服务的调用关系。
因此,我们要做自动地按需下发。避免用户手动地去配置 Sidecar CRD,通过一些其他的流量管理方式来收集业务的调用关系,让用户真正地体会到业务的无感介入。让我们通过一些技术的手段,收集到这个服务之间这种调用关系,然后快速地提升业务的接入体验。这样,我们能够减少配置的非必要数据,提升服务网格的整体性能。
另外,百度其实在云原生服务网格做了很多的积累。在内部,包括核心的商业性也做了很多的生产级别的服务网格论证,我们准备去赋能业界,提供百度的服务网格产品。
可以看到,服务网格本身是有很多能力的,比如服务治理、服务监控,还有可靠的安全传输。真正去落地的时候,可能还要涉及到 Sidecar 性能的提升,还有运维侧,以及良好的可扩展性,包括我们在传统虚机的一些接入,这些都是我们都是要考量的。
另外,在可视化方面其实也有一些诉求的。业务在接入的时候,可能会有一些场景化的诉求,比如真正地接入了 mesh 它能带来的价值或收益,或者是能在什么样的一些场景里面带来业务的落地。
在周边生态,除了有 Istio、Envoy、Jaeger、OpenTelemetry、Prometheus,可以看到它是非常庞大的一个云原生体系,基于这样一个云原生技术栈,我们发现一家公司要去落地的话,挑战也是比较大的。百度可以提供这种开箱即用的服务和产品,能够让业界的客户快速地使用,同时,针对一些业界的使用场景来提供用户友好的控制台,并对开发者友好。
经过了百度生产级别的验证,以及核心级别的流量的考证,我们也相信这个产品给广大开发者能够带来真真正正的业务收益,这也是我们百度在服务网格和云原生的一个多年的沉淀,也欢迎大家来持续关注。