在业务发展的早期,为了快速迭代及时响应市场需求,通常架构比较简单,比如数据库表,开始是单表,但随着业务的发展,数据量逐步膨胀,开始考虑分库分表。
本文以交易系统的订单表为例:
订单通常分为两张表:
- 订单主表(order_main)
描述 | 字段名 |
---|---|
订单id | order_id |
买家id | buyer_id |
卖家id | seller_id |
下单时间 | create_order_time |
付款时间 | pay_time |
发货时间 | deliver_time |
买家确认收货时间 | confirm_receive_goods_time |
订单金额 | order_sum |
订单状态 | order_status |
其它业务字段 | 。。。 |
- 订单明细表(order_entry)
描述 | 字段名 |
---|---|
明细id | order_entry_id |
订单id | order_id |
商品名称 | product_name |
商品id | product_id |
商品金额 | product_price |
购买数量 | quantity |
物流单id | logistics_order_id |
快照id | snapshot_id |
明细状态 | entry_status |
其它业务字段 | 。。。 |
注意点:
- 订单主表与明细表一对多关系
- 每时每刻都有新的数据进来,可能是新增订单,也可能是订单修改,业务方不接受停机迁移方案
问题:
- 老order_main的订单id采用mysql自增方式,如果采用分表比如1024张,关于订单id肯定要提供单独的id生成器服务
- 迁移到新表,采用新的订单id,老的订单id与新的订单id需要建立映射关系
核心步骤
一、双写
原来的创建订单接口上,增加新库的写入逻辑
过程:
- 老库创建订单
- 申请新的订单号(为了查库方便,新订单分表键采用订单号,订单号采用id序列号+buyer_userId后6位组成,理论上可最多分100W张表)
- 这样分的好处,无论是按买家维度查订单列表,还是按订单号查询,都能快速定位到具体表
- 在新库创建订单
- 并建立新老order_id之间的映射关系
新订单,如果是修改操作,新、老表都同时修改,保证事务。
老订单,修改操作,只操作老表
二、全量数据迁移
- 启动任务,开始全量迁移老表的数据,到新的表中,返回新的order_id
- 并建立新老order_id之间的映射关系
注:
- 老的订单要在老库完结。此时对老库表记录修改时,需要发kakfa,异步任务监听,同步数据到新表
三、读切到新库
- 所有依赖于老的订单表的接口,增加中间层逻辑
- 先根据老的订单号查新的订单号
- 将流量切到新的订单服务接口
注意:
- 上面的步聚2和3之间会有时间间隔,所以这段时间的订单修改记录要单独存储
- 在步骤3完成后,对这些增量数据再做一次同步
四、下双写
- 对老订单表的写依赖下线