BY JOAKIM RECHT
【编者的话】:Uber 使用的 Schemaless 存储系统支撑了 Uber 最重要的服务,如, Mezzanine 等。Schemaless 是一个构建在 MySQL 集群上,可扩展高可用的数据存储。但管理 Uber 数据量庞大的数据库集群服务需要应用 Docker 技术。
当集群节点数为 16 个时,集群管理非常容易,但若集群规模超过 1000,并运行了 4000 多个数据库服务,就需要另一种工具了。之前所有的集群都由 Puppet 来管理。大量的临时脚本,以及人工操作已无法满足 Uber 业务扩展的要求。我们开始寻找一个管理规模递增的 MySQL 集群工具,工具必须具备以下基本需求:
解决方案被命名为 Schemadock,采用 Docker 容器运行 MySQL,目标是将管理集群拓扑定义在配置文件中。集群拓扑定义了 MySQL 集群,例如,一个集群内应用 3 个数据库,其中一个是主,代理应用这些拓扑定义在每个数据库上,一个集中服务用来维护和监控各实例的目标状态,并及时纠正偏差。
Schemadock 由几个部件组成,Docker 是其中一小部分但却是最重要的部分。转型到这种可扩展的方案需要付出相当大的努力,本文将介绍 Docker 如何帮助系统达成目标:
为何 Docker 成为首选?
容器化进程可实现不同版本及配置的 MySQL 进程运行在同一台主机上,也可以在同一个主机上配置不同小集群用来实现在更少的主机上运行同样规模集群的目的。最终,可实现不依赖 Puppet,并且所有主机都可配置成一样的角色。
对于 Docker 自身,工程师可在 docker 上构建微服务。关于 Docker 已经有大量的工具及知识累积,虽然 Docker 肯定不是最好的,但目前来讲是最佳选择。
为何不使用 Docker 呢
选择 Docker 包括全虚拟化、LXC 容器、或通过类似 Puppet 工具来管理运行在主机上的 MySQL 进程。对我们而言,选择 Docker 是因为简单,因为它很适合现有的架构。但如果没准备好使用 Docker,而只想使用 MySQL,这便是一个巨大的工程:包括完成镜像建立、发布、监控、升级 Docker、日志收集、网络设置、等等工作。
这意味着,若要使用大量资源,就只能选择 Docker。此外,Docker 只是一种技术,而不是一个解决方案。在 Uber 的方案中详细规划了一个以 Docker 为核心部件的大型系统,用于管理 MySQL 数据库。但并不是所有公司都像 Uber 一样广泛使用 Docker,因为一些更简单的方案例如 Puppet 或 Ansible 可能更适合。
Schema 无关的 MySQL Docker 镜像
在这些基础上,Docker 镜像只用下载、安装 Percona Server、启动 mysqld,仿佛 Docker 镜像已经在那一般。然而,在下载和启动之间,会出现很多问题:
容器的角色通过环境变量来定义。而这个角色仅负责接收初始化数据,Docker 镜像本身不包含任何建立复制拓扑、状态检查等逻辑。由于这些逻辑比 MySQL 本身变动更为频繁。因此需要花大力气实现分离。
MySQL 数据目录通过主机的文件系统实现挂载,意味着 Docker 没有任何写开销,实际部署时将 MySQL 的配置考入了镜像来固化配置。也就是说,当修改配置时无法生效。若容器因为某种原因宕机,不是重启容器,而是删除容器,再使用同样的参数,从最新版本镜像(若目标变更,则从新镜像)重新创建一个新容器,并启动它。
这种做法的好处是:
Uber 通过建立镜像支持微服务架构,此架构从数据中心复制镜像到本地激活注册使用。
但在一个节点上运行多个容器也有缺点,因为在每个容器之间没有合适的隔离 I/O 的功能,一个容器可能占满所有 I/O 带宽,导致其他容器故障。Docker 1.10 新增了 I/O 限额功能,但是目前还没有应用过。不过我们通过降低主机负载和持续监控数据库性能来预防此问题。
规划 Docker 容器及配置拓扑
现在我们有一个可以启动的 Docker 主或从节点镜像,为了启动这些容器并将它们配置到正确的复制拓扑,每台数据库节点上需要运行一个代理,用来接收不同节点上的所有数据库的目标信息。典型的目标状态信息实例如下:
- "schemadock01-mezzanine-mezzanine-us1-cluster8-db4": {
- "app_id": "mezzanine-mezzanine-us1-cluster8-db4",
- "state": "started",
- "data": {
- "semi_sync_repl_enabled": false,
- "name": "mezzanine-us1-cluster8-db4",
- "master_host": "schemadock30",
- "master_port": 7335,
- "disabled": false,
- "role": "minion",
- "port": 7335,
- "size": "all"
- }
}
这段信息表明:在节点 schemadock01 上需在 7335 端口上运行一个 Mezzanine 数据库从节点,并启动 schemadock30:7335 的数据库主节点。这里的'all'参数是说该节点上只运行数据库,因此需要分配所有的内存给数据库使用。
创建目标状态对另一个请求来说就是一个会话,下一步:主机上运行的代理接收到这个信息后存储在本地,并开始处理。
处理方式是一个周期为 30 秒的无线循环,类似每 30 秒运行 Puppet 一样。通过以下步骤每隔 30 秒循环检查系统实际状态同目标状态记录的信息是否一致:
3、基于角色检查 MySQL 各种 参数(例如:read_only 、 super_read_only、 sync_binlog 等),如:主节点可写,从节点为只读模式等等。此外,为了降低从节点的运行负载可关闭日志同步等相关参数。
4、启停任何支持的容器功能,例如关闭 pt-heartbeat 心跳检测和 pt-deadlock-logger 死锁检测;
注意:Uber 特别认同这种单进程只做单用途的容器设计理念,因为不用对已运行的容器进行重新配置,更容易控制或升级。
何时何地只要发生错误,进程只用报错或者退出。然后尝试重新启动。但架构会确保在各个代理之间尽量减少交互,也就是说集群不关心执行顺序。例如,在创建一个新的集群时,手工创建需以下步骤:
1、创建 MySQL 主节点并等待生效;
2、创建第一个从节点并连接到主节点;
3、剩余的从节点重复以上步骤。
必然会发生一些意外,但我们并不关心严格的执行顺序,只要最终状态达到我们设定的目标就行:
- "schemadock01-mezzanine-cluster1-db1": {
- "data": {
- "disabled": false,
- "role": "master",
- "port": 7335,
- "size": "all"
- }
- },
- "schemadock02-mezzanine-cluster1-db2": {
- "data": {
- "master_host": "schemadock01",
- "master_port": 7335,
- "disabled": false,
- "role": "minion",
- "port": 7335,
- "size": "all"
- }
- },
- "schemadock03-mezzanine-cluster1-db3": {
- "data": {
- "master_host": "schemadock01",
- "master_port": 7335,
- "disabled": false,
- "role": "minion",
- "port": 7335,
- "size": "all"
- }
}
这个信息将被随机推送到相关代理,并启动运行。为达到最终目标状态,会有一些重试操作。通常经过几次重试后都会最终达到目标状态,但有一些操作可能需要 100 多次的重试。例如,从节点最先被启动,无法连接到主节点,然后不得不重启。因为主节点启动并运行需要一段时间,所以从节点需要重启很多次。
Docker 运行实践
大部分主机运行的是带设备映射的 Docker 1.9.1 版本使用 LVM 实现存储管理。应用 LVM 进行设备映射比使用 loopback 的运行性能优异得多。设备映射本身存在很多性能和可靠方面的问题,但是若是选择使用 AuFS 或 OverlayFS 也存在大量问题。目前在社区中关于存储的选型存在很大的争议。目前为止,OverlayFS 获得了一定的认可,运行比较稳定,因此我们确定了选择它,并将 Docker 的版本升级到 1.12.1。
Docker 升级的痛点之一是需要重启所有的容器。这意味着升级时没有主节点,升级进程必须可控。希望 Docker1.12.1 是最后一个要担心这个问题的版本,1.12 已经有一个选项可以控制重启和升级 Docker 守护进程时无需重启容器。
Docker 的每个版本都有一些进步和新特性,同时也会有一些漏洞。1.12.1 比以往的版本都要有优势,但也有以下限制:
1、docker inspect 在 Docker 运行几天后偶尔会 hang 住;
2、TCP 连接终端使用 userland proxy 协议的桥接网络模式可能会有一些奇怪的现象,客户端经常会收不到 RST 信号,无论如何设置超时,客户端均保持 opend 状态;
3、容器进程偶尔会恢复到初始状态,Docker 便会对进程失去控制;
4、最常见的案例是:Docker 守护进程需要很长时间才能创建一个新的容器;
总结
我们在 Uber 的集群存储上提了大量需求:
1、一台主机运行多个容器;
2、自动
3、单点登录
现在,我们已经可以通过简单的工具和统一的UI界面进行日常维护,而无需登录主机。
在一台主机上运行多个容器提升主机利用率。在可控模式下进行整体升级。Docker 已经为我们的技术架构提升了很多,也支持测试环境下整体集群的本地化升级,及试运行所有操作系统进程。
从 2016 年开始 Uber 实施系统 Docker 化的迁移,目前已运行了 1500 多个生产服务器(仅 MySQL ),并运行了 2300 多个 MySQL 数据库。
虽然应用 Docker 是技术上的重大成功,也使得 Uber 架构进步更快。但比 Schemadock 项目本身意义更大的是,整个出行数据仓库,几乎每天百万次出行记录已经运行在了 Docker 上的 MySQL 中。另一个角度想,Docker 成为使用 Uber 出行打车的关键。
来源: