数据库表 sharding 综述
数据库sharding
基本思路
表多:垂直划分
表不多但表的数据很多:水平划分
切分策略
先垂直后水平
垂直:“聚集“,聚合
水平:聚合根
举例:
社交网站:根据用户区分
论坛:垂直shard:用户和论坛
水平shard:Form是聚合根
Tips:
只读字典或变化较小的表:每个shard里维护一份,可以加速读取速度(join)
同时进行垂直和水平切分时,在垂直方向上的切分将不再以“功能模块”进行划分,而是需要更加细粒度的垂直切分,而这个粒度与领域驱动设计中的“聚合”概念不谋而合,甚至可以说是完全一致,每个shard的主表正是一个聚合中的聚合根!
这样切分下来你会发现数据库分被切分地过于分散了(shard的数量会比较多,但是shard里的表却不多),为了避免管理过多的数据源,充分利用每一个数据库服务器的资源,可以考虑将业务上相近,并且具有相近数据增长速率(主表数据量在同一数量级上)的两个或多个shard放到同一个数据源里,每个shard依然是独立的,它们有各自的主表,并使用各自主表ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。
问题
事务问题使用分布式事务优点:交由数据库管理,简单有效
缺点:性能代价高,特别是shard越来越多时
由应用程序和数据库共同控制将一个跨多个数据库的分布式事务分拆成多个仅处
于单个数据库上面的小事务,并通过应用程序来总控
各个小事务。
优点:性能上有优势
缺点:需要应用程序在事务控制上做灵活设计。如果使用
了spring的事务管理,改动起来会面临一定的困难。
跨节点Join的问题只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。
跨节点的count,order by,group by以及聚合函数问题这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
拆分实施策略和示例演示
准备阶段
领域模型
分析阶段
垂直切分
水平切分垂直切分后,需要对shard内表格的数据量和增速进一步分析,以确定是否需要进行水平切分若划分到一起的表格数据增长缓慢,在产品上线后可遇见的足够长的时期内均可以由单一数据库承载,则不需要进行水平切分
若划分到一起的表格数据量巨大,增速迅猛,需要进一步进行水平分割结合业务逻辑和表间关系,将当前shard划分成多个更小的shard,通常情况下,这些更小的shard每一个都只包含一个主表(将以该表ID进行散列的表)和多个与其关联或间接关联的次表。这种一个shard一张主表多张次表的状况是水平切分的必然结果。
这样切分下来,shard数量就会迅速增多。如果每一个shard代表一个独立的数据库,那么管理和维护数据库将会非常麻烦,而且这些小shard往往只有两三张表,为此而建立一个新库,利用率并不高,因此,在水平切分完成后可再进行一次“反向的Merge”,即:将业务上相近,并且具有相近数据增长速率(主表数据量在同一数量级上)的两个或多个shard放到同一个数据库上,在逻辑上它们依然是独立的shard,有各自的主表,并依据各自主表的ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。这样,每个数据库结点上的表格数量就相对平均了。
所有表格均划分到合适的shard之后,所有跨越shard的表间关联都必须打断,在书写sql时,跨shard的join、group by、order by都将被禁止,需要在应用程序层面协调解决这些问题。
实施阶段
如果项目在开发伊始就决定进行分库分表,则严格按照分析设计方案推进即可。如果是在中期架构演进中实施,除搭建实现sharding逻辑的基础设施外(关于该话题会在下篇文章中进行阐述),还需要对原有SQL逐一过滤分析,修改那些因为sharding而受到影响的sql.
全局主键生成策略
一些常见的主键生成策略
UUID索引慢问题
Sequence表: nextId
tableName
单点问题:使用master-slave
访问压力问题:暂没有好的解决方案
一种极为优秀的主键生成策略
flickr
常见分库方式
无单一主键生成方式
假设分成N个 shard,那么 为每个shard的表采用 auto increase N 方式.
优点:不必使用主键生成中间件
缺点:由于是在数据库上进行increase控制,因此 扩展非常困难,迁移数据很坑爹
无法生成连续ID
单一主键生成方式
使用某种主键中间件生成主键,通过hash取余将数据分配。
优点:可以生成连续ID,在迁移时比“无单一主键生成方式”稍微简单
缺点:迁移困难
单一主键生成方式 + 路由表
使用某种主键中间件生成主键,随机散列到shard里,将关联关系放到 路由表里
优点:没有迁移问题
缺点:路由表将成为瓶颈改进方式:将路由表读取通过分布式缓存进行控制(解决读慢的问题),但还是无法解决写的压力(多次写和数据库表大引起的写压力)
增量区间进行路由
按增量区间进行路由(如每1千万条数据或是每一个月的数据存放在一个节点上 ),虽然可以避免数据的迁移,却有可能带来“热点”问题,也就是近期系统的读写都集中在最新创建的节点上(很多系统都有此类特点:新生数据的读写频率明显高于旧有数据),从而影响了系统性能。面对这种两难的处境,Sharding扩容显得异常困难。
散列路由 + 增量区间路由
略
常见路由层算法实现
DAO层实现:大部分技术团队采用优点:快速、简单
缺点:与业务代码耦合
在ORM框架层实现实现O-R Mapping的前提下同时提供sharding支持
在JDBC API层实现:实现难度大
在介于DAO与JDBC之间的Spring数据访问封装层实现:优点:spring提供了很多方便的连接管理
在应用服务器与数据库之间通过代理实现
多数据源的事务处理
分布式事务:两阶段提交
基于Best Efforts 1PC模式的事务
事务补偿机制: pikaq