当生产环境中业务量激增,数据库数据量也会极具增加。当数据库的数据量达到一定程度时(数据库瓶颈),数据库宿主机负载超高,会严重影响业务,严重时会导致数据库宕机。
为了避免这种极端情况的发生,我们应当在发生前做好预案,用于解决数据库数据量过载的问题。
以下是我个人工作中使用的解决方案:
1)数据库主从或多主多从方案
2)数据库冷热数据拆分
3)数据库分库分表操作
4)在数据库前端增加缓存redis或memcached
一开始时,公司部分业务的架构如下(全都是单节点情况)
经过自己强调该架构严重的缺点:节点单一,服务中断情况严重等严峻问题,后演变为如下简图的架构:
上述架构中,我们更新代码实现了不中断服务,同时解决了部分单一节点问题。后来一段时间业务做推广优惠活动,业务量激增,数据库数据量成指数性增长。数据库负载一直居高不下。当前的架构已经无法满足业务需求,后又提出对数据库进行数据的冷热数据拆分和数据读写分离(可以考虑使用中间件Mycat、Atlas、MaxScale,也可以考虑让开发写路由规则,有些框架自带路由规则),这里我们使用Mycat,如下图架构,这里只显示后端:
随着时间的推移,单库单表已经达到业务忍受的极限,这时候已经在硬件还是在mysql参数调优上都已经无法满足业务。这时候提出了数据库的分库分表。这里主要讲解数据库的分库分表
分库分表一共有两种方案:
1) 垂直拆分
2) 水平拆分
1、 垂直拆分
所谓垂直拆分,就是将单一数据库拆分成多个数据库,可以考虑的方案:
1) 根据业务逻辑进行拆分
2) 根据冷热数据进行拆分
这里先讲解根据业务逻辑拆分:
这里以商品--订单--用户为例,当多种类商品存放于一个数据库和一张表中时,随着时间推移,数据量增大,单库单表查询能力下降,可以根据商品种类来进行拆分,比如公司的产品:高中、初中、小学,由单个表拆分成三个独立表。如果还大,继续拆分,高中拆分成高一、高二、高三。初中拆分成初一、初二、初三。小学拆分成小一、小二......小六。(所有的分库分表都源于生活中的逻辑)。
我们生产环境中针对数据进行了拆分,冷数据(采用MyISAM引擎),热数据(采用Innodb引擎),同时将MyISAM引擎的数据库的宿主机多采用redis或memcache进行缓存,Innodb引擎的数据库的宿主机针对业务情况分配计算型和内存型物理机,还是混合型,同时可以考虑使用redis做部分存储,用于缓解数据库压力。
垂直拆分优缺点:
优点:
1) 由单库单表拆分成多库多表,降低了数据库增删改查压力
2) 对冷热数据拆分,降低了成本并合理利用硬件
3) 对于垂直拆分的数据库,在设计数据库时就应当考虑好数据库架构的延展性,(否则会对后期的扩展造成很大的阻力)
4) 按照业务分库后,业务逻辑更加清晰,更方便运维管理。
缺点:
1) 对于联表查询,带来了不便,可以通过调用接口的方式来触发联表查询的操作,对整个系统而言,复杂度提高了。
2) 对于有些数据库,存在单库性能瓶颈影响整个业务情况。
3) 同时对于事务而言,复杂度提高了。
2、 水平拆分
所谓水平拆分,简单来说就是将一张表中的数据按照行拆分成多份存储到不同的数据库的表中。比如user表有9000条数据,将user表拆成user1、user2、user3三张表存放于不同的数据库中,user1存储前3000条数据,中间3000条数据存储到user2表中,最后3000条数据存放到user3表中。
水平拆分的分片维度(有很多算法来决定采用哪种水平拆分的方案)
1) 按照哈希切片,对某个字段进行求哈希值,然后除以分片的数量,最后取模,取模相同的数据为一个分片,这就是哈希分片。
这种分片方式没有时效性。对数据分散比较均匀,缺点是如果需要查询则需要对数据进行聚合处理。
2) 按照时间序列算法切片,有的业务会有明显的季度波动,可以使用时间算法。这种算法就是数据分配不均。
生产环境中,我们部分业务采用的是哈希算法和时间序列算法的混合式
说到水平拆分,不得不提的是水平拆分的路由规则
设计数据库的时候,就要考虑到数据库中各种表的路由规则,同时还需要考虑数据表将来按照什么样的路由规则来进行分库分表。
比如,某个新用户注册,这个用户是如何分配到哪个库哪个表中?一般在注册的时候都会系统自动分配一个uid。根据这个uid可以按照某种算法来进行分配。比如uid%4,(这里将一张表分成4个表),如果是1 则分到第一张表中,以此类推。
水平拆分的优缺点:
1) 单库单表的数据量最大就那么大,有数据量的限制,我们可以根据业务情况来分配,刚好达到最大(这个量很难把握),既能提高该表的操作性能又节省了资源
2) 因为对表的结构改变非常少,对于开发而言更改代码量非常少,只需要增加路由规则。
3) 对整体系统的稳定性和负载都有大大的提高
缺点:
1) jion操作非常困难,尤其是跨库的联表查询
2) 有的拆分规则很难抽象出来
3) 分片的事务一致性比较难解决
4) 还有数据库的扩容和维护比较困难
针对上述垂直拆分和水平拆分,都有以下缺点:
1) jion操作困难
2) 事务一致性困难
3) 多个数据源的管理变复杂。
如何解决事务一致性问题呢?(这个重要性排在第一位)
1) 采用本地事务的一致性(能用则用)
2) 分布式事务处理
分布式事务处理的解决方案:
1) 两阶段提交方案(最严格的方案,很少使用,因为是阻塞协议造成性能问题):分为准备(锁定资源)和提交(消费资源)两个阶段。这种方式依赖资源管理器。
2) 最大努力保证模式(最常用,极端情况需要实时补偿,将已提交的数据进行回滚)不依赖资源管理器
一共有两种操作:从消息队列中消费消息和更新数据库操作
开始消息事务?开始数据库事务?接收消息?更新数据库?提交数据库事务?提交消息事务
当更新数据库时,突然中断,会进行回滚,恢复到初始状态,特殊情况,如果提交数据库事务成功,但是提交消息事务失败,就会造成消息再次消费的情况。这个可以通过消息幂等处理(有时候很多消息无法满足幂等性比如update操作,可以考虑增加一个消息应用状态表来记录消息消耗情况和数据库事务情况),出现上述极端情况,需要实时补偿。这种补偿机制类似于TCC模式Try-Confirm-Cancel(如果try都成功了,它会重复confirm或重复cancel,直到都成功),TCC要求Confirm/Cancel都必须是幂等操作。
3) 事务补偿机制
前面两种方案,都不是最好的,事务补偿机既能保证性能又能尽最大可能保证事务的一致性。说白了就是突破事务一失败就回滚的思想,指定在一定的时间内不断提交直到成功为止,如果超时则回滚。
分库分表过程中带来的问题
1、 扩容和数据的迁移
数据已经分片,同时数据量已经快达到阈值,需要对集群进行扩容,都采用成倍扩容。
以下是5个扩容步骤:
1) 增加新的路由规则,对新的数据库采用新路由规则进行写,同时对旧数据库采用旧路由规则写。(双写,两套路由规则)
2) 将双写前的旧数据按照新规则写入到新数据库中(需要做大量的数据清洗工作)。
3) 将按照旧的分片规则的查询更改为按照新的分片规则查询。
4) 将双写的路由规则代码下线,只按照新规则写数据
5) 删除按照旧分片规则写入的历史数据。
2、 分库分表带来的联表查询问题
举例:
卖家和买家,卖家需要查看该商品卖出情况,买家需要查看自己的交易情况,可以考虑使用两个表,一个以买家维度,记录买家商品交易情况,一个以卖家维度,记录卖家该商品交易情况。也就是查询交易和交易的数据是分别存储的,并从不同的系统提供接口。
方法二:通过搜索引擎解决(大数据了)
参考文档:
https://blog.csdn.net/it_man/article/details/21593187
原文地址:http://blog.51cto.com/laodou/2110819