本期内容
背景 订单单表面临的问题 拆分类型介绍 常用分片算法 拆分工具介绍 订单分库分表 总结
订单分库分表不同于其他业务表分库分表,订单表的读写场景复杂,一般有买家维度、卖家维度、订单号维度3个主要维度。多读写维度情况下无论采取哪种维度做分库分表,对另外两种维度的查询性能来说,基本都是灾难。而在电商项目中,每个订单从创建到终态往往都会经历上百个业务环节,任何一个环节出问题就会导致整单数据异常。所以交易系统可能不是技术难度最深的系统,但一定是业务复杂度最高、稳定性要求最高的系统
本文将围绕交易系统核心订单表来为大家介绍订单分库分表演化历程
订单单表数据量大了之后一般会面临以下问题:
以上都是比较严重的问题,如果不予以解决,将会严重影响机器性能、限制业务发展
应对以上问题一般常用的优化思路如下:
仅通过以上优化方式,数据量几千万后,业务高峰期还是顶不住,压测也压不上去,这个时候就不得不采取分库分表措施了
根据业务特性,与拆分微服务的做法相似,将关联度低或数据量巨大的不同表存储在不同的数据库,或者将不经常用或字段长度较大的字段拆分到扩展表中
将大表按照某个字段采取不同的路由函数分散到多个数据库或多个表中,表字段完全一致,每个表中只包含一部分数据,所有表加起来等于全量数据
比如按年/日分表,同一天的数据都能落在同一张分表,后续订单的查询条件都必须带上时间属性
按时间分库分表比较难做,由于订单业务逻辑繁多,不可能所有业务都有时间作为划分,即使带上时间属性,业务查询时数据库压力分布也是在很小范围内,并不能减轻实例负载,所以并不适合订单分库分表的场景。时间可以作为后续做冷热分离的条件
HASH函数的算法是简单取模,若分库和分表使用不同拆分键进行HASH时,则根据分库键的键值直接按分库数取模,如果键值是字符串,则字符串会先被换算成哈希值再进行路由计算。若分库和分表都使用同一个拆分键进行HASH时,则根据拆分键的键值按总的分表数取模
以t_user表为例,拆分成8个库,每个库8张表:
采用HASH函数进行分库分表,考虑到订单会有按买家、按卖家、按订单号3种主要查询场景,就需要分别以买家ID、卖家ID、订单号3个维度去做3个不同的分库分表。这种分库分表方式可行。但是数据就会有3份,存储是需要考虑先按哪个维度、再按哪个维度、最后按什么维度,所以数据存储流程拉长了,出异常的可能性变大,对业务不太友好
选取两个拆分键,两个拆分键的后N位需确保一致,根据任一拆分键后N位计算哈希值,然后再按分库数取模,完成路由计算。此路由方式需要自行实现分片算法
以t_order表为例,拆分成8个库,每个库8张表:
如果采取RANGE_HASH函数作为分库分表,则最优方案是以订单号和买家id的后N位做分库分表,后续按订单号维度、买家id维度查询都能满足,卖家维度无法查询。但是前提是订单号后几位和买家id要有关联,涉及到订单号改造的过程
在拆分工具选择上,这里借助ShardingSphere[官网],ShardingSphere是一套开源的分布式数据库解决方案组成的生态圈,它们能提供数据分片、分布式事务、分布式治理等功能如果对ShardingSphere原理、使用感兴趣,大家可以去ShardingSphere[官网]进一步学习
通过对订单表读写场景、不同分表函数、改造成本的分析,订单表采用水平拆分适用的分表方式如下:
综上,因为订单一般都是由买家发起生成的,所以我们认为优先处理买家数据和订单号数据比较合理,所以我们先采用RANGE_HASH拆分算法按买家id后N位、订单号后N位维度做分库分表,作为买家表逻辑表。再用HASH拆分函数按商家id冗余一份数据,作为卖家表逻辑表
订单号生成规则需要根据买家表分表特性订单号后N位等于买家Id后N位做设计
比如用户id为12345678,则用户在下单时生成的单号为:xxxxxxxxx345678,单号前几位可以根据公司自己规则设定,但是要注意不能重复
一般单表数据量控制在100W到5000W之间比较合理,可以根据预估未来2-3年数据的增长预期计算出合适的分表数
历史订单没有按照订单号生成规则来生成,但是历史订单也会有买家id,所以拆分函数配置时,特意将买家id后N位作为前置条件,历史订单会以买家id后N位作为分表键落库
给历史订单建一个索引表,只保留订单号和买家id关系
CREATE TABLE `order_history_index` (
`orderId` bigint(20) unsigned NOT NULL COMMENT '订单ID',
`buyerUserId` bigint(20) unsigned NOT NULL COMMENT '买家的userId'
PRIMARY KEY (`orderId`)
)
历史订单根据订单号查询时先过索引表取到买家id,再查买家表取到具体数据
按照上述方式对订单分库分表后,业务里常用的查订单表sql,通过编码层面区分,路由到各个库的买家表、卖家表里,有效解决单表数据量过大的问题,提升系统稳定性和负载能力。
当然肯定还有个别复杂查询和分表键沾不上边的,这些业务场景对实时性要求不高的,可以走ES或者离线库去查询
牛年邀牛人
一起战斗、一起成长
技术、产品、UED、运营、职能等海量岗位
玩物得志期待你的加入