更多内容关注微信公众号:fullstack888

简介

ShardingSphere一套优秀的开源分布式数据库中间件解决方案,涉及分库分表的系统实现,值得考虑的方案之一,更多的优秀之处,直接带上传送门。


目的

数据需要分库分表,必然是数据量及增量已经超过单实例数据库的承受范围,硬件升级成本会越来越高,通过将数据分散到多个数据库服务,减少单库单表的数据压力,处于一个良好的查询响应范围。

分库分表分片策略选择,决定了以后是否能快速扩容,以常见的取余作为分片取模为例,算法决定了需要准备的库和表的数量。由于难于评估将来的业务发展,库和表准备多了资源是一种浪费,准备少了,再扩容重新做数据分片又将是一个恐怖的计划,没有充足的准备和演练不敢轻易实施,而与日俱增的数据增量压着每一根神经。所以,有没有什么样的实现方式可以尽可能低的代价平滑的支持数据库和表的扩容?


分片取模算法

ShardingSphere数据分片,以取余取模算法为例,前期我们规划有4个库,通过id取模分散写入到DB_0到DB_3

c8a4c7f747f6c7304703072827e8c013.png

公司业务进展很快铺开,用户量增长很快,单表数据量越来越大,需要增加数据库。这时候会遇到一个很严峻的问题,增加库以后,取模值发生变化,需要对历史数据重新取模进行数据迁移,接二连三的挑战迎面而来,不能停机,不能影响业务的正常进行,进行迁移时对数据库的任何操作都是惊心动魄(即使做足了各种演练,依然担心出现不在预期内的情况),出现失误直接就是P0故障。

增加两个库,数据取模发生变化:

d4228c5cb7efb6f35babe98424a07a86.png

如何低成本安全平滑扩容

现在我们的问题已经很明确,数据库扩容,最关键的影响点就是分片算法的选择,我们的切入点就是如何设计数据分片算法。ShardingSphere数据分片算法里,精确分片算法我们可以使用该算法明确指定数据最终落到哪一个数据库的表。

分段分片

分片算法设计第一版,范围分片,最开始我打算分段分片,即以分片键的某一段落到对应的数据库,比如:1到100万落到DB_0,100万到200万落到DB_1....以此类推划分id对应数据库。

54169f53ebc33fe795f5536455c68775.png

这种实现方式完全可行,但是会有一个问题,数据库热点写入问题。

自定义分片key生成策略

还是回到分片key问题上,要解决热点写入问题,最终还是要处理,如何尽可能均衡的写入到数据库,缓解热点压力,所以还是要从分片key生成策略上入手。既然使用精确分片算法(PreciseShardingAlgorithm),那就准确的控制数据最终会落到哪一个库哪一个表。分片key设计如下:

4b7ad96a328569111ec312f2a1ed1f1d.png

我们希望id具有一定的业务含义,业务前缀固定为4位,6-10位为分库位和分表位置,跟着是时间和自增(自增潜在一定的数据增量泄露风险,分库分表位以后保证不重复即可)。示例中明确的数据存储位置为01库的03表。以4库4表(4个数据库,每个数据库4个表)为例,分片key生成为:

05e4256a4010b9cbfa95cea80f332435.png

限定生成范围在0101-0404之间,就能覆盖所有库和表。当我们需要扩容,增加数据库,增大生成范围就能满足。需要注意的问题是,新增的数据库,创建数据表数量需要保持一致(例如每个库的分片表统一都为4个表,增加表需要所有库都同步增加到相应的数量)。如下我们一新增两个库为例,展示增加数据库以后的变化:

90f1fc2dda777e73349bc4028de66ca1.png

这样我们就可以以最小的代价平滑完成库和表的扩容。


数据分配不均衡

按照以上的逻辑,是解决了数据库扩容的问题。但是出现了一个新的问题,继续往复的生成分片key,数据还是分散的到每个库每个表,我们之所以要增加数据库扩容,是因为原有的数据库表数据量已经到了计划扩容的临界值,我们才进行扩容。这时候我们希望降低原有数据库的数据写入权重。这里我的想法是做一个分片key生成的动态权重调整,出发点是定时任务统计每个库的每个表数据量来进行调整,重新初始化数据分片key环。

我们计划单个表的数据量为300万(可以根据数据库服务器性能评估表的数据量上限),当表数据量超过计划阈值,我们开始降低表的写入权重,

  • 第一档阈值60%(180万),降低表数据写入权重

  • 第二档阈值70%(210万),再次降低写入阈值,评估所有库和表的数据量,开始进行数据库扩容

  • 第三档阈值85%(255万),不再写入数据,这里的不再写入数据并不是绝对的,是计划预留一定的数据空间容错,最好能监控到达该阈值还有写入数据,做一个报警,及时调整分片key生成策略,避免数据超出计划的最高值。


初始化

计划定了三档阈值,我们就初始化三个写入权重,生成好以后放入环形结构的数组。

33d183e37dc796a08e17e8173c875736.png

某一个表达到阈值

任务统计结束后,01库的01表达到第一档阈值,重新初始化数据环的时候,移除一个权重,到达第二档再移除一个权重。

46b30bc095229531a7500b24b899fdfe.png

某一个表不再写入数据

当某个表数据量到达第三档,不再写入数据,重新初始化环,移除这个表的写入权重。

8ed031eb17f6e8269535bdc599130672.png

扩容新增数据库

增加数据库后,我们需要重新初始化环,需要确认新增数据写入扩容新增的数据库中,可以考虑手动触发重新初始化,如果任务时间间隔不长,也可以等待任务初始化,只需要注意任务执行后有数据写入新库即可。

权重调整策略

前面提到的权重调整,判断依据是表的数据量是否达到阈值,注意是分库分表的每个库里的每个表单独统计是否达到阈值进行调整,采用定时任务的方式执行,间隔频率可以根据数据增长量评估。

方案的局限性

这个方案虽然很好的解决了扩展增加数据库的问题,但是也有一些使用的局限,首先,依赖于数据分片key的生成,很难再已有数据的基础上使用这个方案(接受新的数据关联key有不小的调整成本)。其次,数据分片位生成策略也有一定的复杂度,这里不再深入,方案其实很多,借助于zk或者redis等三方工具可以搭建一个序号生成服务,既能保证一定的效率,也能保证序号的唯一性。

如果是新的业务线,在数据增量上无法预估未来一两年的数据量,考虑使用分库分表,不妨可以考虑这个方案,减少增加库和表带来的痛苦。以后有足够的运维能力支撑TiDB,分库分表带来的诟病才能一扫而光,最终还是单表操作来的香,啥也不多想,上去就干(TiDB也有不少坑等着踩)!

- END -

往期回顾

Kubernetes 架构及应用场景

服务端高并发分布式架构演进之路

阿里云Redis的规范:键值设计、命令使用、客户端使用、相关工具

工作流引擎 Activiti 教程(非常详细)

652e0b817d71a557de456024ebac3bbf.png

更多内容关注公众号:fullstack888

技术交流,请加微信: jiagou6688 ,备注:Java,拉你进架构群

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐