为什么需要分库分表?是因为MySQL等数据库单库单表能支撑的系统并发量和数据存储量存在瓶颈。
中国有超过 10 亿的网民,单日交易量超过百万、千万单的交易场景越来越多。按照一行数据 1K ,每日订单量 500 万计算,一年需要的数据存储为 1700G,行数1.7亿。
显然此种情况MySQL 单库单表难以保证查询写入性能。一张表一年的数据量恐怖如斯,交易场景成百上千张表,年复一年,所带来的存储和并发量压力何其巨大呢? 业务牵引技术的发展,分库分表技术应运而生。
目前分库分表技术似乎成为行业标准,站在鄙视链上游。不分库分表的系统似乎都是毫无亮点的垃圾系统,然而真的如此吗?
- 分库分表技术是完美的解决方案吗?它有哪些不足之处?
- 为什么单库单表存在系统瓶颈,就选择分库分表呢?有其他更好的方案吗?
接下来我将分享自己在电商交易场景实际遇到的痛点
1. 分库分表难以解决分布式事务问题
一般情况下通过选择合适的分片属性,可以保证业务在一个数据库内完成事务操作。
如用户领券场景,用户一次领取 3 张优惠券,系统写入 3 条优惠券记录和一条领券流水,使用 UserId 进行分库分表,可以保证同一个用户的 4 条记录路由到同一数据库,可使用 MySQL 本地事务即可保证数据一致性。
然而**当同一个事务中多张表的分片属性不同时,难以保证这些表在同一个数据库内完成事务操作,势必会出现难以解决的分布式事务难题**。
如优惠券存在库存,在发券时需同时扣减券库存,库存粒度为券模版ID,用户可同时领取多个券模版的券。如何保证库存扣减、用户发券、领取流水等在同一个数据库内完成呢?难以实现
优惠券和领券流水,基于 UserId 拆分数据库,但库存表无用户属性,只能使用券模版 ID分库。然而不同的分片属性,无法保证在同一个数据库完成事务操作,难以保证数据强一致性。
因此 当同一个事务中多张表的分片属性不同时,难以保证这些表在同一个数据库内完成事务操作,势必会出现难以解决的分布式事务难题。即便业务侧使用复杂的业务架构也难以实现强一致性,作出妥协后,可实现最终一致性。
分库分表技术把数据库解决不了的问题推到业务侧,“胁迫” 业务侧使用复杂方案参与解决,要求业务侧在数据一致性上作出妥协
2. 分库分表难以实现非分片属性的索引查询能力
电商交易场景一般使用 UserId 作为分片属性,联合索引的前缀都是UserId,但这并不绝对,交易场景中存在品牌、门店、司机、配送等其他维度。
如订单表使用UserId 作为分片属性进行分库分表,查询商家在最近 1 小时的订单如何实现呢?由于使用 UserId 分库分表,当使用商家 ID 查询时,需要查询所有的分库分表,这显然不现实。
业务侧有几种实现方案,1)使用商家 ID分库分表,异构另一份 MySQL存储 2)基于 ElasticSearch,异构另一种存储支撑其他维度的检索场景。
无论哪一种方案都会导致,数据一致性降低,但系统复杂度增加。
查询商家最近1 小时的订单,业务上如此简单清晰的需求,在分库分表后,实现方案竟如此复杂。此外还要求业务在数据准确性上作出妥协。
正是因为分库分表技术的先天不足,所以极大地增加了业务系统架构的复杂性。 (甚至很多人以架构复杂为荣,错误的认为系统越复杂越能体现架构能力)
此外还有其他更加 tricky的方案,如为了实现订单 Id 查询,在生成的订单Id中存放UserId后四位(大概意思),用于定位订单在哪个分片。
分库分表技术把数据库解决不了的问题推到业务侧,“胁迫” 业务侧使用复杂方案参与解决,要求业务侧在数据一致性上作出妥协
3. 分库分表导致的全局主键问题
MySQL 主键支持自增 ID,但是这一特性在分库分表后沦为鸡肋功能,系统需要分布式方式的 ID 生成器。
如优惠券系统在分库分表后,优惠券 ID 需要借助分布式ID生成器生成全局唯一 ID。常见的实现方式包括 UUID、雪花算法、美团 Leaf、百度 UidGenerator等。除维护业务系统外,还需要维护其他纯技术类系统。
毫无疑问,这再次增加架构的复杂性。
4. 分库分表难以建立全局唯一键约束
除全局主键问题,全局唯一键也难以实现。 当全局唯一键的前缀是非分片属性时,难以实现全局唯一键。
例如订单在履约完成后,会生成一笔履约单。系统明确一笔订单只能有一笔履约单,因此履约单上的订单 ID 字段应该增加唯一键约束。 然而履约单在基于 UserID 分库分表后,OrderId建立的唯一键约束只在本表内唯一,不能保证在所有的分片内实现全局唯一。
因此 分库分表后,数据库无法对 OrderId等非分片属性建立全局的唯一性约束。
除以上具体场景外,分库分表面临的难题依然有很多,如扩容难问题、存储成本高、运维成本高、高可用难等等问题。
5. 分库分表需要持续造轮子
选择分库分表后,需要业务系统开发和接入很多轮子,包括
- ShardingSphere、mycat等方便客户端接入分库分表。 如果使用 ShardingSphere需考虑多种语言多套轮子,使用 Mycat代理层方案又会面临性能风险。
- 分布式 ID 生成器生成全局 ID
- 为了支持非分片查询,需要 DTS 消费binlog,异构存储For 查询。
- 使用 ElasticSearch用于其他维度检索。
- 分库分表管理后台支持查询数据、修改数据
- 分库分表管理工具支持高效建表、修改表、加索引等 DDL 操作。(1000 张表后,手动建表不现实)
- ……
层出不穷的问题,需要层出不穷的轮子,有些轮子需要自己造,如分库分表管理工具等。
不禁要问,互联网大厂能承受如此复杂架构方案,所有的公司都能承受吗?导致架构如此复杂的根源是什么呢?分库分表方案上的先天不足
6. 分库分表后面临老大难的扩容问题
在系统建设之初,一个合格的架构师必须考虑到:未来数据量庞大后的存储扩容问题。这往往让人很难抉择,因为要权衡当下的硬件成本和未来扩容成本。
使用分库分表后,扩容非常困难。如8 个数据库,想继续拆分为 16 个数据库,一定会对业务造成影响,停机迁移是难以避免的问题,需要极多的运维工具保障扩容过程的安全性和快速性,做到对业务影响程度最低。因此扩容成本和风险都极高。
如果在系统建设初期就拆分为 16 个库,又会面临硬件和运维成本过高的问题。如果每一个业务在系统建设初期都如此铺张浪费,那么公司的硬件成本将极高。
7. 大量分库分表导致运维成本激增
仅一个业务团队就16 个数据库,公司那么多业务将会有多么庞大规模的数据库实例。有 DBA 曾分享经历,他一天新部署了120 套 mysql实例,可想而知,DBA 们面临了多么庞大的运维压力。
研发视角和 DBA视角不同,结论不同。业务研发更多考虑容量不足和并发度不足风险,会倾向于设置较大的分库分表规模,而 DBA 会考虑运维成本、硬件成本,希望数据库规模在可控范围内。 两者站在不同的视角,各有理由,这是一个矛盾。
我相信大多数业务研发不清楚,自家公司MySQL单库单实例的最高并发度,最高容量,在设置分片规模时往往是拍脑袋决定。只要研发定的分片方案不离谱,往往 DBA也就接受了。
这些决策中,往往还掺杂历史问题。如团队已经有了 10 个库,无论业务规模如何,新表默认拆分10 个库,而不会考虑是否真的需要 10 个库。
8. 为什么业务系统要替数据库负重前行?
分库分表技术把数据库解决不了的问题推到业务侧,“胁迫” 业务侧使用复杂方案参与解决,要求业务侧在数据一致性上作出妥协。
为什么单库单表存在系统瓶颈,就选择分库分表呢?有其他更好的方案吗?分布式数据库。
分布式数据库继承了传统单机数据库的核心特性,同时还拥有分布式系统的处理能力。虽然起步较晚,可以预见的是它将是数据库下一个发展方向。
如阿里的 OceanBase基于两阶段提交协议解决了分布式事务问题,提供了比肩本地事务的强一致性,此外还提供以下能力解决了分库分表技术难以解决的问题 1、2、3、4。相比分库分表,业务侧使用分布式数据库的门槛更低。
- 提供了全局索引,可以为非分片属性提供全局的索引和唯一性约束
- 提供了全局自增主键,无需分布式 ID 生成器。
OceanBase 在性能、扩容能力、运维和硬件成本上,相比分库分表有更优的表现 深入OceanBase内部机制:高性能分布式(实时HTAP)关系数据库概述
单机数据库能支撑的并发量和存储量的天花板很低,分库分表和分布式数据库是解决此类问题的两种方案。分库分表通过分库分表中间件及一系列轮子整合了独立的数据库实例,可解决并发量和存储量的难题,但是牺牲了一致性,增加了使用门槛。而分布式数据库则直面困难,使用分布式集群架构在解决并发量和存储量挑战的同时,也提供了类比单机数据库的使用门槛和强一致性事务能力。
就像 ElaticSearch 替代 Lucene ,RedisCluster 替代单机Redis一样,低门槛、强一致性、支持高并发大数据量存储的分布式数据库一定是数据库的主流发展方向。
分库分表更像是开源数据库技术落后互联网业务发展时,不得已的临时过渡方案。
我的开源项目
最后夹带一点私货,五阳最近花了3个月的时间完成一个开源项目。
开源3周以来,已有近 230 多个关注和Fork
Gitee:gitee.com/juejinwuyan…
GitHub github.com/juejin-wuya…
开源平台上有很多在线商城系统,功能很全,很完善,关注者众多,然而实际业务场景非常复杂和多样化,开源的在线商城系统很难完全匹配实际业务,广泛的痛点是
- 功能堆砌,大部分功能用不上,需要大量裁剪;
- 逻辑差异点较多,需要大量修改;
- 功能之间耦合,难以独立替换某个功能。
由于技术中间件功能诉求较为一致,使用者无需过多定制化,技术中间件开源项目以上的痛点不明显,然而电商交易等业务系统虽然通用性较多,但各行业各产品的业务差异化极大,所以导致以上痛点比较明显
所以我在思考,有没有一个开源系统,能提供电商交易的基础能力,能让开发者搭积木的方式,快速搭建一个完全契合自己业务的新系统呢?
- 他们可以通过编排和配置选择自己需要的功能,而无需在一个现成的开源系统上进行裁剪
- 他们可以轻松的新增扩展业务的差异化逻辑,不需要阅读然后修改原有的系统代码!
- 他们可以轻松的替换掉他们认为垃圾的、多余的系统组件,而不需要考虑其他功能是否会收到影响
开发者们,可以择需选择需要的能力组件,组件中差异化的部分有插件扩展点能轻松扩展。或者能支持开发者快速的重新写一个完全适合自己的新组件然后编排注册到系统中?
memberclub 就是基于这样的想法而设计的。 它的定位是电商类交易系统工具箱, 以SDK方式对外提供通用的交易能力,能让开发者像搭积木方式,从0到1,快速构建一个新的电商交易系统!
具体介绍可参见
Gitee开源地址:gitee.com/juejinwuyan…
GitHub开源地址 : github.com/juejin-wuya…
在这个项目中你可以学习到 SpringBoot 集成 以下框架或组件。
- Mybatis、Mybatis-plus 集成多数据源
- Sharding-jdbc 多数据源分库分表
- redis/redisson 缓存
- Apollo 分布式配置中心
- Spring Cloud 微服务全家桶
- RabbitMq 消息队列
- H2 内存数据库
- Swagger + Lombok + MapStruct
同时你也可以学习到以下组件的实现原理
- 流程引擎的实现原理
- 扩展点引擎实现原理
- 分布式重试组件实现原理
- 通用日志组件实现原理 参考:juejin.cn/post/740727…
- 商品库存实现原理: 参考:juejin.cn/post/731377…
- 分布式锁组件: 参考:
- Redis Lua的使用
- Spring 上下文工具类 参考: juejin.cn/post/746927…