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 的配置考入了镜像来固化配置。也就是说,当修改配置时无法生效。若容器因为某种原因宕机,不是重启容器,而是删除容器,再使用同样的参数,从最新版本镜像(若目标变更,则从新镜像)重新创建一个新容器,并启动它。
这种做法的好处是:
配置的核心容易被管理。配置融入了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秒循环检查系统实际状态同目标状态记录的信息是否一致:
1. 检查容器是否已经运行。若没有,则根据配置创建一个并启动;
2. 检查容器是否有复制拓扑。若无,则修复:
3. 基于角色检查MySQL各种 参数(例如:read_only 、 super_read_only、 sync_binlog等),如:主节点可写,从节点为只读模式等等。此外,为了降低从节点的运行负载可关闭日志同步等相关参数。
4. 启停任何支持的容器功能,例如关闭 pt-heartbeat 心跳检测和pt-deadlock-logger死锁检测。
注意:Uber特别认同这种单进程只做单用途的容器设计理念,因为不用对已运行的容器进行重新配置,更容易控制或升级。
何时何地只要发生错误,进程只用报错或者退出。然后尝试重新启动。但架构会确保在各个代理之间尽量减少交互,也就是说集群不关心执行顺序。例如,在创建一个新的集群时,手工创建需以下步骤:
创建MySQL 主节点并等待生效;
创建第一个从节点并连接到主节点;
剩余的从节点重复以上步骤。
必然会发生一些意外,但我们并不关心严格的执行顺序,只要最终状态达到我们设定的目标就行:
“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比以往的版本都要有优势,但也有以下限制:
docker inspect 在Docker运行几天后偶尔会hang住;
TCP连接终端使用 userland proxy 协议的桥接网络模式可能会有一些奇怪的现象,客户端经常会收不到RST 信号,无论如何设置超时,客户端均保持opend 状态;
容器进程偶尔会恢复到初始状态,Docker便会对进程失去控制;
最常见的案例是:Docker守护进程需要很长时间才能创建一个新的容器。
总结
我们在Uber的集群存储上提了大量需求:
一台主机运行多个容器
自动
单点登录
现在,我们已经可以通过简单的工具和统一的UI界面进行日常维护,而无需登录主机。
在一台主机上运行多个容器提升主机利用率。在可控模式下进行整体升级。Docker已经为我们的技术架构提升了很多,也支持测试环境下整体集群的本地化升级,及试运行所有操作系统进程。
从2016年开始Uber实施系统Docker化的迁移,目前已运行了1500多个生产服务器(仅MySQL ),并运行了2300多个MySQL 数据库。
虽然应用Docker是技术上的重大成功,也使得Uber架构进步更快。但比Schemadock项目本身意义更大的是,整个出行数据仓库,几乎每天百万次出行记录已经运行在了Docker上的MySQL 中。另一个角度想,Docker成为使用Uber出行打车的关键。
本文为翻译文章,点击阅读原文链接即可查看原文。