# 高并发的哲学原理（三）-- 基础设施并发：虚拟机与 Kubernetes（k8s）

上篇文章说到，Apache 无法处理海量用户的 TCP 连接，那要是由于宇宙时空所限，你的系统就是无法离开 Apache，该怎么承接高并发呢？有办法：既然单机不行，那就把单机虚拟化成多个 Linux 机器，就能顶得住高并发了。

## Apache 成为了单点，那就把它拆了

基于虚拟化做多节点集群，可以相对线性地提升系统性能，你只要在前面搭建一个负载均衡即可。

一个负载均衡服务器 + 多个上游服务器的技术架构正是“找出单点，进行拆分”思想的具体体现。如果 Apache 顶不住，那我们就仔细思考一下，Apache 架构中，到底哪个单点是不好拆的，哪些部分是可以并行的？

### 监听 443 端口的进程（root 用户）是单点

显然，监听 HTTPS 443 端口的进程就是肉眼可见的单点，而执行 PHP 的进程在一台服务器上本身就是并行的。

（其实监听单点也能拆，只是我们在这一步还不需要拆，后面会拆。）

### 跑 PHP 的进程（apache 用户）可以并行

在单机上，PHP 解释器进程本身就是并行的，那我们把它拆到多台机器上，就是顺理成章的事情。

### 百亿架构

下面是我为自研电商设计的可伸缩技术架构：
![](https://qn.lvwenhan.com/2022-12-29-16722875590335.jpg)

经过一年多的运行，我发现它撑到年 GMV 100 亿问题不大，以今年十多亿的 GMV 为例，目前 2 核 4G Kong 网关虚拟机的 CPU 占用最大只到了 20%，那 8 核 Kong 网关应该就能顶住 100 亿的冲击了。

这套架构的核心是两个东西：Kong 网关和服务发现。

### Kong 网关

虚拟机技术无法单独提升系统容量，因为必须有东西来承接“监听 443 端口”这个单点，在这里我选择了 Kong 网关作为 HTTP 反向代理服务器，将用户的海量 HTTPS 请求 handle 住，再以 HTTP 协议发送到后端的多台 2 核 4G 虚拟机上。

Kong 是基于 `国产 OpenResty 技术` 开发的开源网关，OpenResty 将非常轻量、高效的 Lua 语言嵌入进了 Nginx 生命周期，大幅扩展了 Nginx 的功能。Kong 除了拥有 HTTP 网关的一整套功能外，还有插件系统和基于数据库的水平扩展能力，理论上可以支撑超高并发的系统。这部分具体的内容我们会在后面《百万人民币的负载均衡硬件以及它的软件替代方案》里面做详细的讨论。

### 服务发现

在流量低谷期我们使用一台虚拟机跑 Apache 作为上游服务器，在开团前需要再开出来一台上游服务器的话，Kong 该怎么知道这台服务器开出来了，并且获取到他们的 ip 呢？答案是使用`服务发现`技术。

像 Kong 一样，HashiCorp 开源的 consul 也可以独立于 k8s 环境运行，于是我选择了它来做服务发现。服务发现的原理说起来其实非常简单：构造一个共识集群，各个节点之间会使用固定的端口相互通信，当新的上游服务器开机后，它上面的 consul 服务会开机启动，之后就会和配置好的一个 master 的 ip 进行通信，加入这个集群，并广播本机可以提供的服务名称，假设名叫`up`。这时，同样在集群内的 Kong 网关服务器就可以通过它本机上安装的 consul 提供的 DNS 服务拿到新加入集群的这台机器的 ip 了。

Kong 向 consul DNS 发送一个查询：查找`up.service.consul`这个域名对应的 ip，之前，这个域名只会返回一个 ip，在新机器开机并成功加入集群后，这个域名对应的就是两个 ip 了，Kong 就可以把 HTTP 请求均匀地发送给新旧两台上游服务器了。

好了，基础架构准备就绪，下面我们深入一下本文的主题：基础设施并发。

## 何为基础设施并发

虚拟机是第一代基础设施并发技术：既然由于软件架构的限制导致单机性能有上限，那我就把多核服务器拆成多机。因为现在服务器核心数越做越多，除了 web 服务，周边的 MySQL、Redis、ElasticSearch 等也需要计算资源，将多核服务器虚拟化，除了可以提升系统性能，还能减少资源浪费，换个词就是：省钱。在今天整个行业都在降本增效的大背景下，省钱无疑是技术潮头。

而 k8s 容器编排技术是最新一代基础设施并发技术：可以把它看成一种更优秀且更容易进行自动化管理的虚拟机技术。

### 虚拟化技术的价值

不知道屏幕前的读者有没有想过，在一台裸金属服务器的操作系统内直接运行 Apache、MySQL、Redis、ElasticSearch，和开四台虚拟机分别运行他们，到底有什么区别。系统设计哲学不是“如无必要勿增实体”吗？虚拟机技术到底有什么价值来让我们新增四个实体呢？

#### 1. 资源隔离

后端技术拥有很多成熟的开源软件，每个软件都拥有独特的发展路径，百花齐放，群魔乱舞。如果把所有服务都装在裸金属服务器上，理论上讲使用内存管道通信的效率比虚拟机之间通过虚拟以太网通信还要更高，但是资源隔离就难做了。

拥有自我资源限制能力的软件不多，大部分软件都是“有多大屁股穿多大裤衩”：根据请求压力任意获取计算资源，这就让计算资源的分配容易出现问题，产生木桶效应，最终导致系统整体性能反而更低。

虚拟化技术通过将虚拟机和物理核心进行强绑定，可以无限接近 **“将一台 64 核服务器拆成 32 台 2 核服务器”** 的目标，内存也可以进行完全预分配，虚拟机之间的相互干扰可以做到最低。

#### 2. 降低运维复杂度

市面上成熟的监控软件都是基于操作系统的，进程也能监控，但是比较困难。控制虚拟机也比控制进程要更加容易。当然，控制 k8s 容器平台更加灵活高效，我们下面会讲到。

虚拟机显著降低了对每个重要服务进行资源监测的复杂度，大幅提升了运维效率，甚至实现了第一代的“自动运维”，时至今日，VMWare 在超融合领域也拥有统治地位：基于三台服务器的小集群，就可以提供高可用架构的全套基础设置：统一应用网关、共享存储、虚拟机热漂移、自动故障转移等。如果只用一台物理服务器直接安装 Linux，很显然这些高可用特性都做不到，一旦这台物理机的某个部件发生了故障导致宕机的话，全部服务都会挂掉。

#### 3. 隐藏价值：性能氮泵

由于很多软件都拥有 Apache 一样的“单机性能极限”，所以虚拟化技术将单机拆成多机，就可以直接实现性能增益。如果你把 Apache、Redis、MySQL、ElasticSearch 都装在裸金属操作系统上的话，他们就会异口同声说：给我那么多核心有什么用，我真的用不上啊。

Apache 我们前文已经讲过，下面我们讨论一下其他几种技术的“单机性能极限”。

#### Redis 的单机性能极限

Redis 作为单线程内存数据库，两核就能做到十万 QPS，你给他 32 核，还是十万 QPS，对资源是一种浪费。

在前几天 Redis 回应新兴内存数据库 Dragonfly 挑战的文章中，Redis 维护团队建议大家搭建“单机集群”¹ 来实现对机器上所有 CPU 的完全利用，Redis 团队的内心 OS 一定是：宝宝心里苦但宝宝不说。

而且，基于虚拟化技术搭建多虚拟机上的 Redis 集群，是一种比单机集群更加稳妥的高性能 Redis 解决方案。

#### MySQL 的单机性能极限

在今天这个 NVME PCI-E 4.0 时代，单个闪存磁盘的读写速度已经站上了 7GB/S，MySQL 的单机 CPU 使用强度已经大大提高，但是，如果我拿最新的双路 AMD EPYC 9654 96 核服务器来跑单机 MySQL 呢？显然单节点 MySQL 无论如何是无法吃完 192 个物理核心，384 个线程的，而它的性能极限也会停在几万 QPS。

实际上，标准 MySQL 的性能瓶颈在内存容量和磁盘速度，对 CPU 资源的需求并不是很高，在两台虚拟机拥有独立固态硬盘的情况下，把 16 核虚拟机拆成两台 8 核虚拟机并打造成一个半同步一主一从的 MySQL 集群，便可以轻松将数据库的读性能提升一倍，只需要稍微牺牲一丢丢写性能。

web 系统的瓶颈在数据库，数据库的瓶颈在存储，这个话题很大，我们后面有三篇文章专门讨论数据库和存储。

#### ElasticSearch 的单机性能极限

ES 作为一个 Java 应用，显然拥有着明确的单机性能极限：线程切换需要读写内存，性能上限就在那里。ES 自己就采用了自选举集群架构来实现水平扩展：官方建议单节点不要超过 32GB 内存。

如果你的母机 CPU 和内存很多，就可以基于虚拟机技术做多节点 ES 集群，ES 集群的扩展能力是非常不错的：作为一个搜索引擎，大家竟然都拿他当数据库用，从这里就足以看出它优秀的水平扩展能力。

### k8s 技术的本质

容器技术是谷歌给人类社会的又一个重大贡献，不仅开发了 Cgroups 并将其合并进了 Linux Kernel（2008 年），还开源了自己的容器编排工具 Kubernetes（简称 k8s，2014 年开源），而在这两件事之间，2013 年 Docker 为容器世界贡献了一个决定性的新思想：镜像技术。

#### 镜像技术开天辟地

Docker 发明了镜像，并提出了 build、ship、run 的概念，通过将整个操作系统的文件系统打包进容器镜像，真正实现了`一次构建、处处运行`。而在这之前，只有拥有完整操作系统的虚拟机才能做到这一点，而 Java 则号称自己能做到。

#### git 一样的镜像

比容器思想更为重要的是，Docker 项目还在容器镜像的制作上引入了“层”的概念，这种基于“层”（也就是“commit” ) 进行 build，push，update 的思路，显然是借鉴了 Git 的思想。所以这个做法的好处也跟 GitHub 如出一辙：制作 Docker 镜像不再是一个枯燥而乏味的事情，因为通过 DockerHub 这样的镜像托管仓库，你和你的软件立刻就可以参与到全世界软件分发的流程当中了²。

Docker 的成功主要就是靠的 DockerHub，它将开源软件推进了一大步：开源软件可以直接分发 **“下载即可用”** 的标准化软件镜像，大家可以像在开源社区协作一样，在镜像社区协作，一层一层地搭建出适合自己的镜像，人类作为群居动物的本能嘎的一下就觉醒了。

#### 软件架构本质上是软件维护团队的组织架构

稍微扯远一点，说说软件架构和运维架构。我认为，容器、镜像、容器编排的兴起，与其说是一种技术创新，不如说是一种生产关系的创新：在越来越大的服务器数量面前，以前那套基于虚拟机的管理技术不再适用了，Google 需要一种少数人管理数十万台服务器的新技术，于是 k8s 诞生了。

正如软件架构本质上是软件维护团队的组织架构，运维架构本质上也是运维团队的组织架构：规模大到必须自动化，于是容器编排便自然而然地出现了（唯物史观有木有）。

> 重要的不是电影拍摄(讲述)的年代，而是拍摄电影的年代。
> —— 戴锦华（北京大学教授，电影评论家）

#### k8s 是一种更加优秀的基础设施并发技术

k8s 是人类面对暴增的物理服务器和上面数不清的虚拟机的管理难题时想出的一种解决方案，正如设计模式是面向对象编程思想对现实问题的一种妥协。

以 k8s 为代表的容器编排技术，就是一种更容易被软件自动化管理的“虚拟机”，它可以实现虚拟机一样的功能——资源隔离、降低运维复杂度和性能氮泵，而且，它还可以在裸金属环境下快速部署出一个数千台服务器的集群。虽然虚拟化技术研发出了很多像 SR-IOV 一样的硬件直通技术，但无论是物理网卡性能还是虚拟网卡性能，都不如容器技术：五年前我在一台非常老的服务器上做过超过一天的压测，docker 的内存网关又快又稳，可以实现超过万兆的带宽，并持续 24 小时一个包都不丢。

传统虚拟机镜像巨大、启动缓慢、多个操作系统同时运行带来了资源浪费、迁移缓慢、管理困难，而 k8s 镜像很小，启动迅速，资源损耗低，管理 API 丰富，更是可以轻易实现“单机多实例部署”和“目录共享”，是海量服务器新时代的优秀基础架构。

k8s 就是云时代的虚拟机 + 操作系统。

### 还记得我们的目标吗？一百万 QPS

通过使用负载均衡 + 虚拟机，或者负载均衡 + k8s，在依然使用前面那台双路 E5-2682 V4 64 vCore 的情况下，我们可以把单个系统的 QPS 从上篇文章的 1000 提升到 5000。

（实际上在这个 QPS 下，数据库早就成为瓶颈了，这个问题我们留到后面几篇文章再详细讨论。）

### 接下来

下一篇文章，我们将讨论隐藏在语言背后的魔鬼：运行架构为何会成为性能瓶颈。编程语言的性能差异是程序员社区经久不衰的话题，但当你对各种技术的了解越深入，就越能感受到各种语言的本质区别：不同语言的设计方向不同，就像时间换空间、空间换时间，他们只是选择了一种优势找信息之神换成了另一种优势罢了。

> [高并发的哲学原理（四）-- 隐藏在语言背后的魔鬼：运行架构为何会成为性能瓶颈](https://github.com/johnlui/PPHC/tree/main/04.%20%E9%9A%90%E8%97%8F%E5%9C%A8%E8%AF%AD%E8%A8%80%E8%83%8C%E5%90%8E%E7%9A%84%E9%AD%94%E9%AC%BC%EF%BC%9A%E8%BF%90%E8%A1%8C%E6%9E%B6%E6%9E%84%E4%B8%BA%E4%BD%95%E4%BC%9A%E6%88%90%E4%B8%BA%E6%80%A7%E8%83%BD%E7%93%B6%E9%A2%88)

### 参考资料

1. Redis 维护团队建议大家搭建“单机集群” https://redis.com/blog/redis-architecture-13-years-later/
2. 你和你的软件立刻就可以参与到全世界软件分发的流程当中了 http://liupzmin.com/2019/11/06/docker/container-chat/
