云原生Service Mesh探索与实践

1. 背景
程序员

随着互联网业务不断发展, 业务服务以及服务实例呈快速增加的趋势,然而传统微服务架构虽然能在一些场景知足服务高性能, 高可用, 可治理等需求,但同时也面临着耦合性高,灵活性差,管理复杂,可运维性低,缺少多语言支持等问题。而现在云原生场景下,Service Mesh则愈来愈成为热议的话题。web

ESA Mesh是OPPO互联网自研的Service Mesh组件, 隶属于ESA Stack微服务体系的一部分。ESA Mesh致力于提供云原生场景适合公司的Mesh方案,解决公司跨语言调用难,多语言服务治理生态匮乏,服务治理不统一等诸多问题。提供云原生场景下弹性易用的微服务架构基础组件,层层突破微服务落地难,Service Mesh落地难上难的困境。后端

2. Service Mesh的前世此生

随着近几年来云原生生态的不断壮大,CNCF基金会中的会员以及容纳的项目愈来愈多,CNCF为云原生进行了定位。微信

如下是CNCF对云原生的从新定义(中英对照):网络

Cloud native technologies empower organizations to build and run scalable applications in modern,dynamic environments such as public, private, and hybrid clouds. Containers, service meshes,microservices, immutable infrastructure, and declarative APIs exemplify this approach.

云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的表明技术包括容器、服务网格、微服务、不可变基础设施和声明式API。
架构

可见Service Mesh(服务网格)在CNCF的定义中已然成为云原生时代不可或缺的一部分, 而且同时与容器,微服务有着密不可分的关系。app

最初的网络计算机交互

最初人们想要让不一样计算机之间进行通信时, 最简单的模型是这样的。负载均衡

虽然要真正完成计算机之间的交互须要很是多的网络细节, 可是上图依然是用户最原始的需求:一个计算机上的服务调用另外一个计算机上的服务。框架

可是实际上的交互须要更多的网络细节上运维

上图中网络通信的细节是经过Networking Stack实现, 可是早年间这层网络细节仍然是须要人们人为的去管理网络链接等细节,直到计算机开始变得不是那么的昂贵,开始逐渐普及,计算机与计算机之间的链接需求开始了爆发式的增加,如何让计算机能发现其余的计算机, 如何有效控制计算机之间的流量, 特别是如何进行流量控制等成了广泛性的问题。

因而为了知足流量控制的功能需求, 人们在本身的应用中开发了流量控制的功能, 可是此功能逻辑的代码与业务逻辑交织于一处。

直到TCP/IP的出现以及兴起让网络细节问题以及流量控制等功能都获得了统一且标准化的解决, 同时成为计算机系统的一部分供用户透明的使用。

直至今天互联网大多都依赖着TCP/IP提供的基础能力完成着上层复杂的功能。

Microservices时代

微服务的出现能够说掀起了互联网服务实现与组织方式新的浪潮。同时也带来了一些新的技术以及功能上的挑战。

微服务强调着服务的细化(服务划分或者说拆分)以及架构的轻量化,同时出现了一些新的需求:服务发现, 熔断, 负载均衡等等。

初期面对这样的需求聪明的程序员老是能很快的在业务中便实现相应的功能, 可是遭遇了与最初网络计算机交互时代时一样的问,这些功能与业务逻辑混杂在一块儿, 难以管理与复用。

因而一些先驱者便将这些功能的实现打包成Library(或者说SDK)并公开给世界各地的程序员使用, 避免了重复造轮子,同时也让不少没有那么多精力去研究此类技术的公司或者我的能快速的享受到前人的智慧结晶。

此间变出现了Spring Cloud, Dubbo等优秀的微服务框架, Spring生态更甚至能够说当今Java生态中的“杀手锏”,这些优秀的框架或是组件很大程度上推动了微服务的发展和标准化。

Microservices is a silver bullet?

这个问题彷佛已经有了比较明确的答案。

微服务广泛存在着落地困难的问题多语言支持困难

  • Library与业务耦合

  • Library升级地狱

  • 陡峭的学习曲线

  • 指数级增长的系统复杂度

  • ...

与上面的Networking Stack同样, 人们彷佛迫切的想要屏蔽掉一些通用基础组件。

但是现在TCP/IP网络栈已经足够的稳定,彷佛不容许人们直接将微服务能力下沉至此, 因而便有了Sidecar的概念。Sidecar就是与应用程序一块儿运行的独立进程,为应用程序提供额外的功能。

Service Mesh

每一个服务都会有一个Sidecar与之配对。因而在错综复杂的服务部署结构下便会造成下图。

全部的服务通信都经由Sidecar代理, 造成网状, 所以称之为:服务网格。

Service Mesh的概念最初由Buoyant 的 CEO William Morgan在博客上的一篇文章What's a service mesh? And why do I need one?中提出。

其定义

A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It’s responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware. (But there are variations to this idea, as we’ll see.)

服务网格是一个基础设施层,用于处理服务间通讯。云原生应用有着复杂的服务拓扑,服务网格保证请求能够在这些拓扑中可靠地穿梭。在实际应用当中,服务网格一般是由一系列轻量级的网络代理组成的,它们与应用程序部署在一块儿,但应用程序不须要知道它们的存在。

这个定义最强有力的部分在于,它再也不把代理当作单独的组件,并强调了这些代理所造成的网络的重要性。

Cloud Native时代服务便以下图所示

一个Cloud Native App部署时都将自动部署一个Sidecar与之对应, 服务期间全部的服务通信都经由Sidecar代理,同时Sidecar配置Control Plane(控制面板)完成诸如服务发现, 熔断, 负载均衡, 限流, 链路追踪等功能。而相对的业务服务仅仅须要关注本身的业务逻辑和一个仅仅用于通信的轻量级RPC便可。业务无需关注Service Mesh层面的逻辑,甚至没法感知到它们的存在, 仅仅只须要像是咱们最初的网络计算机交互时的模型同样, 看成仅仅是服务调用了另一个服务便可。

行业先驱

业内已有许多优秀的Service Mesh开源组件

  • Linkerd(by: Buoyant)

  • Istio(by: Google, IBM)

  • Envoy(by: Lyft)

  • ServiceComb(by: 华为)

  • SOFA Mesh(by: 蚂蚁)

  • Nginmesh(by: Nginx)

  • TSF(by: Tencent)

3. ESA Mesh探索与实践

随着公司上“云”步伐的层层迈进, 咱们已然具有了云原生时代雄厚的基础实力, 在此之上ESA Mesh致力于提供云原生场景适合公司的弹性, 易用, 可靠,可观察的Mesh方案。

ESA Mesh设计目标

  • 跨语言支持

  • 高性能,低延迟

  • 业务无感知

  • 服务发现

  • 负载均衡

  • 路由

  • 熔断/限流/隔离

  • 多协议支持

  • 故障注入

  • 链路追踪

  • ..

咱们并非开发一套新的微服务生态, 而是用新的方式服务业务。

流量拦截

Service Mesh架构中首当其冲的问题即是如何拦截业务流量, 并引流到Sidecar的问题。

iptables

Istio中的流量拦截方式即采用的iptables实现,经过一系列的iptables 规则将业务Pod中的Inbound流量以及Outbound流量均Redirect到Sidecar中随后由Sidecar处理。

此方式好处在于

  • 用户无感知

    用户无需感知Sidecar的存在, 像日常同样进行RPC调用便可。

  • 随意接管任意流量

    因为iptables的规则很是的灵活, 全部Netfilter以后流量都可经过不一样的规则实现流量接管。

  • 老应用无缝迁移

    甚至能够在老应用彻底不用变动的状况下, 接管全部的服务注册/发现,服务调用的流量。

可同时iptables也存在着诸多问题

  • 性能不理想

    iptables的性能老是使人诟病的地方, 最初其存在的目标是用于网络防火墙使用, 而且多年来Linux中的iptables并没有太大改变(虽然随后推出了nftables), 可是随着iptables规则的增长,遍历带来的消耗剧增, 性能以及网络延迟严重降低。

  • 没法增量更新

    每次添加新规则时,必须更新整个规则列表。装配2万个Kubernetes服务产生16万条的iptables规则须要耗时5个小时

  • 应用流量复杂,复杂度高

    Istio中是拦截全部的业务Pod流量, 而实际业务中除了RPC调用以外每每还存在着不少别的流量,错综复杂的流量对于Sidecar处理来讲相对比较困难。

  • 暂时没法使用eBPF

    现阶段Linux内核版本大多为3.x, 不太建议采用eBPF拦截方案(理论可行, 但一般须要4.x, 甚至4.8+使用XDP)。

理想中的流量拦截方式 - eBPF

eBFP为比较理想的流量拦截方式, 具备性能高,灵活性强, 功能丰富等诸多特色。

BPF

在eBPF以前不得不聊聊BPF, BPF全称Berkeley Packet Filter, 顾名思义这是一个用于过滤(filter)网络报文(packet)的架构。

BPF的架构很是的简洁, 途经网卡驱动层的报文在上报给协议栈的同时会多出一路来传送给 BPF,再经后者过滤后最终拷贝给用户态的应用。全部的过滤操做都在内核空间完成。单这么看可能会有些许陌生, 可是若是提到大名鼎鼎的tcpdump以及wireshark想必便了然于心了, BPF即为tcpdump以及wireshark的基础, 乃至许多网络监控领域的基石。最初在Linux中为LSF(Linux Socket Filter),其实现几乎与BPF无异。后改名为cBPF(classical BPF)。

eBPF

Linux 3.15版本伊始,eBPF便进入人们的视野, 并在随后v3.17被添加到kernel/bpf 下(得到一等公民待遇),并以eBPF命名(extended BPF), 同时先前的LSF改名为cBPF(classical BPF)。

相比于cBPF而言eBPF这次升级属革命性改变

  • 全新的开发接口

  • 基于 map 的内核与用户空间的交互方式

  • 丰富了指令集

  • In-kernel verifier

  • C语言编写程序

早期的cBPF所覆盖的功能范围很简单而 eBPF 的利用范围则要广的多

  • XDP(eXpress Data Path)

  • 流量控制

  • 网络包跟踪

  • 防火墙

  • 应用性能调优/监控

  • cgroups

eBPF相较于cBFP带来了大幅度的性能提高,同时在内核追踪(Kernel Tracing)、应用性能调优/监控、流控(Traffic Control)等领域也带来更多更丰富的特性和可能性。

现实中的流量拦截 - Mesh SDK

ESA Mesh初期并未采用流量拦截的方式(虽然很想)来导入流量到Sidecar, 而是采用了轻量级Mesh SDK的方式直接从RPC客户端定向打到Sidecar。

上图能够看到实际通信时,采用了Unix Domain Socket的方式进行业务与Sidecar的通信以求获取更高的性能, 由于Sidecar始终会与业务Pod在同一个Node节点(物理机), 所以不必经过端口地址的方式, 直接进程间通信便可。

此种方式的好处

  • 流量已知,可控

    Sidecar全部接收到的流量都是本身指望的流量, 不会受到干扰。

  • 服务治理参数传递方便

    一般Sidecar进行服务治理时或多或少都须要一些特定的参数(好比AppId), 而使用SDK即可随意传递想要的参数。

  • 可规避协议探测逻辑

    能够将不一样的协议分别在不一样的端口上启动, 避免笼统的绑定一个地址接受全部流量时频繁的协议探测(有的协议理论上是没法探测的, 好比Http协议)。

可是一样也存在着问题

  • 多语言问题

    又回到了多语言须要提供SDK的问题。

  • 业务SDK侵入

    不可避免的形成了必定程度的SDK侵入

出于前期简单化考虑, 咱们仍是选择Mesh SDK的方式与Sidecar进行通信。

如何选择Sidecar部署架构方案

业内一般存在着两种部署方案, 一种是Sidecar与业务在同一个Pod,分属不一样的Container(称之为Sidecar注入模式), Sidecar注入模式也是Istio采用的部署方案。而另外一种模式则是将Sidecar独立使用DaemonSet部署, 让每一个Node节点都启动一个Sidecar实例为当前节点的业务Pod服务

Sidecar注入

Sidecar注入的方式能够说是Service Mesh中Sidecar部署模式的最终形态

它具备如下特色

  • 隔离性强

    全部诸如配置, 限流,链接等资源都是业务独享, 不会和其余业务互相影响。

  • 扩展性强

    Sidecar随着业务Pod发布自动注入, 业务扩缩容均不影响Sidecar提供服务。

  • 可用性高

    一个Sidecar仅服务于单个业务Pod, 即便一个Sidecar故障也仅会影响一个业务实例

  • 资源占用-按需

    Sidecar随着业务Pod发布自动注入, 而且能够根据业务需求分配不一样的资源给Sidecar, 作到按需使用。

  • 服务治理简单

    仅需对单个目标用户(当前业务)进行服务治理, 简单高效。

  • 可持续发展性高

    符合业内Sidecar趋势, 方便吸取开源优秀的架构设计。

  • 用户可接受程度高

等优势, 能够说是比较理想的部署模型, 可是考虑前期投入,则存在一些须要考量的问题

  • 不支持Sidecar 独立升级

    试想一下若是Sidecar作了版本升级(即便新增的特性并非一些业务所须要的)也要求业务去重启一下本身的服务, 这彷佛违背用户无感知的设计原则。

  • 不支持 sidecar 监控(异常没法告警)

    这个几乎能够说是致命的了, Sidecar自身做为基础组件都没法具有监控能力又拿什么去像业务保证可用性呢。

  • 不支持登陆 Sidecar Container进行故障排查

    这个能够说是致命的了(而不是”几乎“), Sidecar出错没法登录到对应的Container去进行问题排查, 应该没有人敢发布这样的服务。

  • 没法控制业务Container和Sidecar启动顺序

    一般咱们要求Sidecar要先于业务Container启动。

DaemonSet模式

DaemonSet模式属于介于传统网关与Sidecar之间的一种, 或者说一种折中。

借助于DaemonSet, 在每一个Node节点上都会有一个Sidecar的实例, 用于服务当前Node中的全部业务(即便业务的Pod会常常的被调度)。

相较于Sidecar注入模式DaemonSet

  • 隔离性较低

    同时服务多个业务, 不免会有一些CPU/线程, 网络, 甚至是内存资源上的共享。

  • 扩展性较低

    DaemonSet模式的部署模式已经相对比较固定, 没法灵活的作扩展。

  • 可用性较低

    一旦Sidecar故障便会影响全部当前Node节点中的服务, 须要额外的高可用机制。

  • 资源占用高(前期较低)

    由于K8s随时都有可能调度不一样的Pod到当前Node节点, 所以须要预先预分配能服务整个Node节点的资源(即便能够超卖) 。理论上要真的能服务好全部的Pod就得占用当前Node一半的资源(虽然实际这样不太可能)。

  • 服务治理难度较高

    须要在Sidecar中同时维护多个业务的服务治理, 加剧Sidecar自己资源占用的同时, 甚至比常规RPC更加复杂(由于RPC一般只须要在Client端作本身的服务治理就能够了)。

  • 可持续发展性通常

    鲜有采用DaemonSet方案的用户, 后期难以进行开源跟进。

  • 用户可接受程度通常

缺点虽多, 可是考虑实际状况, DaemonSet仍旧有优势

  • 部署简单

    独立部署, 不须要对业务部署作侵入。

  • 支持监控与故障排查

    因为是独立分配的容器, 支持使用CMDB登录排查问题以及监控等。

  • 可独立升级

所以咱们初期选择了DaemonSet做为部署方案。

DaemonSet带来的可用性问题

上面提到DaemonSet因为是一对多的部署, 所以一旦Sidecar故障将会形成大面积的影响。

因而在DaemonSet以外咱们追加了一个Common集群, 用于本地Sidecar故障的Failover。

  • 当本地Sidecar请求故障后降级到Common集群

  • 本地Sidecar恢复后回退到本地Sidecar正常运行

这无疑又增长了SDK的复杂性。

4. ESA Shaft

ESA Shaft是ESA Mesh中的高性能sidecar实现, 至关于envoy, Linkerd的角色。

初期考虑开发效率以及Control Plane, 以及公司微服务生态等因素决定采用Java, Netty实现。

协议上支持

  • Http1/Http2

  • Dubbo

  • gRPC

  • HttpToDubbo

服务治理支持

  • Service Discovery

  • Loadbalance

  • Rate Limit

  • Circuit Breaker

  • Concurrent Limit

  • Tracing

架构设计

ESA Shaft架构上总体分为

  • Listener

    监听本地地址并分发IO事件

  • L4 Filter

    处理网络事件及协议编解码

  • L7 Filter

    7层过滤器,负责服务治理及请求转发

不一样的协议由不一样的Listener启动(包含着不一样的L4/L7 Filter), 经过不一样的Filter组合完成协议解析, 服务治理等复杂的功能。

宏观架构

经过集成ESA Registry注册中心SDK, 服务治理框架Service Keeper以及ESA Conf做为配置中心下发动态配置完成动态化服务治理功能

Threading Model

ESA Shaft的线程模型很是简单

Boss线程:负责监听 & 处理链接

Worker:负责处理I/O, L4 Filter, L7 Filter,请求转发等全部后续操做。

值得注意的是这里的Worker线程数默认使用和CPU相同的数量, 意在尽可能减小线程切换带来的开销(虽然Java暂时没法比较方便的作线程亲和性)。

HTTP1.1场景下的性能表现

性能测试环境

Sidecar Echo

直接在Sidecar层面返回Echo数据, 不作请求代理

最高TPS超过25W

正常负荷平均RT:avg(rt)<0.5ms

Sidecar Proxy

代理到3个后端节点, 负载均衡方式为随机。

最高TPS接近12W

正常负荷平均RT:avg(rt)<1ms

由图中可看出性能上Shaft仍是比较高的, 可是在活跃链接较多的场景则表现稍差(线程数量设置偏小)。

5. The Future of ESA Mesh

总的来讲初期实践阶段咱们采用适合公司环境的较为折中的方案(Mesh SDK, DaemonSet, Java), 也踩了很多的坑, 将来ESA Mesh将着眼于行业领先的Mesh解决方案, 进行进一步的演进。

其中包括

  • 采用流量拦截方式, 去除SDK

  • 采用Sidecar注入方式部署

  • 接入统一服务治理平台(ESA Sailor)

  • 自动化部署/运维

ESA Shaft

  • Rust重写

    Java语言确实不太适合作Sidecar, 即便有协程(如今尚未)也有着内存占用高的问题, 再加上GC带来的硬伤,所以很难在Sidecar这个领域施展拳脚。Rust是一门很是好的语言(除了学习曲线异常陡峭以外), 优秀的语言设计以及强大的编译器让应用能达到几乎与C++媲美的性能和资源占用, 也能保有必定的开发效率。目标内存占用在10M级别完成Sidecar的重写。

  • 兼容XDS

    与开源靠拢, 兼容XDS协议。

  • 独立升级 & 热更新

  • 自定义RPC协议(与ESA RPC保持一致)

    采用更高效的RPC协议完成Sidecar之间的通信, 例如Coap, Quic, 基于UDP自定义协议等。

  • 鉴权/加密

  • Back Pressure

ESA Sailor统一服务治理平台

目前咱们已经完成公司统一的服务治理平台基本功能研发

  • 兼容XDS标准, 采用XDS与客户端通信

  • 单元化的分级架构, 避免加载全量数据

6. 结语

ESA Mesh仍处于积极探索与实践的时期, 期间可能会走弯路, 但随着Mesh架构及技术的演进咱们但愿提供给用户一个开箱即用的Mesh解决方案, 在行业Service Mesh的演进之路上留下一个脚印甚至是一个里程碑。


☆ END ☆


招聘信息

OPPO互联网基础技术团队招聘一大波岗位,涵盖C++、Go、OpenJDK、Java、DevOps、Android、ElasticSearch等多个方向,请点击这里查看详细信息及JD


你可能还喜欢

OPPO自研ESA DataFlow架构与实践

数据同步一致性保障:OPPO自研JinS数据同步框架实践

微服务全链路异步化实践

Dubbo协议解析与ESA RPC实践

自研代码审查系统火眼Code Review实践

更多技术干货

扫码关注

OPPO互联网技术

 

我就知道你“在看”

本文分享自微信公众号 - OPPO互联网技术(OPPO_tech)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索