分库与分表设计

一、数据的切分模式

  垂直(纵向)切分:把单一的表拆分成多个表,并分散到不同的数据库(主机)上。

  水平(横向)切分:根据表中数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上。

  ①、垂直切分

        一个数据库由多个表构成,每个表对应不同的业务,垂直切分是指按照业务将表进行分类,将其分布到不同的数据库上,这样就将数据分担到了不同的库上(专库专用)。

优点如下:

1)、拆分后业务清晰,拆分规则明确。

2)、系统之间进行整合或扩展很容易。

3)、按照成本、应用的等级、应用的类型等将表放到不同的机器上,便于管理。

4)、便于实现动静分离、冷热分离的数据库表的设计模式。

5)、数据维护简单。

缺点如下:

1)、部分业务表无法关联(Join),只能通过接口方式解决,提高了系统的复杂度。

2)、受每种业务的不同限制,存在单库性能瓶颈,不易进行数据扩展和提升性能。

3)、事务处理复杂。

②、水平切分

     与垂直切分对比,水平切分不是将表进行分类,而是将其按照某个字段的某种规则分散到多个库中,在每个表中包含一部分数据,所有表加起来就是全量的数据。

简单来说,我们可以将对数据的水平切分理解为按照数据行进行切分,就是将表中的某些行切分到一个数据库表中,而将其他行切分到其他数据库表中。

这种切分方式根据单表的数据量的规模来切分,保证单表的容量不会太大,从而保证了单表的查询等处理能力,例如将用户的信息表拆分成User1、User2等,表结构是完全一样的。我们通常根据某些特定的规则来划分表,比如根据用户的ID来取模划分。

优点如下:

1)、单库单表的数据保持在一定的量级,有助于性能的提高。

2)、切分的表的结构相同,应用层改造较少,只需要增加路由规则即可。

3)、提高了系统的稳定性和负载能力。

缺点如下:

1)、切分后,数据是分散的,很难利用数据库的Join操作,跨库Join性能较差。

2)、拆分规则难以抽象。

3)、分片事务的一致性难以解决。

4)、数据扩容的难度和维护量极大。

③、 水平切分的分片维度

对数据切片有不同的切片维度,可以参考Mycat提供的切片方式(见本书3.4节),这里只介绍两种最常用的切片维度。

1)按照哈希切片

   对数据的某个字段求哈希,再除以分片总数后取模,取模后相同的数据为一个分片,这样的将数据分成多个分片的方法叫作哈希分片。

2)按照时间切片

   与按照哈希切片不同,这种方式是按照时间的范围将数据分布到不同的分片上的,例如,我们可以将交易数据按照月进行切片,或者按照季度进行切片,由交易数据的多少来决定按照什么样的时间周期对数据进行切片。

④、垂直切分和水平切分的共同点:

       存在分布式事务的问题。

       存在跨节点Join的问题。

       存在跨节点合并排序、分页的问题。

       存在多数据源管理的问题。

二、分库与分表带来的分布式困境与应对之策

数据迁移与扩容问题

前面介绍到水平分表策略归纳总结为随机分表和连续分表两种情况。连续分表有可能存在数据热点的问题,有些表可能会被频繁地查询从而造成较大压力,热数据的表就成为了整个库的瓶颈,而有些表可能存的是历史数据,很少需要被查询到。连续分表的另外一个好处在于比较容易,不需要考虑迁移旧的数据,只需要添加分表就可以自动扩容。随机分表的数据相对比较均匀,不容易出现热点和并发访问的瓶颈。但是,分表扩展需要迁移旧的数据
针对于水平分表的设计至关重要,需要评估中短期内业务的增长速度,对当前的数据量进行容量规划,综合成本因素,推算出大概需要多少分片。对于数据迁移的问题,一般做法是
通过程序先读出数据,然后按照指定的分表策略再将数据写入到各个分表中。

表关联问题

在单库单表的情况下,联合查询是非常容易的。但是,随着分库与分表的演变,联合查询就遇到跨库关联和跨表关系问题。在设计之初就应该尽量避免联合查询,可以通过程序中进行拼装,或者通过反范式化设计进行规避。

分页与排序问题

一般情况下,列表分页时需要按照指定字段进行排序。在单库单表的情况下,分页和排序也是非常容易的。但是,随着分库与分表的演变,也会遇到跨库排序和跨表排序问题。为了最终结果的准确性,需要在不同的分表中将数据进行排序并返回,并将不同分表返回的结果集进行汇总和再次排序,最后再返回给用户。

方法一:全局视野法

1)将order by time offset X limit Y,改写成order by time offset 0 limit X+Y

2)服务层对得到的N*(X+Y)条数据进行内存排序,内存排序后再取偏移量X后的Y条记录

这种方法随着翻页的进行,性能越来越低

方法二:业务折衷法-禁止跳页查询

1)用正常的方法取得第一页数据,并得到第一页记录的time_max

2)每次翻页,将order by time offset X limit Y,改写成order by time where time>$time_max limit Y

以保证每次只返回一页数据,性能为常量

方法三:二次查询法

1)将order by time offset X limit Y,改写成order by time offset X/N limit Y

2)找到最小值time_min

3between二次查询,order by time between $time_min and $time_i_max

4)设置虚拟time_min,找到time_min在各个分库的offset,从而得到time_min在全局的offset

5)得到了time_min在全局的offset,自然得到了全局的offset X limit Y

分布式事务问题

随着分库与分表的演变,一定会遇到分布式事务问题,那么如何保证数据的一致性就成为一个必须面对的问题。目前,分布式事务并没有很好的解决方案,难以满足数据强一致性,一般情况下,使存储数据尽可能达到用户一致,保证系统经过一段较短的时间的自我恢复和修正,数据最终达到一致

分布式全局唯一ID

在单库单表的情况下,直接使用数据库自增特性来生成主键ID,这样确实比较简单。在分库分表的环境中,数据分布在不同的分表上,不能再借助数据库自增长特性。需要使用全局唯一 ID,例如 UUID、GUID等。关于如何选择合适的全局唯一 ID,我会在后面的章节中进行介绍。

业界方案:

UUID:通过唯一识别码16个字节128位的长数字。

组成部分:当前日期和时间序列+全局的唯一性网卡mac地址

优点:代码实现简单、不占用宽带、数据迁移不受影响

缺点:无序、无法保证趋势递增(要求3)字符存储、传输、查询慢、不可读

Snowflake雪花算法

 国外的twitter分布式下ID生成算法

1bit+41bit+10bit+10+bit=62bit

高位随机+毫秒数+机器码(数据中心+机器id)+10的流水好

国内:

保证数据的唯一性就行了IDC机房

优点:代码实现简单、不占用宽带、数据迁移不受影响、低位趋势递增

缺点:强以来时钟(多台服务器时间一定要一样)、无序无法保证趋势递增Redis:
缩减版本、有关业务代码没有包含到里头、redis方案
优点:不依赖数据、灵活方便、性能优于数据库的、没有单点故障(高可用)
缺点:需要占用网络资源、性能要比本地生成慢、需要增加插件

三、总结

分库与分表主要用于应对当前互联网常见的两个场景:海量数据和高并发。然而,分库与分表是一把双刃剑,虽然很好的应对海量数据和高并发对数据库的冲击和压力,但是却提高的系统的复杂度和维护成本。

因此,我的建议:需要结合实际需求,不宜过度设计,在项目一开始不采用分库与分表设计,而是随着业务的增长,在无法继续优化的情况下,再考虑分库与分表提高系统的性能。

猜你喜欢

转载自blog.csdn.net/baidu_28068985/article/details/102895444