如何快速扩展MySQL数据量?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

在一些评估里,我们(Pinterest)的发展比先前任何初创公司都要快。大约在2011年9月份,我们的基础设施都超过负载。一些NoSQL技术导致灾难性的后果,同时大量用于读的MySQL从属服务器,产生了大量令人恼火的Bug,特别是缓存。我们重新架构了整个存储模型。令人欣喜的是,新架构还是比较有效果的,基本满足了我们的要求。

 

 
 
1
 
业务需求

 

  • 系统总体要非常稳定,便于操作,便于拓展。我们想让数据库能从开始小存储量,能随着业务发展而拓展;

  • Pin友生成的内容必须能永久访问;

  • (支持)请求的N个Pin在板块中以确定的顺序(像按照创建时间倒序,或是按照用户特定的排序)(显示)。对于喜欢的Pin友,发Pin的Pin友列表等,也必须以特定的顺序;

  • 为了简单,更新一般而言要保证最好的性能,为了获取最终一致性,需要额外的东西,如分布式事物日志。这是一个有趣(但不太容易)的事情!

 

 
 
2
 
设计原理与笔记

 

由于我们想要的这些数据是横跨多个数据库的,我们不能使用数据库的join,外键或者收集所有数据的索引,不过他们可以被用于不能横跨数据库的子查询。

我们也需要支持负载均衡我们的数据。我们厌恶来回移动数据,尤其是逐项移动,因为容易出错,也容易让系统变得不必要的复杂。如果我们必须移动数据,最好移动一整个虚拟节点到物理节点。

 

为了实现快速成型,我们需要一个简单可用的解决方案,并且在我们的分布式数据平台上,节点要非常稳定。

 

所有的数据都需要被备份到从节点进行高可用,并为 MapReduce 转存到 S3。 在生产中,我们只与主节点交互。在生产中,你不能在从节点上读/写。从节点是滞后的,它会引发奇怪的bug。如果你共享数据,一般来说在生产中与从节点交互是没有优势的。

 

最终,我们需要一个好的方式来生成一个统一且唯一的ID(UUID)分配给所有我们的对象。

 

 
 
3
 
我们如何切分数据

 

无论我们如何构建系统,都需要满足我们的业务要求并保证系统稳定,具有高性能,易于维护。换言之,我们需要系统不糟糕 ,因此我们选择成熟的技术-MySQL作为我们构建系统的基础。我们有意避免使用具有自动扩展功能的新技术,例如MongoDB,Cassandra 和Membase等,因为他们还不够成熟(并且他们会以无法预知的方式崩溃)。

 

悄悄话:我依旧建议初创公司避免使用花哨的新事物——尝试使用完全能够正常运行的MySQL。相信我,我有很多错误的实践(创伤)来证明这一点。

 

MySQL是成熟的,稳定的并且能够正常运行的。不仅我们使用MySQL,还有大量公司在广泛的使用。MySQL支持我们所需要的数据排序、范围查询功能并且具备行级事务等功能。它还有很多的特性,但是我们不需要或使用这些。尽管MySQL非常适合我们,但MySQL是单一解决方案,因此需要我们对数据进行分片。下面是我们的解决方案:

 

最开始时我们有8个EC2 服务,每个服务对应一个MySQL实例:

 

 

为了应对MySQL服务崩溃,每一个MySQL服务都是应用主复制模式构建,即每一个MySQL服务都将数据备份到一个备份主机上。我们的应用只读写主服务。我建议你也这样做。它简化了一切操作并且避免了复制延迟问题。

 

每个MySQL数据库可以有多个库:

 

 

但每个库的命名唯一,如db00000, db00001, …,dbNNNNN。 每个库都是我们数据的一个分片。我们的决计决策是一块数据只在一个分片中,而不会在其它片中。然而你可以通过增加容量,以便将片移到其它机器上(后续将讨论这个)。

 

我们维护了一个配置表,以记录这些数据分片在什么机器上:

 

 

这个版本仅在我们需要移动分片或是替换某台机器时修改。如果主机挂掉了,我们可以将某台从属服务器升级为主机,然后再加台新的从属服务器。其配置保存在Zookeeper,更新时,配置发送到其它维护分片的MySQL服务器上。

 

每个分片包含相同的表:pins, boards, users_has_pins, users_likes_pins, pin_liked_by_user等等。后面我会再详细讲解。

 

那怎样将数据部署到这些分片上呢?

 

我们创建了一个64位的ID,包含了分片ID,包含数据的类型,及数据在那台服务器上(本地ID)。分片ID是10位,本地ID是36位。精明的专家对此提示只能加到62位。以前编译器和芯片设计的经验已经告诫我,预留位的长度是很重要的,所以我们有两个(设置为0)。

ID = (shard ID

 

给定这个Pin: https://www.pinterest.com/pin/241294492511762325/,让我们开发剖析这个Pin ID 241294492511762325:

 

 

那这个Pin对象保存在3429分片上,它的类型是1(如‘Pin’),及它在pin表的所在行是7075733。 举个例子,让我们假设这个分片是在MySQL012A.上,我们可能通过下面来访问到这个对象:

 

 

这些数据有两个类型:objects(对象)和mappings(映射)。objects包含了具体的数据,例如Pin的数据。

 

 
 
4
 
对象表

 

对象表存储了如Pin数据、用户数据、看板(boards)数据以及评论数据。每个对象都包含一个ID列(本地的ID,自增主键),和一个blob字段包含整个对象的JSON数据。

 

 

例如一个Pin数据:

 

 

在创建新Pin的时候,需要将所有的对象数据收集起来并生成一个JSON blob。然后,需要确定一个分片(shard)ID(我们偏向于选择与它关联(inserted)的看板的分片ID相同的ID,但是没有必要)。Pin的类型码为1。连上数据库并在Pin对象表中插入JSON数据。MySQL会返回一个本地自增ID。一旦生成了分片ID、类型码和本地ID,就可以生成完整的64位ID!

 

编辑Pin的时候可以在一个MySQL事务中以读-修改-写的方式修改JSON数据:

 

 

删除Pin的时候可以删除MySQL中的一行。但是更好的方式是在JSON数据中添加一个‘active’字段,设置值为false,并且在客户端上过滤到这些信息。

 

 
 
5
 
映射表

 

映射表用于建立对象之间的连接,如看板(board)和Pin之间的连接。MySQL中建立的映射表包含3列,一个64位的来源对象ID‘from’、一个目的对象ID ‘to’ 以及一个序列ID ‘sequence’ 。在(from, to, sequence)上建立联合索引,并且每条记录以 ‘from’ 字段的分片ID分片。

 

 

映射表中保存单向映射关系,如看板(board)到Pin的映射表‘board_has_pins’。如果需要反向的映射关系,需要建立一个独立的反向映射表‘pin_owned_by_board’。‘sequence’ 主要用于排序(分片之间是不可以比较的,因为本地ID可能相互是冲突的)。当插入一个新的Pin到看板的时候,通常可以用unix本地的时间戳作为序列号(sequence ID = unix timestamp)。序列号可以是任意的数字,但是unix时间戳是一种使新增的列处于更大的数值的便捷方式,因为时间是单调递增的。查询映射表的方式如下:

 

 

这样可以返回至多50个pin_id,再通过它们获取Pin对象。

 

以上的方法便是一次应用层的连接(board_id -> pin_ids -> pin objects)。应用层连接的一个杰出的特性是可以在独立于对象数据的情况下缓存映射数据。在memcache集群中缓存pin_id到pin对象的映射数据的同时还在Redis集群中缓存board_id到pin_ids的映射数据。这样就可以实现与缓存数据更匹配的缓存技术。

 

 
 
6
 
扩容

 

我们系统有三种主要的扩容方式。最简单的就是升级机器(容量更大、速度更快的硬盘,更多内存,任何成为瓶颈的部分)。

 

第二种方式是开启新的分片范围。虽然我们的分片ID是16位的(最多64k个分片),但是一开始我们只创建了4096个分片。新建的对象只能存储在开头的4k个分片。某天我们决定添加新的MySQL服务器用来存储4096到8191的分片,并且开始填充数据。

 

最后一种方式是将一些分片移动到新的机器上。例如需要对MySQL001A(存储着0到511分片)扩容是,先新建一对主从节点(译者注:此处原文为“a new master-master pair”,可能为笔误),命名为最大的编号(比如是MySQL009A和MySQL009B),然后开始从MySQL001A复制数据。

 

 

当复制完成时,改变配置,使MySQL001A只存储0到255分片,MySQL009A只存储256到511分片。现在每个服务器只处理原先一般的数据量。

 

 

 
 
7
 
一些优点

 

如果你曾构建过生成UUIDs的系统,你会发现这个系统可以轻松地实现这个需求!当你创建一个新的对象并插入到数据库的时候,会得到一个本地ID。这个本地ID和分片ID、类型ID组合便可得到一个UUID。

 

如果你曾用ALTER命令为MySQL数据表添加过字段,你肯定知道这种操作十分缓慢而且十分痛苦。本文的方法不需要任何级别的MySQL Alter操作。在Pinterest的过去三年中,我们可能完成了一次Alter操作。给对象添加新的字段只需要让那些服务知道JSON模式中添加了几个新的字段。当反序列化一个JSON对象的时候如果没有某些字段,此时便可以返回预设的默认值。如果需要新的映射表,只需要创建一个,然后在需要的时候填充数据就可以。完成这些事情就可以发布新产品了。

 

 
 
8
 
取模分片(Mod Shard)

 

取模分片和 Mod Squad(译者注:一部电影)是一样的,唯一的区别是它们完全不同。

 

有一些对象并不是通过ID查询的。比如一个Pin友用Facebook账号登录的时候,我们需要将Facebook的ID映射到Pinterest的ID。Facebook的ID在我们看来就是比特位,所以我们将他们存储到一个独立的分片系统中,并命名为取模分片。包括其他诸如IP地址、用户名、邮箱地址等。

 

取模分片正如和上文中的分片系统类似,除了它们可以以任何数据查询。对查询输入取hash然后以系统中总分片数取模。结果就是数据将被存储或已经存储的位置,如下:

shard = md5(“1.2.3.4") % 4096

 

此例中shard 值会被赋为1524。类似ID分片,需要维护一个配置文件,如下:

 

 

则,查询一个IP地址1.2.3.4的方式,如下:

 

 

但是会因此而失去一些ID分片优势,比如空间分布(spacial locality)。一开始就必须创建好所有分片,并且手动创建分片ID(并不能自动生成)。系统中的所有对象都最好有一个不变的ID。只有这样当一个用户修改他的用户名的时候才不用去更新那些关于这个用户名的引用。

 

 
 
9
 
最后的一些想法

 

至今为止,这个系统在Pinterest已经在线上使用了3.5年了,似乎还会一直用下去。实施这样一套系统相对比较直截了当,但是保持运行并将全部数据从旧机器迁移过来还是相当困难的。如果你在初创公司并且承受着数据增长的痛苦,然后你构建了新的分片机制,并且考虑构建一套集群用于在后台执行脚本把旧数据库的数据迁移到新的分片机器(提示:可以用pyres)。可以肯定的是,无论你做怎样的努力,数据丢失都是不可避免的(我敢肯定它是系统中的捣蛋鬼),所以要一遍一遍重复的迁移数据直到写入新系统的迁移数据为0或者数量小到一定程度。

 

这套系统是最大努力的交付。它没有保证原子性、隔离性和一致性。听起来很糟糕,但是不用着急,因为这些保证可能并不是必须的。如果需要的话,可以在其他的过程或系统中实现这些特性,但是你能直接获得的好处是,这个系统刚刚好可以正常运行。通过简洁可以得到好的可靠性,并且运行起来相当快速。如果你还是担心原子性、隔离性和一致性,请联系我。我可以帮助你解决这些问题。

 

关于故障恢复怎么样呢?我们构建了一个维护MySQL分片的服务。我们把分片配置信息存储到了ZooKeeper。如果一台master服务器宕机了,可以执行一些脚本,将slave升级为master,然后再开启一台替补服务器(包括数据同步)。直到现在我们也没有使用自动的故障恢复。


本文来自云栖社区合作伙伴"DBAplus",原文发布时间:2016-11-01

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
4月前
|
关系型数据库 MySQL 数据库
|
1月前
|
SQL 监控 关系型数据库
MySQL如何查看每个分区的数据量
通过本文的介绍,您可以使用MySQL的 `INFORMATION_SCHEMA`查询每个分区的数据量。了解分区数据量对数据库优化和管理具有重要意义,可以帮助您优化查询性能、平衡数据负载和监控数据库健康状况。希望本文对您在MySQL分区管理和性能优化方面有所帮助。
131 1
|
2月前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:百万数据量的优化实录
【10月更文挑战第6天】 在现代互联网应用中,随着用户量的增加和业务逻辑的复杂化,数据量级迅速增长,这对后端数据库系统提出了严峻的挑战。尤其是当数据量达到百万级别时,传统的数据库解决方案往往会遇到性能瓶颈。本文将分享一次使用MySQL与Redis协同优化大规模数据统计的实战经验。
161 3
|
2月前
|
存储 关系型数据库 MySQL
MySQL 如何查看每个分区的数据量
MySQL 如何查看每个分区的数据量
69 3
|
3月前
|
NoSQL 关系型数据库 MySQL
当Redis与MySQL数据一致性校验中Redis数据量小于MySQL时的全量查询处理方法
保持Redis和MySQL之间的数据一致性是一个需要细致规划和持续维护的过程。通过全量数据同步、建立增量更新机制,以及定期执行数据一致性校验,可以有效地管理和维护两者之间的数据一致性。此外,利用现代化的数据同步工具可以进一步提高效率和可靠性。
60 6
|
4月前
|
存储 关系型数据库 MySQL
深入解析 MySQL 中的扩展
【8月更文挑战第31天】
72 0
|
5月前
|
关系型数据库 MySQL 数据库
|
6月前
|
分布式计算 大数据 关系型数据库
MaxCompute产品使用问题之如何查看数据离线同步每天从MySQL抽取的数据量
MaxCompute作为一款全面的大数据处理平台,广泛应用于各类大数据分析、数据挖掘、BI及机器学习场景。掌握其核心功能、熟练操作流程、遵循最佳实践,可以帮助用户高效、安全地管理和利用海量数据。以下是一个关于MaxCompute产品使用的合集,涵盖了其核心功能、应用场景、操作流程以及最佳实践等内容。
|
6月前
|
Ubuntu 关系型数据库 MySQL
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
91 0
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
|
7月前
|
DataWorks Shell 对象存储
DataWorks产品使用合集之在 DataWorks 中,有一个 MySQL 数据表,数据量非常大且数据会不断更新将这些数据同步到 DataWorks如何解决
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
97 3