前段时间,Oracle官方发布了MySQL 5.7的GA版本。新版本中实现了真正意义的并行复制(基于Group Commit的Group Replication),而再也不是基于schema的并行复制。这一特性极大的改善了特定场景下的主从复制延迟太高的情况。随着MySQL成熟度的提高,愈来愈多的用户选择使用MySQL存放自家的数据,其中不乏使用MySQL来存放大量数据的。前端
在过去的半年多时间里,听云业务量呈爆发式增加,后端的数据量由去年第一季度的几TB增加到几十TB,业务量翻了十几倍。后端应用及数据库面临的一个突出的问题就是频繁的进行扩容来应对前端流量的增加。数据库层面咱们使用MySQL来分布式存储业务数据,数据库集群的架构也比较简单,咱们使用开源中间件Amoeba来实现数据的拆分和读写分离。Amoeba后端有几百个数据库的节点组,每一个节点组中都包含一对主从实例。master实例负责接受write请求,slave负责接受query请求。以下图:mysql
正确的拆分姿式sql
随着可选择的开源中间件愈来愈多,好多数据量并非很大的使用者都会过早的考虑水平拆分数据库。但其实过早的水平拆分未见得是一件有意义的事情。主要缘由有两个:一个方面是水平拆分会对现网的业务形成冲击,若是系统在设计之初就没有考虑事后续要进行拆分的话,这个冲击就会被放大。好比业务中有大量的多表join的查询,或者是对事务有强一致性的要求时,水平拆分就捉襟见肘了。另外一方面,若是过早的进行了水平拆分,那么到达必定程度后再想要垂直进行拆分时,代价是很大的。以听云app为例,当咱们业务库拆成8个分片后,有一天发现数据增加的很快,因而决定对其进行垂直拆分,将小时纬度和天纬度的数据拆分到一个新的实例上去,这时咱们不得不一样时部署8个节点组来将现有的8个分片上的小时纬度和天纬度的数据迁移出来。工做量至关大。若是水平拆分到了64个片,那么这时要想再作垂直拆分,保证累的你不要不要的。数据库
因此更合理的路线是这样的,首先对业务数据进行垂直拆分,本来一个库按业务单元垂直拆分红多个库,同时应用中配置多个数据源或者使用中间件来访问拆分后的多个库,对应用自己来讲,基本没作什么改动,可是后端存储的容量和性能却翻了好几倍。若是某天出现瓶颈以后,再来考虑水平拆分的事情。后端
优雅的从n到2n架构
水平扩展过程当中最让人头疼的是数据的迁移,以上图中迁移mod(mobile_app_id,4)=2的数据为例,最开始的作法是先建立两个新的节点组shared0_new和shared2,拿shared0的全备恢复到shared0_new和shared2,而后在shared0_new上删除mod(mobile_app_id,4)=2的数据,在shared2上删除mod(mobile_app_id,4)=0的数据,删除操做完成后shared0_new、shared2与shared0作同步,同步删除操做执行过程当中的数据增量。同步追上以后,切换amoeba的路由规则,而后下线shared0。这种方式问题不少,首先时耗很高很高,delete完了以后并不能释放存储空间,还要optimize table,一样也是一个漫长的过程。针对大表的delete会产生一个很大的transaction,会在系统表空间中申请很大一块undo,delete完成后事务提交。这个undo空间并不会释放,而是直接给其余事务复用,这无疑会浪费不少存储空间。app
后来咱们想到一个便捷的办法,就是利用mysqldump的—where参数,在备份数据的时候加一个mod(mobile_app_id,4)=2的参数,就能够单独备份出余数为2的数据,而后拿这个逻辑备份恢复到shared2上去,高效且优雅。运维
数据倾斜分布式
MySQL分布式存储不可避免的一个问题就是数据倾斜。业务在运行一段时间以后,会发现少部分shared数据增量特别快,缘由是该shared上面部分用户的数据量较大。对于数据倾斜问题咱们目前的措施是将这些shared迁移到1TB存储上来,但这并不是长久之计。所以咱们目前正在作一些新的尝试,好比对Amoeba作了一下扩展,扩展后的Amoeba支持将某一个mobile_app_id的数据单独指向后端一个shared节点组,即一个shared只存放一个用户的数据,同时采用ToKuDB存储引擎来存储这部分数据,ToKuDB可以对数据进行有效的压缩,除了查询性能稍有损耗以外,基本具有InnoDB引擎所拥有的特色,并且在线表结构变动比InnoDB快好几倍不止。这些测试基本已经进入尾声,很快将会应用到生产环境。工具
分布式join
分布式join在业界仍没有完美的解决方案,好在听云业务在设计之初就从业务上避免了多表的join,在业务库中,报表中的每一个纬度都会有一张表与之对应,所以查询某个纬度直接就会查询后端的某张表,都是在每张表上作一些操做。目前比较流行的分布式join的解决方案主要有两种:
一、全局表的形式。举个例子,A表 join B表,B表分布式存储在多个shared上,若是A表比较小,能够在全部的shared上都存一份A表的全量数据。那么就能够很高效的作join。看起来很美好,可是限制不少,应用的场景也颇有限。
二、E-R形式。举个栗子,用户表user(id,name)和订单表order(id,uid,detail),按用户id分片,order表的uid引用自user表的id。存放订单时,首先肯定该订单对应用户所在的shared,而后将订单记录插入到用户所在的shared上去,这样检索某个用户全部的订单时,就能够避免跨库join低效的操做。
目前的开源中间件中,MyCat对分布式join处理的是比较细腻的。阿里的DRDS对于分布式join的处理也是这样的思路。
MySQL擅长什么
任何一种工具可能都只是解决某一个领域的问题,确定不是放之四海而皆准的。正确的使用方式是让工具作本身擅长的事情。关系型数据库擅长的是结构化的查询,自己并不擅长巨量数据的清洗。咱们在出2015年度APP行业均值数据报表时,须要将后端全部shared上的相关数据汇总起来而后作进一步的分析,这些数据最终汇总在5张表中,每张表都有几亿条的记录。而后对五、6个字段group by以后取某些指标的 sum值,最初尝试在MySQL中处理这些数据,MySQL实例给出24GB的内存,结果OOM了好几回也没有出结果。最后把数据拉到了hadoop集群上,使用impala引擎来汇总数据,处理最大的表近7亿条记录,9min左右出结果。因此,不要有all in one的想法,要让系统中的每一个组件作本身擅长的事情。
分布式MySQL架构下的运维
MySQL分布式虽然解决了存储和性能问题,可是在运维支持过程当中却带来了一些痛点。
一、跨分片统计数据。中间件是没法对后端的全量数据作查询的,相似年度APP行业均值报表这样的跨分片的全量数据的查询,只能使用自动化脚本从后端逐个shared上提取数据,最终再汇总。
二、DML。常常会有变动表结构的需求,这样的操做大部分中间件是支持不了的,若是只有一个库好说,当后端几十个shared时,就比较头疼了,目前咱们并无很好的处理办法,只能使用自动化脚本批量到后端shared上执行命令,执行完成后,运行一个校验的脚本,人工核对校验脚本的输出内容。
应对这样的情景,发型必然会稍显凌乱,可是目前仍旧很无奈,有必要从新设计一下咱们的脚本,写一个输出更加友好,彻底自动化的工具出来。