分库分表(垂直分库,垂直分表,水平分库,水平分表)

分库分表(高效,解耦,扩展,维护,性能,减少了磁盘 IO)

原因

        MySQL 底层是通过数据页存储的,一条记录占用空间过大会导致跨页

        另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘 IO,从而提升了数据库性能。

为什么这么做

        数据库分布式核心内容无非就是数据切分 (Sharding),以及切分后对数据的定位、整合。数据切分就是将数据分散存储到多个数据库中,使得单一数据库中的数据量变小,通过扩充主机的数量缓解单一数据库的性能问题,从而达到提升数据库操作性能的目的。

        数据切分根据其切分类型,可以分为两种方式:垂直 (纵向) 切分和水平 (横向) 切分

优点

  1. 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
  2. 应用端改造较小,不需要拆分业务模块

缺点

  1. 部分表无法 join,只能通过接口聚合方式解决,提升了开发的复杂度
  2. 跨分片的事务一致性难以保证
  3. 跨库的 join 关联查询性能较差
  4. 数据多次扩展难度和维护量极大

解决方案

一般来说,在系统设计阶段就应该根据业务耦合松紧来确定垂直分库,垂直分表方案,在数据量及访问压力不是特别大的情况,首先考虑缓存、读写分离、索引技术等方案。若数据量极大,且持续增长,再考虑水平分库水平分表方案。

怎么分库分表

垂直分表: 根据业务规则,或访问频次、是否大字段,将常用字段与不常用字段为为N个表

垂直分库: 根据业务规则,将商品表,店铺表,订单表分别放在不同数据库,库可以分布再不同服务器

水平分表: 根据表数据规则,将一个表得数据根据规则进行拆分成多个表(比如:分区,轮询,取模)

水平分库:根据表数据规则,将一个表(商品表)得数据分到不同得库中,每个库只有这个表的部分数据

垂直分表:可以把一个宽表的字段按访问频次、是否是大字段的原则拆分为多个表,这样既能使业务清晰,还能提升部分性能。拆分后,尽量从业务角度避免联查,否则性能方面将得不偿失

垂直分库:可以把多个表按业务耦合松紧归类,分别存放在不同的库,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能,同时能提高整体架构的业务清晰度,不同的业务库可根据自身情况定制优化方案。但是它需要解决跨库带来的所有复杂问题。

水平分库:可以把一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的部分数据,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能。它不仅需要解决跨库带来的所有复杂问题,还要解决数据路由的问题(数据路由问题后边介绍)。

水平分表:可以把一个表的数据(按数据行)分到多个同一个数据库的多张表中,每个表只有这个表的部分数据,这样做能小幅提升性能,它仅仅作为水平分库的一个补充优化。

 分开分表的思考问题    

          1.数据同步问题,

          2.分布式事务问题 ,最终一致性

分库分表能有效的环节单机和单库带来的性能瓶颈和压力,突破网络 IO、硬件资源、连接数的瓶颈,同时也带来了一些问题。下面将描述这些技术挑战以及对应的解决思路。         

1、事务一致性问题

分布式事务

        当更新内容同时分布在不同库中,不可避免会带来跨库事务问题。跨分片事务也是分布式事务,没有简单的方案,一般可使用 “XA 协议” 和 “两阶段提交” 处理。

        分布式事务能最大限度保证了数据库操作的原子性。但在提交事务时需要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间。导致事务在访问共享资源时发生冲突或死锁的概率增高。随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平扩展的枷锁。

最终一致性

        对于那些性能要求很高,但对一致性要求不高的系统,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。与事务在执行中发生错误后立即回滚的方式不同,事务补偿是一种事后检查补救的措施,一些常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等等。事务补偿还要结合业务系统来考虑。

2、跨节点关联查询 join 问题

        切分之前,系统中很多列表和详情页所需的数据可以通过 sql join 来完成。而切分之后,数据可能分布在不同的节点上,此时 join 带来的问题就比较麻烦了,考虑到性能,尽量避免使用 join 查询。

解决这个问题的一些方法:

1) 全局表

        全局表,也可看做是 “数据字典表”,就是系统中所有模块都可能依赖的一些表,为了避免跨库 join 查询,可以将这类表在每个数据库中都保存一份。这些数据通常很少会进行修改,所以也不担心一致性的问题。

2) 字段冗余

        一种典型的反范式设计,利用空间换时间,为了性能而避免 join 查询。例如:订单表保存 userId 时候,也将 userName 冗余保存一份,这样查询订单详情时就不需要再去查询 “买家 user 表” 了。

        但这种方法适用场景也有限,比较适用于依赖字段比较少的情况。而冗余字段的数据一致性也较难保证,就像上面订单表的例子,买家修改了 userName 后,是否需要在历史订单中同步更新呢? 这也要结合实际业务场景进行考虑。

3) 数据组装

        在系统层面,分两次查询,第一次查询的结果集中找出关联数据 id,然后根据 id 发起第二次请求得到关联数据。最后将获得到的数据进行字段拼装。

4)ER 分片

        关系型数据库中,如果可以先确定表之间的关联关系,并将那些存在关联关系的表记录存放在同一个分片上,那么就能较好的避免跨分片 join 问题。在 1:1 或 1:n 的情况下,通常按照主表的 ID 主键切分。

3.跨节点分页、排序、函数问题

        都需要每个分片进行计算,耗费cpu,网络,内存

4、全局主键避重问题

        1.UUID,结合数据库维护主键 ID 表。

        flickr 团队使用的一种主键生成策略,与上面的 sequence 表方案类似,但更好的解决了单点和性能瓶颈的问题。

        这一方案的整体思想是:建立 2 个以上的全局 ID 生成的服务器,每个服务器上只部署一个数据库,每个库有一张 sequence 表用于记录当前全局 ID。表中 ID 增长的步长是库的数量,起始值依次错开,这样能将 ID 的生成散列到各个数据库上。但有以下几个缺点:系统添加机器,水平扩展时较复杂; 每次获取 ID 都要读写一次 DB,DB 的压力还是很大,只能靠堆机器来提升性能。

        可以基于 flickr 的方案继续优化,使用批量的方式降低数据库的写压力,每次获取一段区间的 ID 号段,用完之后再去数据库获取,可以大大减轻数据库的压力。如下图所示:

        

 

        还是使用两台 DB 保证可用性,数据库中只存储当前的最大 ID。ID 生成服务每次批量拉取 6 个 ID,先将 max_id 修改为 5,当应用访问 ID 生成服务时,就不需要访问数据库,从号段缓存中依次派发 0~5 的 ID。当这些 ID 发完后,再将 max_id 修改为 11,下次就能派发 6~11 的 ID。于是,数据库的压力降低为原来的 1/6。

2.Snowflake 分布式自增 ID 算法

        Twitter 的 snowflake 算法解决了分布式系统生成全局 ID 的需求,生成 64 位的 Long 型数字,组成部分:

第一位未使用

接下来 41 位是毫秒级时间,41 位的长度可以表示 69 年的时间

5 位 datacenterId,5 位 workerId。10 位的长度最多支持部署 1024 个节点

最后 12 位是毫秒内的计数,12 位的计数顺序号支持每个节点每毫秒产生 4096 个 ID 序列

 

        这样的好处是:毫秒数在高位,生成的 ID 整体上按时间趋势递增; 不依赖第三方系统,稳定性和效率较高,理论上 QPS 约为 409.6w/s(1000*2^12),并且整个分布式系统内不会产生 ID 碰撞; 可根据自身业务灵活分配 bit 位。

不足就在于:强依赖机器时钟,如果时钟回拨,则可能导致生成 ID 重复。

综上结合数据库和 snowflake 的唯一 ID 方案,可以参考业界较为成熟的解法:Leaf——美团点评分布式 ID 生成系统,并考虑到了高可用、容灾、分布式下时钟等问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值