如何将AI能力与大数据技术结合,助力数据分析治理等工作的效率大幅提升,优化大数据引擎的性能及成本? 了解详情
写点什么

优步工程团队对容器化 MySQL 的应用

  • 2016-11-24
  • 本文字数:5369 字

    阅读完需:约 18 分钟

优步工程团队的 Schemaless 存储系统驱动着优步内部一些最大规模的服务,例如 Mezzanine 。Schemaless 是一种基于 MySQL [1] 集群的可缩放、高可用数据存储。当我们最初只有 16 个集群时,这些集群的管理工作相当简单,但是目前我们有超过 1000 个集群,其中承载了超过 4000 个数据库服务器,我们的管理工具也需要与时俱进了。

最初所有集群都通过 Puppet 管理,我们根据需要陆续编写了大量脚本,但这种手工的操作方式已经无法跟上优步快速扩张的节奏。在为数量逐渐增加的 MySQL 集群选择更适合的管理方式时,我们也确立了一些基本需求:

  • 在每台宿主机上运行多个数据库进程
  • 一切操作实现自动化
  • 跨越多个数据中心,通过一个位置管理并监控所有集群

我们设计了一种名为 Schemadock 的解决方案。所有 MySQL 均运行在 Docker 容器内,我们通过在配置文件中定义的集群拓扑为目标状态(Goal state)对其进行管理。集群拓扑决定了 MySQL 集群的组成方式,例如集群 A 应包含 3 个数据库,其中某个数据库为主数据库。随后通过代理(Agent)将定义好的拓扑应用给每个数据库。我们会通过一个集中化的服务维持并监视每个实例的目标状态,并对偏差做出响应。

Schemadock 包含多个组件,其中的 Docker 虽小但很重要。换为使用缩放性更强的解决方案,这样的过程需要付出诸多努力,本文将介绍我们如何借助 Docker 实现这个目标。

为何一开始就选择使用 Docker?

以容器化的方式运行进程,使得我们可以轻松地用一台宿主机同时运行多个不同版本和配置的 MySQL 进程。此外我们也可以将多个小规模群集共置在一台宿主机上,这样即可在确保集群数量不变的情况下减少宿主机数量。最终,我们可以消除对 Puppet 的依赖,将所有宿主机以相同的角色进行供应。

对于 Docker 本身,我们的工程师已经开始全面采用 Docker 构建无状态(Stateless)服务。这意味着我们针对 Docker 积累了大量工具和知识。Docker 虽不完美,但至少目前比其他备选技术更优秀。 为何不使用 Docker?

可以取代 Docker 的技术包括完整虚拟化技术、 LXC 容器,以及通过 Puppet 等工具管理宿主机上直接运行的 MySQL 进程。对我们来说,选择 Docker 是一种很自然的做法,因为该技术可以融入我们现有的基础架构。然而如果你目前尚未开始使用 Docker,单纯为了 MySQL 就上马 Docker 这样的做法有些得不偿失:需要处理镜像的构建和分发,监控,Docker 的升级,日志收集,网络等一系列繁琐工作。

这一切也意味着,只有在愿意投入不菲的资源前提下,Docker 对你来说才是可行的。此外你应该将 Docker 视作一种技术,而非应对所有问题的终极解决方案。优步通过大量慎重的规划设计才使得 Docker 能够成为 MySQL 数据库管理这一庞大系统中的一个小组件,然而并非所有公司能有优步这样的规模,对这些公司来说,也许更简单直接的 Puppet 或 Ansible 等方式是最适合的做法。

Schemaless MySQL Docker 镜像

作为其他所有内容的基础,我们的 Docker 镜像会下载并安装 Percona Server ,然后启动 mysqld —这一过程与现有的 Docker MySQL 镜像相差无几。然而在下载和启动过程之间我们的镜像会执行很多其他操作:

  • 如果挂载的卷中不包含数据,则可以确定这是一个自举(Bootstrap)场景。对于主数据库(Master),将运行mysql_install_db并创建一些默认用户和表。对于从属数据库(Minion),将会通过备份或集群中的其他节点发起数据同步操作。
  • 容器包含数据后,将会启动 mysqld。
  • 如果数据复制过程失败,容器将重新关闭。

容器的角色可通过环境变量来配置。这里比较有趣的地方在于,角色仅控制了初始数据的获取方式,Docker 镜像本身不包含用于设置复制拓扑或执行状态检查等操作的任何逻辑。由于逻辑的变化可以比 MySQL 自身的变化更频繁,因此将两者区分开来是一种合理的做法。

MySQL 数据目录将从宿主机的文件系统直接挂载,这意味着 Docker 不会产生写操作的负担。不过我们会将 MySQL 的配置直接放入镜像中,使其成为常量。虽然可以更改配置,但由于 Docker 容器绝对不会重新使用,因此配置的改动也绝对不会生效。如果因为某些原因必须关闭一个容器,我们不会将这样的容器重新启动。我们会删除容器,使用最新镜像通过相同参数(如果目标状态有变化则使用新参数)重建一个容器,然后重新启动实例。

这样的做法为我们提供了多个收益:

  • 更易于控制配置漂移。有变化的最多只是 Docker 镜像的版本,并且我们会密切监视版本变化。
  • MySQL 的升级过程更简单。我们会新建一个镜像,随后按顺序关闭老的容器。
  • 如果任何地方出错,只须重头再来。不再需要考虑该如何打补丁,抛弃原有的一切新建所需容器即可。

镜像的构建工作也是通过驱动无状态服务的同一个优步基础架构完成的。该基础架构会将镜像复制到所有数据中心,使其可在每个数据中心的本地注册表(Registry)中使用。

用一台宿主机运行多个容器也有相应的劣势。由于容器间无法进行恰当的 I/O 隔离,一个容器可能占用掉所有可用的 I/O 带宽,导致其他容器开始卡顿。Docker 1.10 引入了 I/O 配额功能,但我们尚未对该功能进行过测试。目前我们主要通过避免超额订阅(Oversubscribing)宿主机,以及对每个数据库进行持续监控等方式降低这一问题的影响。

Docker 容器的调度和拓扑的配置

在具备了可作为主数据库(Master)或从属数据库(Minion)配置并启动的 Docker 镜像后,需要通过某种方式启动这些容器并配置恰当的复制拓扑。为此我们在每台数据库宿主机上运行了一个代理(Agent)。该代理可以获取每台宿主机上应该具备的所有数据库的目标状态信息。一个典型的目标状态是类似这样的:

复制代码
“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。该处理环路会通过下列操作检查目标状态与系统的实际状态是否匹配:

  1. 检查是否有一个容器已经在运行。如果没有,则使用配置创建一个并将其启动。
  2. 检查该容器是否应用了正确的复制拓扑。如果不正确,则尽量修复。
  • 如果本应是主数据库但实际为从属数据库,首先确认能否安全地更改其角色。为此我们会检查原本的主数据库是否为只读的,并且所有 GTID 均已收到并应用。一旦符合要求,即可安全地删除到原本主数据库的链接并启用写入。
  • 如果是主数据库但本应禁用,则开启只读模式。
  • 如果是从属数据库但复制未运行,则设置复制链接。
  1. 根据具体角色检查各种 MySQL 参数(read_onlysuper_read_onlysync_binlog等)。主数据库应当是可写的,从属数据库应当是只读的。此外我们会关闭 binlog fsync 以及其他类似参数 [2] 以降低从属数据库的负载。
  2. 启动或关闭任何其他用于提供支持的容器,例如 pt-heartbeat pt-deadlock-logger

这里需要注意,我们会尽可能采用单进程、单用途容器这种做法。这样就无需重新配置运行中的容器,并且升级的过程也更易于控制。

如果任何一点出现错误,执行过程将抛出错误信息并将其忽略。整个过程会在下次运行时重试。我们会尽可能确保不同代理之间只需要最少量的协调。这意味着我们并不关心具体顺序,例如供应新集群时的供应顺序。如果用手工的方式供应新集群,可能需要执行类似下面的操作:

  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”
}
}

上述信息会以随机的顺序推送给相关代理,随后代理会开始执行操作。为达到目标状态可能需要重试多次,者主要取决于执行顺序。通常可以在几次重试或实现目标状态,但某些操作可能需要重试上百次。举例来说,如果首先处理的是从属数据库,此时将无法连接至主数据库,并只能稍后重试。由于主数据库可能要等一段时间才能启动并正常运行,因此从属数据库可能需要重试多次:

(点击放大图像)

两个从属数据库先于主数据库启动的范例。最初的启动阶段(第1 和第2 步)中,从属数据库无法从获得主数据库的快照,因此启动过程失败。随后主数据库在第3 步中顺利启动,从属数据库即可通过第4 和第5 步连接并同步数据。

Docker 运行时的使用体验

我们的大部分宿主机运行了 Docker 1.9.1,并使用 LVM 中的devicemapper作为存储。我们发现使用 LVM 中的devicemapper在执行效果方面要比回调(Loopback)的devicemapper更好。devicemapper在性能和可靠性方面有不少问题,但 AuFS OverlayFS 等替代品的问题也不少 [3] 。这意味着整个社区对最佳存储选项方面还有不少的争议。目前 OverlayFS 的改进最多,似乎已经逐渐变得稳定了,因此我们准备在升级到 Docker 1.12.1 之后切换为使用 OverlayFS。

升级 Docker 最大的痛苦之一在于需要重启动,并且需要重启动所有容器。这意味着升级过程必须进行必要的控制,确保升级宿主机时没有处于运行状态的主数据库。希望 Docker 1.12 能成为我们必须关注这个问题的最后一个版本。1.12 提供了在不重启动容器的前提下重启动并升级 Docker 守护进程(Daemon)的选项。

每个新版本在带来大量改进和新功能的同时,也不可避免引入了一些瑕疵和问题。1.12.1 看起来比老版本改进了很多,但我们依然面临一些局限:

  • Docker 成功运行多天后,遭遇docker inspect时不时挂起的情况。
  • 配合 Userland 代理(Proxy)使用桥接网络会遇到 TCP 连接终止的怪异问题。无论配置多长的超时值,客户端连接有时候会完全无法收到 RST 信号并始终保持打开状态。
  • 容器进程偶尔会重置(Reparented)至 Pid 1(init),这意味着 Docker 与容器的连接丢失。
  • 我们经常看到 Docker 守护进程新建容器所需的时间变得很长。

总结

优步对存储集群的管理有下列几个需求:

  1. 同一台宿主机运行多个容器
  2. 自动化
  3. 单点管理和访问

现在我们已经可以通过一个界面用简单的工具执行日常维护操作,所有操作均不需要直接访问宿主机:

管理控制台截图。在这里可以追踪目标状态进度,本例中我们首先添加第 2 个集群,随后断开复制链接,借此将一个集群一分为二。

通过在一台宿主机上运行多个容器,可以更充分地利用宿主机资源,借此即可用受控的方式对整个环境进行升级。Docker 的使用让我们可以更快速实现这一切,Docker 还使得我们能够在本地测试环境中运行完整的集群配置工作,借此对各种操作过程进行实验。

我们从 2016 年初开始向 Docker 迁移,目前共运行了约 1500 台生产用 Docker 服务器(仅用于运行 MySQL),同时我们已经供应了大约 2300 个 MySQL 数据库。

有关 Schemadock 可说的还有很多,但 Docker 组件为我们提供了极大的帮助,使得我们可以更快速地演进并尝试各种实验,同时还能很好地融入现有的优步基础架构。我们的整个行程数据库每天会收到数百万条行程信息,现在我们可以将这样的数据库通过 Docker 化的 MySQL 与其他数据库环境共置一处。换句话说,Docker 已成为向乘客提供服务过程中的一个关键组件。

Joakim Recht 是优步工程团队位于阿尔路斯办公室的一名软件工程师,同时也是 Schemaless 基础架构自动化技术的技术负责人。

作者:JOAKIM RECHT,阅读英文原文 DOCKERIZING MYSQL AT UBER ENGINEERING


[1] 严格来说应为 Percona Server 5.6

[2] sync_binlog = 0以及innodb_flush_log_at_trx_commit = 2

[3] 部分问题见这里: https://github.com/docker/docker/issues/16653 https://github.com/docker/docker/issues/15629 https://developerblog.redhat.com/2014/09/30/overview-storage-scalability-docker/ https://github.com/docker/docker/issues/12738


感谢陈兴璐对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-11-24 16:292900
用户头像

发布了 283 篇内容, 共 94.3 次阅读, 收获喜欢 54 次。

关注

评论

发布
暂无评论
发现更多内容

GQM 概述:构建研发效能度量体系的根本方法

思码逸研发效能

研发效能 创新方法 效能度量

阿里云架构师唐风:生命科学产业现状及发展趋势分享

阿里云弹性计算

高性能计算 生命科学 AI制药

TDengine 如何进行数据建模?

TDengine

数据库 tdengine 开源

面向商业市场,华为式“抢滩登陆”

脑极体

4种Kafka网络中断和网络分区场景分析

华为云开发者联盟

后端 开发 网络 网络中断

2种数据库覆盖式数据导入方法介绍

华为云开发者联盟

数据库 大数据 后端

小白 0-1 学习 app 开发,从配置到 helloword

APICloud

跨平台 安卓 低代码开发 多端开发

wallys/DR8072V01/IPQ8072A networking SBC supports dual 10GbE, WiFi 6

wallys-wifi6

【Unity】绘制阿基米德螺旋线

萧然🐳

Unity 7月月更

东方甄选品控翻车,如何通过智能协同的供应链建设建开启可持续商业模式?

数商云

数字化转型 供应链 企业数字化

一体化实时HTAP数据库StoneDB,如何替换MySQL并实现近百倍分析性能的提升

StoneDB

云原生 #数据库 HTAP 大数据 开源 #开源

SpringBootAdmin 2.5.5 发布,支持在线重启服务

冉然学Java

编程 springboot 构架 Java’

开源代码难阅读?几位研发的“妙招”帮你解决

TDengine

数据库 tdengine 开源

让预训练语言模型读懂数字:超对称技术发布 10 亿参数 BigBang Transformer [乾元]金融大规模预训练语言模型

亚马逊云科技 (Amazon Web Services)

架构 数据 模型

让智慧物联赋能高效生产, AIRIOT助力数字化油田转型升级

AIRIOT

低代码 物联网 低代码,项目开发

oa办公系统都有哪家?

优秀

OA oa办公系统

升哲科技入选《中国企业家》2022年度“新锐100”企业

SENSORO 升哲科技

Golang生成OpenAPI接口文档

百家饭隐私计算平台创业者

Go OpenAPI

编写Dockerfile,让你的程序一键部署

技术小生

Dockerfile 7月月更

云原生时代,金融企业如何完成全栈信创改造?

秒云

云原生 信创 国产化 金融信创 全栈改造

建木持续集成平台v2.5.1发布-全面拥抱云原生架构

Jianmu

云原生 k8s 持续集成 CI/CD

推理实践丨如何使用MindStudio进行Pytorch模型离线推理

华为云开发者联盟

人工智能

复杂查询so easy ,GaussDB(for Cassandra)推Lucene引擎全新解决方案

华为云开发者联盟

数据库 后端

2022 开放原子全球开源峰会 OpenAnolis 分论坛携干货来袭!

2022开放原子全球开源峰会

centos 开源 龙蜥操作系统 开放原子全球开源峰会 OpenAnolis

清源(CleanSource) SCA推出容器镜像扫描功能

安势信息

容器 安全 SCA 容器镜像 容器镜像Docker

工作中养成的工作习惯与给老板的汇报

松子(李博源)

大数据 个人成长 高效 高效率 工作总结

共建开源人才生态,2022 开放原子全球开源峰会聚焦 “产学研用”

2022开放原子全球开源峰会

开源 数字化 产学研用 开放原子全球开源峰会

构建工业软件开源工具链,2022 开放原子全球开源峰会开源工业软件论坛即将开幕

2022开放原子全球开源峰会

开源 开放原子全球开源峰会 开源工业软件

企业自己如何快速开发一个简单实用的CRM客户管理系统?

优秀

CRM系统

RadonDB MySQL Kubernetes 2.2.0 发布!

RadonDB

MySQL Kubernetes 云原生 容器化 RadonDB

内部排序——交换排序

乔乔

7月月更

优步工程团队对容器化MySQL的应用_MySQL_Joakim Recht_InfoQ精选文章