随着业务容器化的推进,经常有客户抱怨应用 QPS 无法和在物理机或者云主机上媲美,并且时常会出现 DNS 查询超时、短连接 TIME_OUT、网络丢包等问题,而在容器中进行调优与诊断的效果因为安装工具的复杂度大打折扣。本文基于网易轻舟中间件业务容器化实践,总结容器场景下的性能调优心得,供读者参考。

1性能调优的“望闻问切”

在讨论容器化场景的性能调优之前,先谈一下性能调优中的“望闻问切”。对于性能问题,大部分人首先想到的是 CPU 利用率高,但这只是个现象,并不是症状。打个比方:感冒看医生时,病人跟大夫描述的是现象,包括头部发热、流鼻涕等;而大夫通过探查、化验,得到的医学症状是病人的白细胞较多、咽喉红肿等,并确诊为细菌性感冒,给开了 999 感冒灵。性能调优流程与此相似,也需要找到现象、症状和解法。回到 CPU 利用率高的例子:已知现象是 CPU 利用率高,我们通过 strace 检查,发现 futex_wait 系统调用占用了 80% 的 CPU 时间——这才是症状;根据这个症状,我们业务逻辑代码降低了线程切换,CPU 利用率随之降低。

大部分的性能调优都可以通过发现现象、探测症状、解决问题这三个步骤来完成,而这在容器的性能调优中就更为重要的,因为在主机的性能调优过程中,我们有很多的经验可以快速找到症状,但是在容器的场景中,很多客户只能描述问题的现象,因为他们并不了解使用的容器引擎的工作原理以及容器化架构的实现方式。

2容器化性能调优的难点

容器化场景中的性能调优主要面临 7 个方面的挑战。

 中间件种类多,调优方法又各不相同,如何设计一种好的框架来适配

我们借鉴 redhat tuned 思想,提出一种与业务相关的内核参数配置新框架,它可以把我们对 Linux 系统现有的一些调优手段(包括电源管理工具,CPU、内存、磁盘、网络等内核参数)整合到一个具体的策略 (profile) 中,业务场景不同,profile 不同,以此来快速实现云计算对不同业务进行系统的性能调节的需求。

 VM 级别的调优方式在容器中实现难度较大

在 VM 级别我们看到的即是所有,网络栈是完整暴露的,CPU、内存、磁盘等也是完全没有限制的。性能调优老司机的工具箱安个遍,诊断流程走一趟基本问题就查个八九不离十了,但是在容器中,很多时候,都是默认不自带诊断、调优工具的,很多时候连 ping 或者 telnet 等基础命令都没有,这导致大部分情况下我们需要以黑盒的方式看待一个容器,所有的症状只能从物理机或者云主机的链路来看。但是我们知道容器通过 namespace 的隔离,具备完整网络栈,CPU、内存等通过隔离,只能使用 limit 的资源,如果将容器当做黑盒会导致很多时候问题症状难以快速发现,排查问题变难了。

 容器化后应用的链路变长导致排查问题成本变大

容器的场景带来很多酷炫的功能和技术,比如故障自动恢复、弹性伸缩、跨主机调度等,但是这一切的代价是需要依赖容器化的架构,比如 Kubernetes 网络中需要 FullNat 的方式完成两层网络的转发等,这会给排查问题带来更复杂的障碍,当你不清楚编排引擎的架构实现原理的时候,很难将问题指向这些平时不会遇到的场景。例如上面这个例子中,FullNat 的好处是降低了网络整体方案的复杂性,但是也引入了一些 NAT 场景下的常见问题,比如短连接场景中的 SNAT 五元组重合导致包重传的问题等等,排查问题的方位变大了。

 不完整隔离带来的调优复杂性

容器本质是一种操作系统级虚拟化技术,不可避免涉及隔离性,虽然平时并不需要考虑隔离的安全性问题,但是当遇到性能调优的时候,内核的共享使我们不得不面对一个更复杂的场景。举个例子,由于内核的共享,系统的 proc 是以只读的方式进行挂载的,这就意味着系统内核参数的调整会带来的宿主机级别的变更。在性能调优领域经常有人提到 C10K 或者 C100K 等类似的问题,这些问题难免涉及到内核参数的调整,但是越特定的场景调优的参数越不同,有时会有“彼之蜜糖,我之毒药”的效果。因此同一个节点上的不同容器会出现非常离奇的现象。

 不同语言对 cgroup 的支持

这个问题在大多数场景下无需考虑,列在第四位是期望能够引起大家重视。网易轻舟在一次排查“ES 容器(使用 java 11)将 CPU requests 都配置成 8 时,其性能低于将 request CPU 都配置成 1”的问题时,发现是 Java 的标准库中对 cgroup 的支持不完全导致的,好在这点在大多数场景中没有任何影响。

 网络方案不同带来的特定场景的先天缺欠

提到容器架构避不开网络、存储和调度,网络是评判容器架构好坏的一个核心标准,不同的网络方案也会有不同的实现方式与问题。比如网易轻舟 Kubernetes 中使用了 Flannel 的 CNI 插件实现的网络方案,标准 Flannel 支持的 Vxlan 的网络方案,Docker 的 Overlay 的 macVlan,ipvlan 的方案,或者 OpenShift SDN 网路方案,还有网易轻舟自研的云内普通 VPC 和 SRIOV+VPC 方案等等。这些不同的网络方案无一例外都是跨宿主机的二层网络很多都会通过一些 vxlan 封包解包的方式来进行数据传输,这种方式难免会增加额外的 CPU 损耗,这是一种先天的缺欠,并不是调优能够解决的问题。有的时候排查出问题也只能绕过而不是调优。

 镜像化的系统环境、语言版本的差异

应用容器化是一个需要特别注意的问题,很多公司并没有严格的配管流程,比如系统依赖的内核版本、语言的小版本等等,很多时候都是选择一个大概的版本,这会带来很多语言层级的 BUG,比如 Java 应用程序运行在容器中可能会遇到更长的应用程序暂停问题、Java 7 无法感知 CPU 个数导致 GC 线程过多问题和 PHP7.0 中 php-fpm 的诡异行为。环境的问题本就需要严格管控,但是当遇到了容器,很多时候我们会分不清哪些不经意的行为会带来严重的问题,警惕性因为容器镜像能够正常启动而降低了。

3性能优化步骤和调优管理变更流程

性能优化通常可以通过如表五个步骤完成:

调优管理变更和性能优化并不直接相关,但可能是性能调优成功最重要的因素。以下总结来源于网易轻舟团队的实践和经验:

4调优手段

1. 通用调优

对于容器业务来说,尽量让 CPU 访问本地内存,不要访问远端内存。

CPU 访问不同类型节点内存的速度是不相同的,访问本地节点的速度最快,访问远端节点的速度最慢,即访问速度与节点的距离有关,距离越远访问速度越慢,此距离称作 Node Distance。正是因为有这个特点,容器应用程序要尽量的减少不同 Node 模块之间的交互,也就是说,我们根据容器内存 Node 亲和性,选择容器使用的 CPU 固定在一个 Node 模块里,因此其性能将会有很大的提升。有一种特殊场景除外,如果一个容器申请的 request 大于单个 Node 上预留的 CPU 后,这种亲和性的绑定就会失效,此时回归到原始的跨 Node 范围绑定,对于之前已经做了亲和性的容器(申请的 request 小于单个 Node 上预留的 CPU)我们的策略是继续维持不变。

2. 针对不同的场景的参数调优

针对不同的场景,可以考虑以下参数着手进行调优。

与CPU相关的配置

与内存相关的配置

磁盘相关的参数

与sysctl相关的配置

注:了解以上网络参数的具体含义,需学习 Linux 内核收发包原理,强烈推荐《Monitoring and Tuning the Linux Networking Stack》系列文章。详见文末参考文献 4-7。与 sysctl 相关的配置

3. 网络调优

软中断隔离:将容器网卡的软中断绑定到某几个专用 CPU 上,而容器业务进程绑定到其他 CPU 上,这样可以减少业务和网卡软中断之间的影响,避免频繁的上下文切换,特别适用于对网络性能要求极高的服务例如 Redis。对于云内普通 VPC 和云内 SR-IOV VPC 都适用:

5调优效果

 Redis

调优后 SR-IOV 下 QPS 接近 BGP 物理网络,99.99% 时延从 BGP 物理网络的 990ms 下降到 140ms。

 Flink

调优后简单 ETL 任务 QPS 比 YARN 上高 20%,复杂 ETL 任务 QPS 比在 YARN 上高 30%。

 RDS

对于 RDS MGR 集群,K8S 容器部署相比 RDS2.0 云主机 VM 部署,同等规格下性能提升可以达到 30%~170%;

经过优化后,与物理机部署相比,常规模式下,只写场景、只读场景和读写混合场景的性能差距保持在 5~10% 之间。

 RocketMQ

异步复制集群:普通容器单分片相比物理机性能有 40% 差距,增加生产消费者数量,集群整体性能有所提升,但依然与标准物理机有 25% 左右差距;增加生产消费者数量,容器调优后的性能基本持平标准物理机,差距 5% 以内。

同步复制集群:普通容器性能略差于标准物理机,差距在 10% 左右,容器调优后的性能基本持平标准物理机,差距缩小到 5%。

运营多个专注于各自技术业务领域的平台比运营一个平台团队更好。平台思维是关于促进演进的,而不是关于重用的,而专注于重用会破坏这种机会。将平台思维灌输到所有团队中,给自主领域和平台出现的机会,而不应强加于事。

超过一定规模的大多数技术公司都开始考虑创建一个内部平台团队来构建 / 管理供多个团队 / 产品使用的系统。这是一个杠杆率非常高的团队,因为他们可以同时对许多产品产生有益的影响,并为能给组织带来巨大的动力。但是,今天,我想提出一些内部平台团队无法顺利工作的情况,至少我遇到过这样的情况。

TL;DR——运营多个专注于各自技术业务领域的平台比运营一个平台团队更好。平台思维是关于促进演进的,而不是关于重用的,而专注于重用会破坏这种机会。将平台思维灌输到所有团队中,给自主领域和平台出现的机会,而不应强加于事。

1这个团队是做什么的?

拥有一个独立的平台团队通常意味着所有水平关注点都会被推到他们身上。最终,这个团队将无法识别它的客户,只能以支持许多有用但互不关联的系统而告终。很难为这样的一家公司设定目标和“北极星”,因为它们“从定义上”就是与最终用户脱钩的。这样团队的唯一目的是在不考虑这些系统的目的和运行这些系统所需的专业知识的情况下,最大限度地重用组织内的这些系统。

更糟糕的情况是,其他团队会毫无顾虑地构建可重用的组件。但是,当涉及到在生产中支持重用、尊重其他团队的 SLO 和 SLA 时,人们会立即开始寻找平台团队来进行操作。其结果是,一个操作繁重的中央小组一直忙于灭火,因为他们对自己所拥有的东西缺乏了解。

随着公司共享用例数量的增加,平台团队将会变成各种无连接的组件的存储库,这些组件的业务用途与这些组件是分离的。最终,我们得到了一个“团队”,但这个团队的成员根本不知道其他人在做什么,因为每个成员最终都只专注于各自的某个部分。我们有技术专家,但他不是对业务影响最大的领域专家。如果业务开始陷入困境,平台团队及其工作通常是第一个被砍掉的。这是因为很难向业务方解释这批“准专家”是如何帮助他们赚钱。

2开发人员淘金热

从根本上讲,对于一个组织的技术栈,采用流行的二维视图是不合理的。它会使我们产生偏见,认为底层的事物在本质上更基础或更复杂。当然,底层比上层支持更大的“规模”。所有这些都会导致开发人员急于加入刚刚孵化出来的内部平台团队。在某种程度上,这项工作被视为是对组织来说更具声望或更为核心的。事实上,在本质上讲,它充其量是更技术性的,这样开发人员就不必处理现实世界的混乱,不必面向客户的产品了。

这在很多方面对组织构成了挑战。一个是管理谁来做什么,以及哪些角色被认为很酷(为什么非平台角色不被视为“业务特性”,而是“出色的工程”)。另一部分是管理最终形成团队的期望值,创建团队很容易,但是要让他们保持以业务 / 客户为中心比预期的要困难。许多平台团队都深陷到了一种混合状态中,即技术傲慢且与客户脱离,这使得他们的实力远远低于他们的能力。

3其他团队就不是平台团队?

如果有一个平台团队,那么是否意味着其他团队就不是平台团队了?有了一个“无所不能”的团队,其他团队开始放弃平台思维,开始在产品孤岛中思考。

如果平台化是有价值的,那么它应该是所有团队的心态和策略,而不是单个团队的领域。由于所有业务问题都是 由领域和组织环境所组成的,因此有理由认为,所有团队都应该生产和运营平台化组件。这些平台可以在其他团队所拥有的平台之上生成。平台或通用产品的目的不仅仅是重用,而且要为集中组织专业知识的领域边界进行建模。平台团队不能在任何出现水平组件的地方都继续使用它们,因为那样他们必须是业务各个方面的专家。拥有底层可重用事物的平台团队的典型定义与组织的工作方式并不兼容。

这导致我们……

4所有权范围

一个公司的技术栈可以通过顶层的高阶系统抽象和底层的低阶系统抽象进行可视化。这意味着,操作架构的技能集可以随着你的深入而发生很大的变化。这就证明了较低层与较高层的“不同”,应该以不同的方式进行管理。

当我们看技术栈时,所有权范围应该如何运行?它们应该沿着具有类似抽象级别的组件水平运行,还是应该垂直运行,将系统分组以生成端到端的业务价值?

如果你正在运行一个自主的、跨职能的团队(许多公司都声称要这样做),那么技术栈的深度是否重要?这个团队的基本前提是能够在技术栈的所有级别上工作,并且在每个级别上与其他团队都能保持松散的一致性。在这种情况下,建立一个具有固定水平章程的平台团队的想法是毫无意义的。每个团队都会创建自己需要的平台层,并根据需要与其他团队共享。这样可以保持低开销的对齐流程的运行,因为创建组件不仅仅是为了重用,它首先是为了使用而创建的,然后才是在需要时再进行重用。

这种所有权还解决了孤立组件的问题,每个人都非常依赖这些孤立组件,但又没有一个团队对其进行维护。开源对于编写代码来说是个好主意,但对于操作来说却是个糟糕的主意。软件必须有一个可操作的所有者——一个负责确保软件按预期运行并达到预期基准的团队。自主团队操作他们构建的东西,如果我们可以教会他们如何构建平台,那么我们就不需要明确地创建平台团队了。

话虽如此,但反对自主团队的一大论点是,他们往往最终不得不做一堆重复的工作。这显然是次优的,那么我们如何才能将其最小化呢。

5解决重用问题

这个问题可以通过以平台化的方式解决所有问题来最小化,这样,一旦被创建出来,所有团队都可以从出现的系统中获益。在调整发布日期等方面可能存在短期问题,但从长远来看,某个特定功能或实体的所有用例开始集中在一个地方。

然而,这并不意味着构建这个平台的团队不再拥有它。重用并不意味着将所有权与创建分离。一个设计良好的平台旨在将平台团队排除在客户的决策周期之外(外部可编程性),因此,如果一个通知平台是由营销团队构建的,并且它被其他许多人所采用了,那么营销团队可以继续拥有并运营它,这没什么错。

一旦一个可重用的东西找到了 3 个以上的客户(Atwood 定律),那么可能是时候考虑一下这是一个横向的责任,并把它转移到一个单独的团队。我并不是说把它转移到一个通用平台团队,而是转移到一个特定的团队,这个团队可以专攻组件建模领域,并致力于将其发展以使其所有客户都受益。例如, 一个通知系统可以完全启动一个全新的商业领域,它本身就是小一点的 Twilio。或者,它可能只是一种共享的技术能力,如托管 Elasticsearch,可以由存储平台团队 /ES 平台团队中的存储 /Elasticsearch 专家团队来处理。

上面的例子表明,如果团队最初拥有其工作中的所有抽象级别(也就是全栈团队),那么新的团队和领域可以从每个团队的工作中派生出来,这些团队和领域可以在相同的抽象级别上产生。从某种意义上说,新领域是一个全新的可重用组件。我们通过对组件进行分层来垂直扩展组织,并且这些层是在彼此之上创建可重用组件的。我们还通过添加需要解决的全新问题空间来水平扩展组织,而每一个问题空间反过来又会带来新的深化机会。

这种有机的过程与固定的、共同定位的平台宪章的理念背道而驰。“平台团队”的概念混淆了所有权和重用的概念,正如它混淆了技术栈中平台的“深度”与领域知识的概念一样。我认为最好是从特定领域的团队角度来考虑,每个团队都开发自己的平台,并在发现冗余或重用时跨团队转移并合并。

在我看来,今天唯一的平台团队是基础设施团队(管理硬件设备),也许还有身份验证 / 授权团队,甚至我认为这些都是业务 / 技术能力,而不是重用 / 技术栈深度驱动的团队。所有其他平台都应该来自产品团队内部。想想细胞分裂而不是神之手。