不少时候因为MySQL技术致使的灾难性的后果,同时大量用于读的MySQL从属服务器,产生了大量使人恼火的Bug,特别是缓存。为了解决这个问题咱们就须要从新架构整个存储模型,今天和你们分享的就是如何快速扩展MySQL数据容量的相关操做,但愿能够帮助你们更好的学习MySQL ,一块儿来看看吧。数据库
1 业务需求缓存
·系统整体要很是稳定,便于操做,便于拓展。咱们想让数据库能从开始小存储量,能随着业务发展而拓展;服务器
·Pin友生成的内容必须能永久访问;架构
·(支持)请求的N个Pin在板块中以肯定的顺序(像按照建立时间倒序,或是按照用户特定的排序)(显示)。对于喜欢的Pin友,发Pin的Pin友列表等,也必须以特定的顺序;app
·为了简单,更新通常而言要保证最好的性能,为了获取最终一致性,须要额外的东西,如分布式事物日志。这是一个有趣(但不太容易)的事情!负载均衡
2 设计原理与笔记分布式
因为咱们想要的这些数据是横跨多个数据库的,咱们不能使用数据库的join,外键或者收集全部数据的索引,不过他们能够被用于不能横跨数据库的子查询。性能
咱们也须要支持负载均衡咱们的数据。咱们厌恶来回移动数据,尤为是逐项移动,由于容易出错,也容易让系统变得没必要要的复杂。若是咱们必须移动数据,最好移动一整个虚拟节点到物理节点。学习
为了实现快速成型,咱们须要一个简单可用的解决方案,而且在咱们的分布式数据平台上,节点要很是稳定。spa
全部的数据都须要被备份到从节点进行高可用,并为 MapReduce 转存到 S3。 在生产中,咱们只与主节点交互。在生产中,你不能在从节点上读/写。从节点是滞后的,它会引起奇怪的bug。若是你共享数据,通常来讲在生产中与从节点交互是没有优点的。
最终,咱们须要一个好的方式来生成一个统一且惟一的ID(UUID)分配给全部咱们的对象。
3 咱们如何切分数据
不管咱们如何构建系统,都须要知足咱们的业务要求并保证系统稳定,具备高性能,易于维护。换言之,咱们须要系统不糟糕 ,所以咱们选择成熟的技术-MySQL做为咱们构建系统的基础。咱们有意避免使用具备自动扩展功能的新技术,例如MongoDB,Cassandra 和Membase等,由于他们还不够成熟(而且他们会以没法预知的方式崩溃)。
悄悄话:我依旧建议初创公司避免使用花哨的新事物——尝试使用彻底可以正常运行的MySQL。相信我,我有不少错误的实践(创伤)来证实这一点。
MySQL是成熟的,稳定的而且可以正常运行的。不只咱们使用MySQL,还有大量公司在普遍的使用。MySQL支持咱们所须要的数据排序、范围查询功能而且具有行级事务等功能。它还有不少的特性,可是咱们不须要或使用这些。尽管MySQL很是适合咱们,但MySQL是单一解决方案,所以须要咱们对数据进行分片。下面是咱们的解决方案:
最开始时咱们有8个EC2 服务,每一个服务对应一个MySQL实例:
为了应对MySQL服务崩溃,每个MySQL服务都是应用主复制模式构建,即每个MySQL服务都将数据备份到一个备份主机上。咱们的应用只读写主服务。我建议你也这样作。它简化了一切操做而且避免了复制延迟问题。
每一个MySQL数据库能够有多个库:
那这个Pin对象保存在3429分片上,它的类型是1(如‘Pin’),及它在pin表的所在行是7075733。 举个例子,让咱们假设这个分片是在MySQL012A.上,咱们可能经过下面来访问到这个对象:
这些数据有两个类型:objects(对象)和mappings但每一个库的命名惟一,如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:
(映射)。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,而后再开启一台替补服务器(包括数据同步)。直到如今咱们也没有使用自动的故障恢复。
文章来源:开源中国