冷热分离
冷热分离
需求分析
随着时间的推移,订单数据会逐渐增加,而在这些数据中,超过15天的订单是无法再进行变化的
因此一笔订单在15日之后的访问频率就会大大降低,为了提高订单表的查询效率,可以它转移到单独的库中
也就是将订单数据分到两个库中进行存储
- 订单库:存储15日之内的订单(数据量较小,访问频率高,属于热数据)
- 历史订单库:存储15日之前的订单(数据量庞大,访问频率低,属于冷数据)
这就是冷热分离(根据数据的特性,将数据划分为冷数据和热数据,然后针对冷热数据进行分离存储)
存储选型
订单冷热分离之后,通常采用MongoDB、HBase、TiDB等分布式数据库来存储冷数据
这么多数据库该如何选择呢?
假设电商项目有50万用户规模,请估算出订单数据量的规模
- 用户活跃度:假设50万用户中有20%是活跃用户,也就是有50万 * 20% = 10万活跃用户
- 订单频率:假设每个活跃用户每月至少下一次订单,也就是每年有10万 * 12次/年 = 120万订单
- 订单大小:假设每个订单大约占用3KB的空间,也就是120万 * 3KB ≈ 3.6GB
对于年百万级别的数据量,比较适合的方案是使用分布式数据库来存储,首选就是TiDB
TiDB 是由PingCAP公司研发设计的开源分布式数据库,它结合了传统的关系型和非关系型数据库的最佳特性
与传统的单机数据库相比,具有以下优势:
- 纯分布式架构,拥有良好的扩展性,支持弹性的扩缩容
- 支持SQL,对外暴露MySQL的网络协议,并兼容大多数MySQL的语法,在大多数场景下可以直接替换MySQL
- 默认支持高可用,在少数副本失效的情况下,数据库本身能够自动进行数据修复和故障转移,对业务透明
- 支持ACID事务,对于一些有强一致需求的场景友好,例如:银行转账
- 具有丰富的工具链生态,覆盖数据迁移、同步、备份等多种场景
但是它也有缺点:
- 作为分布式数据库,对数据存储节点硬件要求比较高
- 不支持存储过程、分区和GBK,数据写入时TiDB压力比较大
- 分布式部署对网络要求也非常高
生产环境建议使用TIDB,但是教学环境可以使用MySQL来暂时替代
方案设计
本项目要在历史订单服务中完成两个主要的功能:
- 对所有终结订单(已完成、已取消、已关闭状态)进行数据统计分析
- 对历史订单进行存储和查询
因此,当订单一旦终结就需要先同步到历史订单库的history_orders_sync表中等待统计分析
当订单时间到达15日的时候,再从history_orders_sync表转移到history_orders表中做存储
方案实施
订单同步
根据冷热分离方案,首先将完成、取消、关闭的订单同步到历史订单库jzo2o-orders-history,涉及的表如下:
- history_orders_sync:用于存储待迁移的订单数据(包括已完成,取消、关闭的订单)
- history_orders_serve_sync:用于存储待迁移的已完成的服务单数据
- 当订单完成、取消、关闭时向同步表写入记录
代码在订单状态机中:com.jzo2o.orders.base.config.OrderStateMachine.postProcessor() - 当监听到订单库history_orders_sync表发生变化时写入到历史订单库history_orders_sync表
代码在MQ监听器中:com.jzo2o.orders.history.handler.HistoryOrdersSyncHandler
冷热分离
订单完成15日后需要迁移到历史订单表
在历史订单同步表的sort_time字段中记录订单完成后15天的时间点
例如订单是2025-01-01 01:00:00完成的,那么sort_time字段中记录的就是2025-01-16 01:00:00
所以,只需要起一个定时任务,每天凌晨将sort_time中时间为前一天的记录迁移到历史订单表就可以
代码位置:com.jzo2o.orders.history.handler.XxlJobHandler.migrateHistoryOrders()
测试:
在测试时修改history_orders_sync表中sort_time字段值小于等于昨天日期
预期结果:
数据由history_orders_sync迁移到history_orders表
订单查询
在运营端进行订单查询的时候,就可以对冷热数据进行分别查询了
订单列表查询
- 查询jzo2o-orders库的orders表
- 代码在 com.jzo2o.orders.manager.controller.operation.OperationOrdersController.page()
历史订单查询
- 查询jzo2o-orders-history库的history_orders表
- 代码在 com.jzo2o.orders.history.controller.operation.HistoryOrdersController.queryForPage()