陈超, 腾讯云 TVP(腾讯云最具价值专家), 历任百度凤巢某业务团队技术负责人, 丁丁租房基础架构与运维部负责人, 现猫眼娱乐基础架构负责人. 参与国内最大的商业广告平台 "凤巢系统" 服务化过程, 从 0-1 搭建丁丁租房整体业务微服务架构和基础架构体系, 从 0-1 搭建猫眼娱乐基础架构体系. 具备 8 年互联网工作经验, 在服务治理领域具备丰富经验.
前言
现在市面上有非常多介绍 Servicemesh 概念, 架构, 方法论以及标准化实现的文章, 但是对于 Servicemesh 应该如何才能被真正有效可靠的落地, 我们会面临哪些困难选择, 并未太多提及. 本文希望从这个角度出发, 结合笔者在生产环境落地中的一些经验和踩过的坑, 探讨如何才能更好地让系统演进到 Servicemesh 架构. 如果对于 Servicemesh 本身概念, 发展现状以及为什么需要它不清楚的话, 可以先阅读架构师成长之路之服务治理漫谈一文. 此处不复赘述. 以下我们直接进入正题.
考虑过资源上的损耗吗?
mesh 本质上相当于寄生在业务机器上. 使用的是业务机器的资源. 实际上的测试中发现, 由于采用了 c++/go 实现的 mesh 对于内存的消耗比较可控, 默认情况下只占用几 M, 在高并发下一般也只会上升至几十 M. 这对于正常情况下 8G/16G 内存的应用机器来说基本可以忽略不计. 所以内存的额外占用这个问题可以基本忽略. 但是其对于 CPU 资源的消耗则较大, 一般会趋近于业务正常使用的 CPU 资源量. 这意味着, 加入了 mesh 之后, 有可能业务能使用的 CPU 资源只剩下本来的一半. 这就是一个比较大的问题了.
关于这个问题, 目前业内的主要论述认为, 由于正常业务机器的资源使用率不到 10%, 所以这部分的额外占用在实际情况下并不会对业务造成实质性的影响, 反而可以让我们更好地利用上闲置资源, 避免浪费. 业务与 mesh 互利共赢.
这个逻辑, 在未来很长一段时间内肯定都是成立的. 但是, 我认为基于这个逻辑, 会衍生出的两个新问题:
资源不会无限期闲置. 我们已经注意到, 由于涉及到成本分摊, 现在越来越多的业务方对于资源使用已经越来越重视, 加上未来的主流趋势云原生的目标之一也是希望提升机器资源使用率. 按着这个趋势, 当有一天资源利用率的问题已经被相对妥善地解决了, 那 mesh 对于 CPU 占用的这个问题就会凸显, 届时将如何解决这一问题呢? 如果让 mesh 绑定单独 CPU 核或者采取绑定单独 pod 资源的方式来和每个业务实例资源做区隔, 那必然也会带来不小的成本浪费.
资源使用率这个数字之外, 还有业务高低峰问题. 我们都知道业务是有高低峰的. 比如外卖业务每日饭点是高峰, 酒店业务每到节假日是高峰, 电影票业务每到春节是高峰. 有高低峰就说明肯定会有资源的适当冗余. 所以虽然看着资源使用率不高, 但是真到了高峰, 部分业务的系统 CPU 资源会飙升甚至趋近于打满, 这种情况下如果引入 mesh, 则给业务方的直接感受就是: 高峰期业务处理能力下滑一半. 我相信业务方听到这个结论后都会表示无法接受. 这个时候怎么处理这一问题呢? 除了业务方扩容一倍机器之外, 还有解法吗?
这看似是一个无解的命题, 因为 servicemesh 的架构就是这样. 资源也就是这些, 不会凭空多出来. 但是, 笔者想问一下, 我们是不是可以打破 servicemesh 的架构, 或者说, 优化 servicemesh 的架构?
回顾下我们第一讲里面介绍服务治理发展史时候提到的三个重要的思潮:
- Server Proxy
- Smart Client
- Local Proxy
Servicemesh 即属于 Local Proxy 之一, 可以解决与业务方强耦合, 语言强相关, 单点等问题. 但其他思潮就一无是处吗? 显然答案是否定的. 其他的方式仍然具备着强大的生命力和存在的价值. 我们的解决方案即使用 Server Proxy 作为闲置资源不够时的兜底方案, 采取逻辑上的 Central Mesh 来解决上述的问题:
Sidecar 进行闲置资源探测
当发现闲置资源即将不足, 则告知 sdk 切换流量至 Central Mesh
Central Mesh 完成 Sidecar 所有的工作.
Central Mesh 装载有所在区域所需的的所有信息, 承担起 Sidecar 的所有能力. 即 Central Mesh 也作为所在区域 Sidecar 的 backup, 在 Sidecar 失灵或者闲置资源不足以正常运转 Sidecar 的情况下, 主动切换流量.
Central Mesh 之所以称之为 "逻辑上", 是因为 Central Mesh 不一定是就一组中央集群, 而是可以分散就近部署, 以尽量降低网络延时和单点带来的额外风险. 比如可以按机房, 按地域, 按网关甚至就近部署到宿主机 https://cloud.tencent.com/product/cdh?from=10680 上.
Mesh Arch
考虑过性能上的损耗吗?
性能上的损耗是回避不了的一个问题. 由于多进行了一次转发以及要进行服务治理, 所以性能天然地比直连 RPC 的方式的性能要差. 基于我们实际的性能测试结果, 其相比于直连, mesh 方式的性能会退化 20-50% 左右, 这还是在不采用 iptables 这种更耗性能的方式下进行的测试. 当然, 这个增加的延时在毫秒级, 对于大部分的业务要求来说, 其实是可接受的. 对业务性能的影响微乎其微.
但是, 我们还是需要考虑如下的潜在问题:
业务应用问题. 对于一些高并发的业务场景, 可能本身延时就低(毫秒级), 且对延时敏感, 加上一次调用链路可能会有七八次甚至十次以上的 RPC 调用, 如果以这种方式进行改造, 则可能导致这类业务性能退化严重, 甚至可能引起比如超时, 线程池打满等问题.
基础应用问题. 如果 servicemesh 的未来趋势是所有通讯流量都 mesh 化而不仅仅是业务应用的流量, 那么我们就还需要考虑, 比如类似 Redis https://cloud.tencent.com/product/crs?from=10680 这样的对延时极度敏感的存储流量进行 mesh 化后, 我们对于 mesh 带来额外延时的忍耐度将进一步降低. Redis 本身就是一个超高并发, 极度低延时, 非常延时敏感的场景. 多出一毫秒的延时都很有可能会引起 Redis 可用性的成倍下滑甚至引起业务故障.
所以针对以上问题, 一方面, 我们需要对于性能的退化有心理预期, 另一方面, 我们也应该竭尽所能地优化甚至压榨 servicemesh 的性能极限, 而不是说选择了 mesh 就放弃了性能听之任之了. 想想 netty 著名的压榨性能到极致的 "eventloop 挑选".
在 mesh 的通讯性能优化上, 有几个可以考虑的点:
本地进程通讯优化. mesh 由于和业务进程在同一机器上, 具备利用本地进程通讯加速通讯性能的前提条件. 本地进程通讯存在多种方式, 比如 mmap,unix domain socket,pipe,signal 等等. 其中又以 mmap 性能最为突出, traffic-shm 是一个异步无锁的 IPC 框架, 能够轻松支撑百万 TPS, 其就采用了 mmap 来进行通讯. 通过实际测试, 采用 mmap 结合适当的事件通知机制, 在某些高并发的场景上, 其性能相较于 tcp 的方式会提升 30% 以上.
线程模型. 基本高性能服务的底层都采用了 Reactor 模式来实现线程模型. 当然, 配合线程池 / 协程池, 以及 Reactor 的层次, 可以有多种的实现路径. 有类似 Nginx 这样的一主多子进程 + 单 Reactor 单线程 (高版本提供了线程池机制) 的模型, evio 和 Envoy 利用 "单 Reactor 协程池(线程池)", Netty 采用多级 Reactor + 多级线程池. 避免 mesh 出现阻塞式设计.
字节重用. 我们习惯于对每个请求去新创建一个他所需要的空间来存放一些信息. 但是当并发量上来后, 这样的空间分配将会导致较大的性能开销和回收压力. 所以基于伙伴算法或者 Slab 算法或其他的方式, 进行一个字节内存使用的分配管理, 将会让你收益良多. 比如 Netty 由于申请了堆外内存而采用了伙伴算法, Nginx 则采用了 Slab 机制, Mosn 在 Golang 分配机制的基础上引入多级容量加 sync.Pool 机制的缓存来进行优化.
内存对齐. 操作系统按照页进行内存管理. 如果你直接操作内存地址进行数据传输 (比如用 mmap) 的话, 那么如果没有内存对齐, 将会导致你拉取到并不需要的内存空间, 且会有内存移动拼接的额外开销, 这将会直接导致你性能的下滑. 高性能内存队列 Disruptor 也是采用了内存对齐的方式进行优化.
无锁化. 通讯的第一反应即需要处理并发安全的问题, 很多时候你可能不得不通过锁的方式来保障安全. 这个时候, 考虑下通过利用硬件层面的 CAS 操作来替代常规的锁操作, 也可以采取类似 Redis 这样单线程处理的方式, 或者 Envoy 这样虽然采用了线程池, 但是连接会进行单个线程的绑定操作来规避并发问题.
池化. 线程资源是很宝贵的, 加上线程本身不可能申请几千上万个出来, 所以线程池是默认的标配. 这边要谈的是, 比如你用了协程技术, 虽然协程被神化为轻量级线程, 性能非常高且可以轻松开出几万个而面不改色. 但是你需要知道, 这也会很严重地影响你实际的处理性能, 并且由于 golang 本身的协程分配原理, 协程的一些元数据并不会在使用后被回收, 这是由于 golang 开发者的理念是 "一旦到达过这样的流量, 就说明系统有可能再次面临这样的洪峰, 那么就提前做好准备". 所以我们对于协程, 仍然需要考虑池化的问题. Motan-Go 和 Thrift 的 golang 版本中目前并没有这方面的考虑, 而 Sofa-Mosn 已经做了对应的池化处理.
还有很多其他的性能优化手段, 此处就不再一一列举.
Sidecar 功能的相互影响
我们去做 Servicemesh 的一大初衷, 是为了解决和业务强耦合的现状, 进行了服务治理能力的下沉. 但是, 下沉的时候, 我们发现, 服务治理本身也囊括了非常多的东西, 动态配置, 流控, 熔断, 故障演练, 负载均衡 https://cloud.tencent.com/product/clb?from=10680 , 路由, 通讯, 服务注册发现, 集中式日志, 分布式链路调用, 监控埋点等等. 这些东西都一股脑地揉入到一个单薄的 Sidecar 里. 我们做这一件事的时候, 是不是也需要开始审视一下, 服务治理本身那么多的功能之间是不是也有类似的问题, 会产生组织层面和技术层面的相互干扰, 依赖, 影响甚至是冲突? 没错, 这就是会延伸出来的问题.
比如, 如何确保日志采集这种大规模但不重要的流量, 完全不会影响核心业务流量?
比如, 如何确保某个功能的升级, 不影响核心业务通讯?
比如, 如何多个团队一起维护一个 Sidecar?
......
以上都是可能带来的问题, 当然, 你可以用隔离舱, 可以用热部署, 可以代码仓库想办法分割. 但是, 当一切都下沉的时候, 面对本来七八个团队一起管理维护的能力集, 你真的能很好地解决上述的问题吗?
我们建议, 这种时候, 拆开 Sidecar 吧. 以一定的规则, 基于你的 mesh 所在的发展阶段综合考虑, 拆开你的 Sidecar. 也许拆开它, 一切就都解决了. 需要关注的是, 拆开的 Sidecar 不宜过多, 否则会导致 Sidecar 泛滥而花费高昂的运维升级成本. 所以你看, 是不是和服务化的历程有些类似, 都在 "拆拆拆" 的过程中, 简化问题, 同时引入新的问题? 这才是我们所身处的事业的美妙之处, 因为你总能在一些看似不相关的地方找到他们的相似之处.
multi-Sidecar Arch
只负责服务订阅不负责服务发布?
Pilot 可以进行服务订阅, 并桥接到 XDS 接口体系中. 但是, 为什么没有进行服务注册的能力呢? 笔者猜测这是因为 Servicemesh 是在云原生背景下基于 Local Proxy 所演进而来的. 而 Local Proxy 在云原生方案里面, 基本都不会负责服务注册, 因为他们会将服务注册交给云 (Mesos,Marathon,K8s 等都有现成方案) 来实现, 或者会一般会结合 consul/etcd/zk 单独另起 agent 来完成服务注册. 此时 Local Proxy 则朴素地只关注反向代理的工作.
而这, 对于我们实际生产环境的使用来说, 则显得没那么友好. 因为业务发展到一定阶段, 一般都有自己的一套服务治理框架, 采用自己的服务注册订阅方式. 他们不太可能为了将服务治理 mesh 化, 反而把整套服务订阅发布体系迁移到云原生体系中去, 那就有点本末倒置了. 所以就必然会选择进行适配. 而适配的过程中, 为了使用上服务订阅发布, 他们就不得不深度改造 Sidecar, 加入服务注册的能力, 再让 Sidecar 对接第三方的注册中心. 工作繁琐复杂的同时, 也打破了 Servicemesh 希望控制平面屏蔽基础设施差异性的初衷.
所以笔者认为 Servicemesh 是需要彻底屏蔽掉具体注册中心的存在的. 发布和订阅应该都经过 Pilot, 为使用方提供统一门面. 以后无论注册中心如何切换, 都无需再深度侵入修改 Sidecar.
register & subscription Arch
控制面板和数据面板如何切割?
这是一个老大难的问题. 控制面板中的 Mixer 现在基本是处于一个 "墙倒众人推" 的地步. Istio 比较偏执地将其单拎出来, 处理限流和数据遥测等事宜. 前者会带来很大的性能瓶颈(即使 Istio 后续在 Envoy 中增加了缓存机制也无力回天), 后者会带来双倍的流量消耗. 很多 Servicemesh 的实现都摒弃或者简化了 Mixer. 网上对应的文章有不少, 此处不复赘述.
虽然 Istio 的这一设计太过于理想化, 希望通过这种方式屏蔽基础设施差异性, 然后为 Sidecar 提供无限庞大的内存容量支持, 同时将一些复杂多变逻辑尽可能剔除出 Sidecar, 来保证 Sidecar 尽可能稳定可靠. 但是现实还是很残酷的, 有通讯的地方就会产生问题. 分布式环境的复杂性大半就是由于网络导致的.
而如果将 Mixer 一股脑地下沉, 则不得不面对各种复杂逻辑下如何保障 Sidecar 本身足够稳定可靠, 消耗足够少资源, 引入尽量少依赖, 足够小而美的状态?
虽然控制面试和数据面板的切割是一个很困难的命题, 而 Istio 这方面也引起了一些吐槽, 但是从先驱者的角度上来看, Istio 成功整合了发展了很长时间的 Local Proxy 方案, 并将其上升到了方法论的高度, 成功引发了业内对于控制面板和数据面板的体系化思考, 这才是 Istio 做出的最大贡献, 这是一场从战术到战略的变革, 从术到道的创新.
结语
我们从多个角度, 分析了 Servicemesh 发展至今可能存在的一些问题. 提出了一些基于实际生产经验总结出的解决方案, 希望能给大家带来一些帮助. 当然所有的选择都很难, 我们也没有标准答案, 如何结合各自公司的实际情况 trade-off 才是体现能力和价值的地方. 虽然 Servicemesh 有一些问题, 但是其必然是未来发展的大势所趋. 其能为我们带来极大的想象力和人力的解放, 笔者对此抱有强烈的信心. 猫眼娱乐目前在自研 Servicemesh, 已经在生产环境上进行了灰度部署. 后续我们也会陆续分享猫眼在该领域的实践经验. 也欢迎有兴趣的同学加入.
来源: https://www.qcloud.com/developer/article/1445047