字节跳动云原生微服务多运行时架构实践

技术

picture.image

picture.image

作者 | 成国柱

“字节跳动多运行时架构的起源”

在过去十年的发展历程中,字节跳动的业务逻辑复杂性不断提升、业务规模得到了迅速增长、合作团队也在陆续增加,驱动着字节跳动微服务架构必须随着业务需求的变化开展演进。

字节服务架构的演进主要历经了两条发展路线:一是横向拆分,即把单体架构拆分为微服务架构;二是纵向下沉,即在云原生出现之后,将微服务架构的通用能力下沉,将其演变为云原生微服务架构。

那么云原生微服务架构存在哪些优点和缺点呢?

picture.image

如上图所示,在部署云原生能力方面,字节跳动服务框架团队基于计算层(Kubernetes + Docker)提供了统一服务治理、服务注册/发现、认证/鉴权等云原生能力;在业务应用领域,业务还依赖远程业务服务、通用服务和通用 SDK。

根据这一分层模型,相对应地,字节云原生微服务架构具有以下四个优点:具备弹性计算资源;具备原生微服务基础能力;Service Mesh 统一流量调度;解决了多语言 RPC 治理和升级问题。但与此同时,字节云原生微服务架构也存在一些不足:

  • 一是,组件多语言 SDK 的问题仍然存在且十分严重。举例来说,在字节内部,线上非常多的服务都依赖 A/B test,业务应用需要实现每一个语言的 SDK,同时,当我们进行策略升级时,还需要推动业务升级,因此它所面临的问题与我们在 Service Mesh 需要解决的问题其实是完全一致的。
  • 二是,通用服务依赖仍需显式接入。比如当接入一些网关服务时,我们需要单独通过 RPC 调用方式。在实际的开发过程中,开发者往往只想要关注业务逻辑本身,但是为了符合公司安全标准需求和业务通用逻辑,他们还需要接入一堆服务组件,这对于开发者来说是比较痛苦的。对于维护方来说,他们也需要推动业务升级它的依赖。

基于此,字节跳动服务框架团队依据过往的业务实践提出了一个新的微服务架构演进方向——通过通用能力持续下沉和 Service Mesh 基础能力复用,促使云原生微服务架构逐步演进到多运行时微服务架构。

picture.image

“字节跳动多运行时架构的应用案例”

本章将基于以下四种流量模型,全面覆盖字节内部业务场景,为大家具体讲解什么是多运行时架构。

主路径运行时 —— 分布式网关

picture.image

第一种模式是主路径运行时,先来看看上图展示的两种网关示意图。 在中心网关示意图中,请求在 Nginx 七层接入后,会通过业务的 API Gateway,最后被打到后端的 Service C 和 Service D 。 这种网关架构存在 3 方面的问题:

  • 成本高:每当接入新的服务时,都需要经过 API Gateway 接入;
  • 隔离性较差:一般来说,一个业务线接入一个网关,这会导致一个网关承载着很多的独立服务,造成网关的隔离性较差;
  • 运维复杂:站在网关开发者的角度来看,他往往需要维护多个业务线网关服务,每次进行升级时,还需要借助 API Gateway 独立服务所依赖的 PaaS 平台进行网关升级,耗费大量精力。

鉴于上述问题,目前字节内部的许多业务线已经接入了分布式网关能力。当请求从 Nginx 七层接入后,会被直接打入服务中去,然后由服务本身的独立进程 PodB、PodC 来完成之前的网关服务。这样做的好处有三点:

  • 一是,当你需要接入网关时,只需要在字节 PaaS 平台上打开开关就可以启动进程,无需对接独立服务;
  • 二是,所有网关都基于服务为粒度进行升级,具有可控性;
  • 三是,借助 Sidecar 运维平台,对网关进行平台化运维。

由于 API Gateway 是在用户请求的主链路上,因此我们将此类型的 Sidecar 称为主路径运行时。

辅路运行时 —— 分布式风控

picture.image

第二种模式是辅路运行时,如上图所示,每一个请求进来之后,都需要访问风控服务。在左图的模式下,风控的 SDK 集成在 Service 中。当一个用户需要上线并且有风控需求时,他总是需要先集成 SDK,同时远程访问风控的 Server。这对于业务来说会增加一道接入手续,并且对于风控团队来说,维护压力非常大。

在此基础上,字节跳动风控研发团队将风控服务下沉为一个 Sidecar,如右图所示。当用户接入风控时,Service 开发者无需任何感知,他只需要在 PaaS 平台上选择“服务需要集成风控”的选项即可。那么所有的请求在经过 Mesh 之后,会被直接发送给风控服务。

分布式风控 S i decar 带来的好处是:

  • 变更灵活可控;
  • 对于业务侧来说,接入是完全无感知的;
  • 对于风控的维护团队来说,他们后续只需要维护风控 Sidecar,无需处理升级,并且去除了风控多语言 SDK 的压力。

当我们需要业务接入一个 SDK 来访问中台能力时,该模型几乎可以做到完美。由于该流量由 Mesh proxy 代理,因此我们称此类型 Sidecar 为辅路运行时。

旁路运行时 —— A/B 测试

picture.image

第三种模式是旁路运行时,旁路运行时和辅路运行时最大的区别是什么呢?旁路运行时模型无需服务网格的接入。如左图所示, A/B test SDK 是由业务自己集成的,它会访问 A/B test 微服务模式。在右图中,虽然 A/B test 是作为一个独立的 Sidecar 运行,但同时它还需要非常薄的一层 SDK,比如将一个具有 HTTP 访问能力或者 gRPC 访问能力的 SDK 集成到 Service 中,用于访问独立的 Sidecar 。

该模型带来的好处有:一是轻量接入,变更频率降低;二是多语言维护成本低;三是远程访问的大部分请求都可以落在本地,从而能够降低延迟。

在实践中,不论是 A/B test Sidecar 还是风控 Sidecar,它们在字节内部的覆盖量都非庞大,是维护团队和业务双方都比较喜欢的接入模式。

独立运行时 —— 流量镜像

picture.image

最后一种模式是独立运行时,以流量镜像为例,如上图所示。之前的的流量镜像一般是采用 TCP dump 或者 ebpf 抓包,或者当有了 Mesh 之后,我们会在 Mesh 中镜像一份。在最初阶段,字节服务框架团队是将流量抓取功能放在 Mesh 的 egress proxy 中,即当我有请求并且打开了一个流量抓取,我会在 Mesh Proxy 复制一份到消息队列中去。

后来,我们实现了一个新的流量镜像方案:抽离并设置流量抓取 Sidecar。由此我们可以只针对线上的某一个 Pod,在其中注入一个流量抓取 Sidecar,它就会经过 Service 和 Mesh egress proxy 之间的TCP连接把流量抓取出来,放到消息队列中去。

小结

综上,四种 Sidecar 模式覆盖了字节内部所有 Sidecar 模式类型,那么多运行时架构具有哪些优势呢?主要有三点:

一是,能够提供安全、灵活、可控的变更;

二是,减轻/消除了多语言 SDK 维护压力。对于基础组件的维护方来说,你必须得去处理两到三种语言,在字节内部,主流语言包含了 Go、Java 、Python、JS 、C++ 以及 Rust 等语言,多运行时架构能够降低我们在维护多语言 SDK 方面的压力;

三是,对于业务方来说,能够提供轻量/无感接入的体验。

当然,多运行时架构也存在一定的局限性:

  • 开发运维复杂:对于 Sidecar 开发者来说,由于进程是运行在一个受限的模式中,流量必须由 Mesh proxy 来决定如何编排进程;同时,监听端口和开发的高性能组件都必须受到严格约束,这比开发一个独立的服务和进程要复杂得多。
  • 与业务资源存在竞争:对于业务方来说,之前在容器中看到的进程就只有自己,当引入 Sidecar 之后,容器中的资源其实不完全属于自己,资源的竞争会引发一些较为复杂的问题。

从字节的角度来看,多运行时架构是必需的,毕竟其优点远大于缺点。当然,我们也不会放任缺点不管,字节跳动服务框架团队提出了多运行时架构优化的目标和路径:

  • 目标:将业务通用能力作为云原生的标准能力向外提供出去。云原生标准能力既包含RPC流量治理、中间件流量治理、配置、缓存等基础组件能力,同时也包括了上文提到的网关、风控等能力。
  • 路径:将 Service Mesh 的开发和运维能力标准化、平台化。随着 Sidecar 的数量逐渐变多,我们考虑规范化 Sidecar 的定义 ,同时将运维平台变得更加标准易用。

由此,我们提出了字节云原生微服务多运行时架构项目——ByteRuntime。

“ByteRuntime 架构解析”

首先来看看当前字节微服务的分层架构,如下图所示:

picture.image

IaaS 层提供了最裸的计算资源能力,包含自建机房、公有云 IaaS、边缘节点等;统一计算平台包含 TCE、ByteFaaS 以及火山引擎等,这一层能够向上提供容器的抽象;

字节内部将微服务分成了以下三层:Application、Framework 和 Runtime。其中,比较有特色的是Runtime层:在 Runtime 层,大部分服务都运行在 ByteMesh 上;ByteRuntime 是对 ByteMesh 的补充,我们也会将更多能力通过 ByteRuntime 放在 Runtime 层;SDK-based 是指将框架里提供的治理能力以 SDK 形式放在这层,是一个概念上的抽象。

此时,所有涉及流量治理的能力都会被交给 Runtime 层负责,Framework 层只需要关注如何为研发同学提供更好的使用体验。

ByteRuntime 架构

picture.image

ByteRuntime 架构包含四个核心模块:Mesh Pilot、Sidecar、治理、运维。

先看左侧框架图,Mesh Pilot 是容器中的1号进程,承担业务服务和 Sidecar 启动引导、流量编排、组件升级以及异常拉起能力;Mesh Ingress、Service、Mesh Egress 以及 Sidecar 都是 Mesh Pilot 的子进程, Sidecar 就代表了多运行时本身。

右侧框架提供了治理和运维能力,运维能力是指 Sidecar 发布、升级、启动引导能力,治理能力是大家熟知的 istio 等控制面。ByteMesh 拥有自己的控制面,采用私有的 xDS/ByteMesh Control Plane v2 协议。如果你的 Sidecar 不想用 istio,而是用自己的控制面,ByteRuntime 控制面也可以提供支持。如果 Sidecar 原生集成了 xDS/ByteMesh Control Plane v2 SDK ,那么你可以直接复用 ByteMesh 控制面。

我们的架构理念包括两点,一是标准化:让每一个 Sidecar 都有标准可循,只要符合标准,就可以把 Sidecar 插入到上图左侧框架中作为运行时能力提供;二是平台化:我们提供平台帮助用户进行 Sidecar 的运维和治理,而用户只需要关心 Sidecar 接口是什么,需要实现什么能力。

picture.image

那么 ByteRuntime 具备了哪些能力呢?以开发者的视角来看,我们可以从三个阶段来一一列举。

  • 开发阶段:我们希望为用户提供良好的开发体验,降低开发成本,提供了包括 Sidecar Framework、调试/灰度、高性能组件、治理 SDK 能力;
  • 上线阶段:ByteRuntime 能够完全支持流量编排、无感/轻量接入、单点/灰度/批量接入、安全部署、安全升级能力;
  • 运行阶段:ByteRuntime 会提供 Sidecar 流量治理、日志收集、性能优化、观测诊断等辅助工具。

基于以上三阶段提供的能力,我们能够有效降低 Sidecar 使用成本,提高运行效率。

ByteRuntime vs DAPR

picture.image

DAPR 和 ByteRuntime 都是对于多运行时架构的具体实践,但是它们所面向的场景不同、实现侧重点也不同。

怎么理解侧重点不同呢?先来看 DAPR,首先,DAPR 是一个标准化的协议(gRPC),比如将 SDK 和 Sidecar 之间的通信收敛到 gRPC 上就是一个非常好的理念,可以让底层的 Sidecar 在不同的平台得到实现和迁移;其次,DAPR 目前会更多关注基础组件(config/cache/Kafka/……);最后,DAPR 是基于 Kubernetes 原生运维体系实现的。

而 ByteRuntime 更多是关注 Sidecar 管理、运维以及如何高效开发,对 Sidecar 的接口和能力没有过多约束。因此 DAPR 的 Sidecar 可以运行在 ByteRuntime 的体系里,二者是可兼容的,DAPR 和 ByteRuntime 协同配合能够适用于更广泛的场景。

“ByteRuntime 架构解析”

自 2020 年初至现在, ByteRuntime 已历经两年多的实践,本章将从开发、上线、运行三个阶段为大家全面解析 ByteRuntime 技术。

开发阶段

picture.image

在 Sidecar 开发阶段,我们遇到了三个问题:

  • 资源敏感:虽然将一个业务能力作为独立的服务或放在容器中,CPU 资源差别并不大(忽略延迟因素),但放在业务容器中时,这个 CPU 资源占用会被业务看见,业务会认为你占用了资源,就会有担忧。一方面,我们需要向业务展示这个额外资源占用的起源,另一方面,我们会对性能有更高的要求;
  • 开发受较多约束:在开发的过程中,某些场景下,如果你需要进行测试,请求只能由 Mesh 发起,测试环境相对而言是比较受限的;
  • 线上调试困难:由于没有容器的权限,你会面临如何进行日志收集、处理 Sidecar CPU 暴涨问题、内存泄露等问题。

针对上述难题,字节跳动基础架构服务框架团队提出了一套开发框架:Sidecar Framework,如上图所示。

在接入/监听层,一般来说只需要做一个 TCP 的监听,但是我们建议所有的 Sidecar 都走 UNIX domain socket + 共享内存通信的方式。因为这套通讯方式已经在 Mesh 场景下经过了大规模的验证,它绝对可以高效提升通信效率,并且保证稳定性。

在高性能 Runtime 层,字节内部在 C++、Rust、Go 方面已经积累了很多深度的优化和实践,用户都能直接使用。在此之上,我们还提供了 xDS/CPv2、Auth 身份认证、服务状态的接口。

在通用工具侧,我们提供了热重启、编译优化、日志收集等工具。其中,热重启能力的建设花费了字节内部开发同学一年左右的时间和精力,而用户只需要使用 Sidecar Framework 就可以直接使用这个能力。

上线阶段

流量编排

picture.image

在上线阶段,我们遇到的第一问题是流量编排,即 Sidecar 放置的位置不同,就会涉及不同的启动顺序以及不同的监听端口。以主路径网关举例,首先 ByteRuntime 可以提供一个发布与管理的平台,当开发好 Sidecar 之后,开发者需要指定 Sidecar 的流量模型和启动顺序。

其次,Mesh Pilot 作为容器的 1 号进程拉起,它会根据服务名获取到 Pod 中有哪些 Sidecar,同时根据 Sidecar 的前后依赖,去渲染 Mesh Ingress、API Gateway、Service 这三个组件的配置,这些配置决定了组件在哪个端口进行监听。

第三步,Sidecar 都是按需注入的,然后由 Mesh Pilot 下载 Mesh Igress/API Gateway 资源。

最后,由 Mesh Pilot 作为一号引导进程,依次拉起各个组件并进行服务注册。

以上就是 ByteRntime 通过 Mesh Pilot 和运维平台能够提供给 Sidecar 开发者同学的能力。

Sidecar 发布与管理平台

picture.image

在 Sidecar 发布与管理平台方面,服务框架团队主要围绕资源管理、服务管理、升级管理、统计报表四个维度进行了建设,如上图所示。

以资源管理中的缺陷管理为例,如果让 Sidecar 面向线上所有场景,它一定会在某些服务上面表现得比较奇怪,这是由于业务进行了一些非标准化的操作,此时我们需要暂时让 Sidecar 拉黑这批服务,直到修复 bug。缺陷管理提供了对该需求的统一支持 。

运行阶段

在这一阶段,我们主要进行了五方面的性能优化:基于共享内存的通信场景泛化、全静态编译 + PGO、Polling mode runtime、无序列化/PRAL、高性能 JSON 库——ByteDance/Sonic。接下来主要来看看 PGO。

picture.image

首先,我们在 C++ 中会使用虚函数调用, 而这些虚函数的调用会产生非常大的开销,比如间接指令跳转开销;同时由于是在运行时跳转,无法进行内联优化;最后也是由于在运行时决定,很多的优化策略都无法进行。

PGO(Performance Guided Optimization) 的理念是: 先运行一遍程序,采集 perf 数据。比如对于一个 if 分支和一个 else 分支,假设 if 分支命中率更高,此时就可以将 if 分支进行更激进的优化,而无需显式地指定分支概率。对于高频的函数调用或者虚函数调用,也可以进行更加激进的内联。

PGO 的流程大致是: 首先进行第一次编译,将线上运行的真实数据反馈给 LLVM 编译器,再由 LLVM 编译器根据 Performance 数据和原始的 Binary 再次编译一次。由此我们能够获得 25% 左右的线上收益。

“ByteRuntime 落地实践”

目前,字节内部拥有 30+ Sidecar 类型,国内 400w+ 容器部署在这种模式上,相比于推动 SDK 升级需要花费半年左右的时间,它的平均升级周期是 3~4 周,带来的体验更好。

接下来首先介绍一个运用 ByteRuntime 最极致的案例。

picture.image

背景: 假设存在 A/B/C 服务,由多个团队进行维护,我们将它做成一个大单体,这会带来一个问题,即每天会存在 4-5 次上线,每次上线需要花费 8-9 小时,会耗费大量的时间成本。此时我们会考虑为什么不将单体服务拆分成微服务呢?但是拆解之后又会面临另一个问题,即它本身的 payload 很大,拆解后会造成远程调用损耗非常高,无法接受。

解决思路: 我们将 Service A 作为主服务,将 Service B 和 Service C 下沉为 Sidecar。该方案有两点优势:

一是资源损耗相对来说可以接受,我们在 IPC 层面做了优化。相比远程调用需要进行全序列化,该方案能够节约更多资源。

二是, Service A、B 和 C 可以独立发布。虽然上线速度并不快,每一次上线需要花费 4-5 小时左右,但相对之前要快速很多。

其次,介绍一下 ByteRuntime 是如何与 DAPR 进行兼容,如下图所示,我们直接将 DAPR 作为 ByteRuntime 的一个 Sidecar 提供给容器即可,二者之间完全不冲突,毕竟它们所解决的问题是不同的。

picture.image

最后,如果大家了解 Go 语言,也欢迎大家关注一下字节跳动基础架构服务框架团队的开源项目 ClodWeGo。

https://github.com/cloudwego

  • END -

下载云原生白皮书

基于字节跳动基础架构提供的云原生技术能力和技术实践,火山引擎已经构建了全栈的云原生服务产品矩阵,包括面向算力的云原生服务、面向应用的云原生服务和面向场景的云原生服务三大板块。

欢迎下载 IDC 和火山引擎联合出品的云原生白皮书,获取企业数字化转型新理念!

picture.image

扫描二维码,下载火山引擎云原生白皮书

118
0
0
0
关于作者
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论