分库分表的基本原理

  1. 分库分表的场景
    首先提供一组mysql的性能数据:对于一个MySQL实例,CRUD上限经验值如下:
    Query:3w/s
    Insert:2w/s
    Update:8000/s
    Delete:8000/s
    出处:https://blog.csdn.net/weixin_41545330/article/details/80734844
    而大型的互联网应用中,数据量是非常庞大的,上亿甚至是数十亿都是非常常见的事情,同时也会伴随着非常高的读写并发,很容易就会突破单个数据库实例的性能上限。单单的读写分离(master->slave)模式,所有的写并发还是落在同一个主库上,无法扩展写的并发能力,同时也只能有限地扩展读的并发能力,因为受master的同步能力影响,挂载的从库数量是有限的。
    分库分表的思想是把一个数据库分成多个部分,存放在多个数据库上,把一个大表拆成多张小表,从而有效缓解单个数据库或者单个表的读写压力,让数据库服务获得无限扩展的能力。

  2. 常见的分库分表策略
    2.1 分库分表的策略有垂直划分和水平划分。先上图:
    这里写图片描述
    图片出处https://www.cnblogs.com/panxuejun/p/5958879.html
    2.2 垂直划分
    垂直划分是指可以考虑将业务上相近,并且具有相近数据增长速率、访问压力的数据放在同一个数据源里,反之分到不同的数据源。举例:一个在线教育的的应用包含用户模块、刷题模块,课件模块等,可以按照不同的模块分别分到不同的数据库。而用户模块包含的信息有账号密码、用户姓名、性别、生日、星座、爱好、积分等。其中用户登陆认证的访问最频繁,而生日、星座、爱好、积分等数据访问的频率会显著低于账号密码,那么又可以根据访问的频率不同而分到不同的表。如下表:
    这里写图片描述
    2.3 水平划分
    经过垂直分库后,单个业务内数据量和并发量依旧非常庞大,则需要进一步进行水平划分。比如提供刷题服务的功能模块,用户提交的答案数据会非常惊人。水平划分通常是先确定主表,将主表与其关联表和间接关联表划分到同一个sharding。例如刷题模块,确定答题卡表为主表,那么与其关联的答案表也一起划分到同一个sharding.
    2.4 常见的水平划分策略
    常见的水平划分策有连续分片和随机分片。连续分片,比如确定userId为关键字,userId在(0,10000000]在shard_0,userId在(10000000,20000000]在shard_1。随机分片,常常是通过对关键字进行hash取模进行分片,例如确定userId为关键字,划分成32个分片,则index=userId%32.
    连续分片和随机分片的的优缺点如下:
    扩容方面,连续分片更加容易扩容,当关键字增长到达某一个段的时候,添加新的分片即可,不需要进行数据迁移。而随机分片在进行扩容的时候需要进行数据迁移。
    数据热度方面,连续分片很多时候存在数据热度不一致的问题,比如1年前的微博数据和现在的微博数据热度差异巨大,连续分片的情况下会导致各个库的访问压力不均匀。而随机分片,数据热度通常更加均匀。补充提一下,在随机分片中,也可能会存在超级Id,这些Id的记录远远超过其他Id,比如电商平台,可能苹果手机的访问数远超过其他商品,针对这些超级ID,通常是使用独立的分片进行特殊处理。
    而对于随机分片的数据迁移问题,也有一些优化的策略。比如采用一致性哈希算法进行分片,那么每次扩容的时候,所要迁移的数据量则会大大减少。而如果数据量的上限是已知的,则还可以进一步进行优化,比如笔者做过一个业务,用户基于手机号码进行注册注销,数据量最多也是14亿,开始的时候直接划分成64个逻辑的数据库,由于前期数据量很少,这64个数据库直接使用两台物理DB提供服务,等数据量上来以后,再增加物理机器,把其中的数据库整个迁移到新的机器,避免行级别的迁移,这个过程逻辑分片数不会变。
    3. ID问题
    一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由.
    一些常见的主键生成策略:
    UUID/GUID(一般应用程序和数据库均支持)
    Twitter的Snowflake(又名“雪花算法”)
    MongoDB ObjectID(类似UUID的方式)
    Ticket Server(数据库生存方式,Flickr采用的就是这种方式)

UUID/GUID方式,在数据库中建立一个Sequence表,表的结构类似于:

CREATE TABLE `SEQUENCE` (  
    `table_name` varchar(18) NOT NULL,  
    `nextid` bigint(20) NOT NULL,  
    PRIMARY KEY (`table_name`)  
) ENGINE=InnoDB

每当需要为某个表的新纪录生成ID时就从Sequence表中取出对应表的nextid,并将nextid的值加1后更新到数据库中以备下次使用。但是意味着所有写的压力都会落到该表,容易成为性能瓶颈。优化的方式是,如果不要求id严格按照1的间隔来递增,可以每次自增100,应用每次取一段数据回去缓存到本地内容,并且为新请求分配id,这样可以减少sequence表99%的访问。

Snowflake方式,除去配置信息,核心代码就是毫秒级时间41位 机器ID 10位 毫秒内序列12位。

10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000

在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。

这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
详见http://blog.sina.com.cn/s/blog_6b7c2e660102vbi2.html
4. 分页排序问题
如果分页排序的数据都落到同一个分片,则跟单个数据源的处理没有区别,例如答案数据根据答题卡ID进行分库分表,那么这些答案数据最终会落在同一个分片,直接使用例如select * from answer_idex where card_id=#cardId order by id limit 10,20的语句即可搞定。
这里写图片描述
如果需要分页排序的数据不是落在同一个分辨,比如查询全部学生的答案数据,按照时间排序,那么数据就会散落在多个分片上,这个时候就需要从各个分片上分别查询出数据,然后再汇总查询,分页的难度会随着页码的增加而增加。例如查询第一页的数据,只需要在各个分片上查询出第一页的数据,然后再汇总排序,例如,查询第一页的数据select * from answer_idex order by create_time limit 0。但如果查询第20页的数据,就需要把各个分片的前20页的数据都查询出来,然后再汇总进行分页排序,这个数据量就非常大,性能也会急速下降。
这里写图片描述
解决跨分片的分页排序难题更多的是在产品设计上进行优化,比如尽量规避这种跨分片分页排序的需求;只允许用户查看前面几页;尽量缩小查询范围。
而技术角度的解决,通常是通过一些冗余数据来进行,比如上述按照时间分页排序的需求如果很强烈的话,可以冗余一张时间和答案ID的映射关系表,先在这个表里查询出最终显示的答案ID,然后再根据这些答案ID去查询答案数据。或者通过大数据平台,把所有数据汇总起来解决。
5. 分布式事务问题
关于分布式事务,通常涉及到的核心内容是两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究。具体可以参考以下网址:
http://blog.csdn.net/bluishglc/article/details/7612811
https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html
6. 常见的分库分表框架
了解清楚分库分表的原理以后,其实也不一定需要我们亲手去造一个轮子,开源平台上已经有很多分库分表的中间件了,可以先分析清楚这些中间件是否满足我们的技术需要,再进行技术选型。
简单易用的组件:
当当sharding-jdbc
蘑菇街TSharding
强悍重量级的中间件:
sharding
TDDL Smart Client的方式(淘宝)
Atlas(Qihoo 360)
alibaba.cobar(是阿里巴巴(B2B)部门开发)
MyCAT(基于阿里开源的Cobar产品而研发)
Oceanus(58同城数据库中间件)
OneProxy(支付宝首席架构师楼方鑫开发)
vitess(谷歌开发的数据库中间件)

感谢阅读 by ivan.rong

猜你喜欢

转载自blog.csdn.net/vipshop_fin_dev/article/details/81416725