摘要
2020年直播财务和审计沟通之后决定把【星豆兑换星币】功能获得的星币也纳入到营收的范畴,这个改动相当于对我们原有的账户体系做一个比较大的改动。星币账户=充值账户+赠送账户 修改为 星币账户=充值账户+赠送账户+赠送账户。另外用户消费的扣费、冻结、解冻等账户之间的扣减顺序也需要做出变动,另外在上线的时候无法灰度。于是前期我们选择使用旁路计算的方式计算兑换账户的流水和余额,后期再对用户的兑换余额做迁移。
图1.账户体系
为了简化和统一后续说明,我们统一把星币账户、充值账户、兑换账户、赠送账户分别定义为coin,money,exchange,virtual
图2.整体方案
一、需求背景
同上
二、问题&难点
1.旁路计算
- 计算规则复杂
用户流水计算
场景 | 计算规则 | 备注 |
---|---|---|
星豆兑换星币 | 80%星币算入exchange,20%星币算入virtual | |
扣费/冻结/管理员调整 | money>exchange>virtual | 扣费顺序 |
退款/解冻原路退回 | virtual>exchange>money | 退款顺序 |
其他 | exchange=0 |
消费流水分摊规则
由于历史的原因,一笔扣费的流水可能会产生多笔消费流水(分摊给多个不同的主播)。这时候我们需要对exchange进行分摊操作。核心的逻辑是按照每笔消费订单分摊到的coin,根据订单的顺序,依次尝试从money、exchange的池中获取相应的money和exchange进行分摊。在每个用户分摊的过程中同样遵循money>exchange>virtual的顺序,下面举个例子说明。
coin=1000,money=500,exchange=300。我们尝试分摊到3笔消费流水
消费流水 | coin | money | exchange | virtual |
---|---|---|---|---|
A | 100 | 200 | 0 | 0 |
B | 500 | 300 | 200 | 0 |
C | 400 | 0 | 100 | 300 |
这里尝试介绍一些比较核心的场景,实际的场景会比描述的复杂一些
- 对流水时序性严格要求
假设用户coin=100,money=0,exchange=60,virtual=40。
我们假设A,C为消费流水,B为星豆兑换星币的流水。
流水顺序 | A | B | C |
---|---|---|---|
coin账户 | -80 | +20 | -20 |
更新后coin账户 | 20 | 40 | 20 |
exchange账户 | -60 | +20 | -20 |
更新后exchange账户 | 0 | 20 | 0 |
按A,B,C的流水顺序我们能够计算得到用户的累计exchange消费=80。
假设我们把流水顺序颠倒一下,A->C->B
流水顺序 | A | C | B |
---|---|---|---|
coin账户 | -80 | -20 | +20 |
更新后coin账户 | 20 | 0 | 20 |
exchange账户 | -60 | 0 | +20 |
更新后exchange账户 | 0 | 0 | 20 |
按A,B,C的流水顺序我们能够计算得到用户的累计exchange消费=60。
流水顺序的错乱会导致整个计算结果的错误。
- 需要支持按指定位点重跑
由于计算规则的复杂性和新业务上线同样会对现有的规则进行扩充和变更,这可能会导致旁路计算的错误。如何及时从就近的一个正确的快照位点恢复是非常重要的。
2.数据迁移
旁路计算确实能够满足财务和审计当前的需求,但是由于计算规则的复杂,已经出现多次由于线上新规则上线没有同步到旁路计算导致的计算错误的问题。我们最终还是希望能够把旁路计算的逻辑最终迁移到业务系统直接计算,尽量减少由于规则变更导致的问题。
- 计算模型抽象
原来系统的账户扣减的优先顺序和分摊逻辑散落在不同的接口中,我们首先要做就是把这部分逻辑抽象和统一。
图2.2.1 模型抽象
- 数据迁移之痛
传统的数据迁移基本都是基于实时的数据进行迁移(比如分表)。迁移的时候仅仅需要对数据进行加锁/禁写,在把数据同步到新表中即可达到目的。
图2.2.2 分表数据迁移方案
然而旁路计算由于依赖了一些异步写入的数据源,导致旁路计算无法做到严格意义上的实时(目前有分钟级别的延时),因此在实时数据迁移的过程中我们不能简单地对数据进行复制。
图2.2.3 数据迁移难点
三、业界解决方案对比
- 实时计算
旁路计算的本质还是基于stream数据的实时计算,业界常用的解决方法,storm,spark,flink。这里我们简单介绍下flink的核心架构。
图3.1.1 flink整体架构
后续我们希望通过使用flink来简化我们整个计算模型的复杂度
- 数据迁移
//TODO 把分表的COPY来一份
四、整体设计
4.1 设计目标
- 通过旁路计算的方式能够在不影响应用程序主逻辑的场景下保证数据的准确性
- 通过存量的数据迁移+热点数据迁移,保证在不停服的情况下将用户的余额迁移到新的余额表
4.2 实现方法及效果
图4.2.1 整体方案
- 计算模型抽象
- 旁路计算快速恢复
- sink逻辑优化
- 流水时序问题
- 数据验收
- 兑换账户迁移数据一致性