前言
因为每个学校学生用餐人数太多,一天订单20万量增长,而且学校使用也在不停的增多,公司最近在搞分库分表,数据分离到不同的库中或表中, 所以这段时间了解过数据库的分库分表,也读过很多大神写的博文,基本上知道个大概,也在实际的应用中掌握分库分表的技术
下面总结一下从以下几个方面说起:
1、服务和数据库的演化过程
3、分库分表有哪几种方式。
4、分库分表有哪些问题
5、目前市面有的一些开源产品,技术,它们的优缺点是什么(只说ShardingJdbc和MyCat)
分库分表详解
下面我们以一个商城系统为例逐步讲解数据库是如何一步步演进。
** 分库**
单架构应用单数据库
早期的项目比如商城购买项目,基本上都是单体架构应用系统,一个系统中包含多个基础功能模块,比如用户模块、订单模块、库存模块等
所有模块在一个系统中,所有和功能模块关联的表也在一个数据库中,通常一个数据库中会存在非常多的表,早期用户数据量不多,使用单体架构足于满足需求。
** 为服务架构应用单数据库**
早期数据量小可能没有多大问题,后期业务量,系统分成了不同的很多功能模块,系统不停地迭代更新,代码量也会越来越大,单体架构已经不能满足需求,当用户量变大系统的访问压力也逐步的增加,项目功能模块就必须拆分。系统拆分按照功能模块拆分成Portal 服务、用户服务、订单服务、库存服务等
微服务架构单一数据库设计存在的问题:
1、微服务提供多个类型服务,但单一数据库的传统设计会产生紧密耦合,无法做为独立部署服务。
如果有多个服务访问同一数据库,则需要在所有服务间协调数据模式的更改,在现实工作中,这会导致额外的工作,延迟部署更新。
2、使用这样的设计很难对单个服务进行扩展,因为你只能选择扩展单一数据库。
3、使用单一数据库,对提高应用程序性能成为挑战。当使用单一共享数据库,在一段时间过后,我们最终会有着一个数据庞大的表,让数据检索变得很困难,我们必须连接多个大表格,方能获取所需的数据。
4、在绝大多数情况下,我们将数据使用关系存储到数据库。而使用关系数据库会限制一些服务。在有些情况下,NoSQL数据存储可能更适合你,能够降低集中式数据存储的紧密耦合。
** 微服务架构多数据库**
随着业务量的加大,数据库访问成为了瓶颈,这个时候多个服务共享一个数据库基本不可行了
,应该做到每个微服务都应该有属于自己的数据库,它仅包含与微服务本身相关的数据。这样就可以允许我们独立部署单个服务。单数据库的能够支撑的并发量是有限的,拆成多个库可以使服务间不用竞争,提升服务的性能。
如上图,从一个大的数据中分出多个小的数据库,每个服务都对应一个数据库,这就是系统发展到一定阶段必须要做的分库操作,如果只拆分服务应用不拆分数据库,不能解决根本问题,整个系统也很容易达到瓶颈。
** 分表**
上面我们说了数据库分库,接下来讲讲数据库分表
那为什么要分表呢,什么情况下需要分表呢?
当一个数据库被创建之后,随着时间的推移和业务量的增加,数据库中表以及表中的数据量就会越来越多,就有可能出现两种弊端:
第一种: 数据库的存储资源是有限的,其负载能力也是有限的,数据的大量积累肯定会导致其处理数据的能力下降。
第二种:数据量越多,那么对数据的增删改查操作的开销也会越来越大,所以,当出现如上两种情况,分库分表势在必行。
分库分表的类型和特点:
从维度来说分成两种,一种是垂直,一种是水平。
垂直切分:基于表或字段划分,表结构不同。我们有单库的分表,也有多库的分库。
水平切分:基于数据划分,表结构相同,数据不同,也有同库的水平切分和多库的切分。
垂直切分如下图:
水平切分如下图:
垂直分表有两种,一种是单库的,一种是多库的。
单库垂直分表
单库分表,比如:订单表,按照字段进行拆分,常规的方案是冷热分离(将使用频率高字段放到一张表里,剩下使用频繁低的字段放到另一张表里)。
多库垂直分表
多库垂直分表就是把原来存储在一个库的不同的表,拆分到不同的数据库。
当我们只是对原来的一张表做了分库的处理,如果业务系统的数据还是有一个非常快的增长速度,比如说订单下单数据库的订单表,数据量达到了几个亿,这个时候硬件
限制导致的性能问题还是会出现,所以从这个角度来说垂直切分并没有从根本上解决单库单表数据量过大的问题。在这个时候,我们还需要对我们的数据做一个水平的切分。
水平切分
当我们的订单表数量已经到达数千万甚至上亿的时候,单表的存储容量和查询效率都会出现问题,我们需要进一步对单张表的数据进行水平切分。水平切分的每个数据库的表结构都是一样的,只是存储的数据不一样,比如每个库存储 1000 万的数据。水平切分也可以分成两种,一种是单库的,一种是多库的。
单库水平分表
水平拆分表就是按照表中的记录进行分片,举个例子,目前订单表 orders 有 2000w 数据,根据业务的增长,估算一年之后会达到1亿,同时参考阿里云 RDS for MySQL 的最佳实践,单表不建议超过 500w,1亿数据分20个子表就够了。
那根据什么来进行拆分呢?
按主键ID拆分数据很均匀,通过ID查询 orders 的场景几乎没有,业务访问 orders 大部分场景都是根据 user_id来过滤的,而且 user_id 的唯一性又很高(一个 user_id 对应的 orders 表记录不多,选择性很好),按照 user_id 来作为 Sharding key能满足大部分业务场景,拆分之后每个子表数据也比较均匀
这样就可以将 orders 表拆分成20个子表,看实际业务量和需求需要拆分成多少张表。
多库水平分表
上面介绍了单表水平分表方案,接下来多库分表,意思是将拆分后的表存储在不同的 数据库中。
举个例子,交易数据库的订单表 有2亿多数据,数据库实例遇到了写入瓶颈,普通的 insert 都需要50ms,时常也会收到 CPU 使用率告警,这时就要考虑分库了。根据业务量增长趋势,计划扩容一台同配置的数据库实例,将订单表拆分20个子表,每个 数据库10个表。
常见的增删改查方式,比如查询方式:查询的时候要先通过定义的分区字段比如userId哈希定位到是哪个数据库后在哈希定位到具体的表,这样解决了订单表 太大的问题。
一般我们说的分库分表都是跨库的分表。既然分库分表能够帮助我们解决性能的问题。那我们在项目设计的时候是不是可以先给它们发几个库或者表呢,其实不然,在我们分库分表的过程中带来了很多的问题,分库分表之后带来了很多的复杂性,比如以前正常的sql使用分库分表以后有些地方需要改动。
拆分以后带来的问题有:
1、跨库关联查询
在单库未拆分表之前,我们可以很方便使用 join 操作关联多张表查询数据,但是经过分库分表后两张表可能都不在一个数据库中,如何使用 join进行关联用户信息表查询数据,因为数据在不同的数据库表中,我们不能直接使用join去关联的啊。
有以下几种方案可以解决:
1.1、字段冗余
一种典型的反范式设计,利用空间换时间,为了性能而避免join查询。例如:订单表保存userId时候,也将用户名称性别等需要查询的字段冗余保存一份,这样查询订单详情时就不需要再去查询用户表了。
1.2、全局表(广播表)
比如系统中所有模块都可能依赖的一些表,为了避免跨库join查询,可以将这类表在每个数据库中都保存一份。这些数据通常很少会进行修改,所以也不担心一致性的问题。
1.3、ER 表(绑定表)
关系型数据库中,如果可以先确定表之间的关联关系,并将那些存在关联关系的表记录存放在同一个分片上,那么就能较好的避免跨分片join问题。在1:1或1:n的情况下,通常按照主表的ID主键切分
1.4、系统层组装
在系统层面,分两次查询,第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据。最后将获得到的数据进行字段拼装。
2、 分布式事务
以客户下单为例:假如用户下单需要经过写入订单表,明细表,库存表,在此期间如果几个动作不是同时成功或者同时失败,就会出现数据一致性的问题。如果是在一个数据库里面,我们可以用本地事务来控制,但是在不同的数据库里面就不行了。所以分布式环境里面的事务,我们也需要通过一些方案来解决。
常用解决方案有:基于可靠消息(MQ)的解决方案、两阶段事务提交、柔性事务等。
3、排序、分页、函数计算问题
在使用 SQL 时 order by, limit 等关键字需要特殊处理,一般来说采用分片的思路:
先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终得到结果。
4、全局主键避免重复问题
MySQL数据库里面字段有一个自增的属性,Oracle订单表也有 Sequence 序列。如果是一个数据库,那么可以保证 ID 是不重复的,但是水平分表以后,每个表都按照自己的规律自增,肯定会出现 ID 重复的问题,因此需要单独设计全局主键,以避免跨库主键重复问题。有一些常见的主键生成策略:
4.1、UUID
4.2、基于数据库自增单独维护一张 ID表
4.3、号段模式
4.4、Redis 缓存
4.5、雪花算法(Snowflake)
4.6、百度uid-generator
4.7、美团Leaf
4.8、滴滴Tinyid
5、 多数据源和读写数据源的解决方案
分库分表之后可能会面临从多个数据库或多个子表中获取数据,一般的解决思路有:客户端适配和代理层适配。
业界常用的中间件有:
shardingsphere(前身 sharding-jdbc)
Mycat
ShardingJdbc和MyCat的区别以及优缺点?
区别:
MyCat是一个基于第三方应用中间件数据库代理裤架,客户端所以的jdbc请求都必须要先交给MyCat,再有MyCat转发具体的真实服务器中。
ShardingJdbc是一个Jar形式,在本地应用层重写jdbc原生的方法,实现数据库分片形式。
MyCat属于服务器数据库中间件,而ShardingJdbc是一个本地数据库中间件框架。
从设计理念上看确实有一定的相似性,主要流程都是SL解析→>SL路由→>SQL改写→>SQL执行→结果归并。但是架构设计是不同的。MyCat是基于Proxy,它复写了MySQL协议,将MyCat Server 伪装成一个MySQL数据库,而ShardingJdbc是基于JDBC的扩展,是以jar包的形式提供轻量级服务的。
ShardingJdbc的优缺点:
优点:1、程序自动完成,数据源方便管理;2、不需要维护、因为没有中间件;3、理论支持任何数据库(sql标准)。
缺点:1、存在代码入侵性;2、加大开发成本;3、不能做到动态添加数据源,添加数据源还需要重启程序;4、程序开发完后,运维人员参与不了。
MyCat的优缺点
优点:1、数据添加不会影响到程序;2、应用层不需管理数据库层方面,由代理层去管理;3、添加数据源不需要重启程序。
缺点:1、程序依赖的中间件,提高维护工作;2、容易出现高可用问题;3、中间件导致切换数据库变的困难;2、增加了proxy,程序性能下降。