分库分表(一)--分库分表基础
内容主要来自下面的链接:MySQL:互联网公司常用分库分表方案汇总!
一、为什么要分库分表:
- SQL执行效率:单表达到了几千万,甚至亿级的规模,即使通过索引来查询都很慢。
- 数据存储:如果数据库里有很多的表,数据量都很大,服务器的存储空间几乎耗尽。
- 机器负载:8核16G的机器部署的数据库,一般TPS不要超过2k。如果达到6k、8k的TPS的话,磁盘、IO、网络、CPU、内存负载会比较高,最终都会导致数据库的活跃连接数增加,随时可能崩溃。
二、分库分表如何拆分:
核心思想:先垂直,后水平。
1、垂直拆分:
- 业务拆分:每个业务都都有自己的数据库,类似微服务都有独立的数据库。
- 字段拆分:将一个表的字段拆分到多个表/库,例如:订单表拆分为:订单表、订单支付表、订单商品表。
- 可以避免同一个数据库里面放入太多的数据,有效的分散对数据库单点的访问量。
- 垂直分片无法真正的解决单点瓶颈,可以缓解数据量和访问量带来的问题,但无法根治。
- 如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理。
2、水平拆分:
- 通过一个或几个字段,根据路由规则将数据分散至多个库的表中,每个分片仅包含数据的一部分。
- 水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是分库分表的标准解决方案。
- 举个栗子,表里面1600W的数据拆分到16个表,每个表才100W数据。
三、分库分表的方式:
1、水平分库:
概念:
Shardingkey按照一定策略(hash、range等),将一个库中的数据拆分到多个库中。
场景:
系统绝对并发量太大,分表已经无法解决了,并且还没有明显的业务归属来垂直分库。
分析:
库多了,io和cpu的压力自然可以成倍缓解。
2、水平分表:
概念:
Shardingkey按照一定策略(hash、range等),将一个表中的数据拆分到多个表中,查询条件必须带上分表的key。
场景:
系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。
分析:
表的数据量少了,单次SQL执行效率高。
3、垂直分库:
概念:
以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。
场景:
系统绝对并发量上来了,并且可以抽象出单独的业务模块。
分析:
就是微服务的场景。
4、垂直分表:
概念:
以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
场景:
系统绝对并发量不高,数据不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。
以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。
分析:
拆分原则是将热点数据放在一起作为主表,非热点数据放在一起作为扩展表。
这样更多的热点数据就能被缓存下来,进而减少了随机读IO。
拆了之后,要想获得全部数据就需要关联两个表来取数据。
但记住,千万别用join,因为join不仅会增加CPU负担并且会讲两个表耦合在一起(必须在一个数据库实例上)。
关联数据,应该在业务Service层做文章,分别获取主表和扩展表数据然后用关联字段关联得到全部数据。
四、分库分表中间件:
1、cobar:
阿里b2b团队开发和开源的,属于proxy层方案。
早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。
而且不支持读写分离、存储过程、跨库join和分页等操作。
2、TDDL:
淘宝团队开发的,属于client层方案。
不支持join、多表查询等语法,就是基本的crud语法是ok,但是支持读写分离。
目前使用的也不多,因为还依赖淘宝的diamond配置管理系统。
3、atlas:
360开源的,属于proxy层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在5年前了。
所以,现在用的公司基本也很少了。
4、sharding-jdbc:
当当开源的,属于client层方案。
确实之前用的还比较多一些,因为SQL语法支持也比较多,没有太多限制,支持分库分表、读写分离、分布式id生成、柔性事务(最大努力送达型事务、TCC事务)。
而且确实之前使用的公司会比较多一些,目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也可以选择的方案。
5、mycat:
基于cobar改造的,属于proxy层方案,所有请求都是走mycat进行代理,将数据库地址修改为mycat地址即可。
支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,很多公司开始在用了。
6、综合考虑:
sharding-jdbc和mycat这两个都可以去考虑使用。
sharding-jdbc这种client层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高。
但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合sharding-jdbc的依赖;
mycat这种proxy层方案的缺点在于需要部署,自己及运维一套中间件,运维成本高。
但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。
个人建议中小型公司选用sharding-jdbc,client层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;
但是中大型公司最好还是选用mycat这类proxy层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护mycat,然后大量项目直接透明使用即可。
五、分库分表带来的问题:
1、事务一致性问题:
1.1、分布式事务:
当更新内容同时分布在不同库中,不可避免会带来跨库事务问题。
分布式事务能最大限度保证了数据库操作的原子性。但在提交事务时需要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间。
导致事务在访问共享资源时发生冲突或死锁的概率增高。
随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平扩展的枷锁。
1.2、最终一致性:
对于那些性能要求很高,但对一致性要求不高的系统,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。
常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等等。
2、跨节点关联查询 join 问题:
切分之前,系统中很多列表和详情页所需的数据可以通过 sql join 来完成。
而切分之后,数据可能分布在不同的节点上,此时 join 带来的问题就比较麻烦了,考虑到性能,尽量避免使用 join 查询。
2.1、全局表:
一些数据字典表,为了避免跨库 join 查询,可以将这类表在每个数据库中都保存一份。
这些数据通常很少会进行修改,所以也不担心一致性的问题。
2.2、字段冗余:
一种典型的反范式设计,利用空间换时间,为了性能而避免 join 查询。
例如:订单表保存 userId 时候,也将 userName 冗余保存一份,这样查询订单详情时就不需要再去查询"user"了。
但这种方法适用场景也有限,比较适用于依赖字段比较少的情况。
而冗余字段的数据一致性也较难保证,就像上面订单表的例子,买家修改了 userName 后,是否需要在历史订单中同步更新呢?这也要结合实际业务场景进行考虑。
2.3、数据组装:
在系统层面,分两次查询,第一次查询的结果集中找出关联数据 id,然后根据 id 发起第二次请求得到关联数据。
最后将获得到的数据进行字段拼装。
2.4、ER 分片:
关系型数据库中,如果可以先确定表之间的关联关系,并将那些存在关联关系的表记录存放在同一个分片上,那么就能较好的避免跨分片 join 问题。
在 1:1 或 1:n 的情况下,通常按照主表的 ID 主键切分。
3、跨节点分页、排序、函数问题:
跨节点多库进行查询时,会出现 limit 分页、order by 排序等问题。
分页需要按照指定字段进行排序,当排序字段就是分片字段时,通过分片规则就比较容易定位到指定的分片;
当排序字段非分片字段时,就变得比较复杂了。
需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户。
上图中只是取第一页的数据,对性能影响还不是很大。
但是如果取得页数很大,情况则变得复杂很多。
因为各分片节点中的数据可能是随机的,为了排序的准确性,需要将所有节点的前 N 页数据都排序好做合并,最后再进行整体的排序,这样的操作是很耗费 CPU 和内存资源的,所以页数越大,系统的性能也会越差。
在使用 Max、Min、Sum、Count 之类的函数进行计算的时候,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。
六、问题:
1、一个表要根据多种维度去查询怎么办?
- 如果有两三种维度的话,不太考虑成本的话,可以分多次表。
- 否则只能根据映射关系做二次路由。
2、查询条件里没包含sharding字段:
- 当我们通过不包含sharding字段的复杂查询,特别是多条件组合,甚至还有全文检索的模糊匹配需求。
- sharding-jdbc并行查询所有的库里的所有的表,然后内存级进行merge,这样就太慢了。
- 一般都是对mysql进行binlog监听,把数据通过canal同步到ES里去,创建索引,基于ES筛选数据。
- 如果是一个字段或者少量字段,并且使用频率特别高,也可以考虑映射表。
- 将这个字段和shardingkey做成联合索引,这样查询效率也是很高的。
3、什么时候进行分表,分库分表?
只是分表:
- 系统绝对并发量不高,只是单表数据量太大了,SQL执行效率较低,无法通过SQL优化解决。
分库且分表:
- 系统绝对并发量太高,分表解决不了问题,单个机器的配置不可能无限的扩大。
- 例如分了8个库,每个库分8张表,减少单表的数据量,尽可能保证SQL执行效率。
七、分库分表物理规划:
目前有多个主库,主库里存放了大量的分表,每个主库都挂了一两个从库,抗80%以上的常规的crud,数据binlog同步到ES支撑复杂查询。
规划:
- 当前数据库的数据量是多少,考虑一下数据的增长速度,几年后的数据量。
- 当前数据库的QPS、TPS多少,未来QPS、TPS能达到多少,需要给的机器配置。
- 每个主库需要挂几个从库,从库需要抗的QPS是多少。
- 包括ES数据量,机器配置,主要是内存。
八、单库到分库分表的迁移方案:
针对分库分表环境的系统代码基于sharding-sphere中间件进行修改,然后部署,连接的就是分库分表的环境;
1、双写:
同时有两套环境,老系统 -> 单库单表,也在运行; 做一个线上流量的拷贝和分发。
2、流量分发:
- 通过Nginx或者中间服务实现流量分发策略,同一个请求,分发给两套系统。
- 需要做数据监测,两边的数据量要相同,数据内容不能变化。
- 直到确定新环境稳定之后,新系统 -> 分库分表这套环境彻底ok了。
九、运维管理工作台:
- 做大量的线上的DDL的操作,建表、修改表,删除表,不可能自己手动1024个表一个一个去做。
- 所以肯定需要运维管理工作台,可以让你对逻辑上的表做DDL,底层自动把DDL在1024张表了去做一个更新,监控。
- 对每个逻辑上的表都知道切分为了多少张物理上的表,分散在哪些库上,库在哪台服务器上。
- 逻辑表的总数据量多少行,每个物理表的数据量多少行,有一个监控。
十、扩容:
一般我们都是设计很多表,扩容的时候就是加更多的服务器和库,然后对表整体做迁移,再修改一下代码里的路由就行了,否则数据迁移是很麻烦的。