FIFO算法详解
📋 算法概述
FIFO(First In First Out)算法是算力点系统的核心消费策略,确保用户优先使用即将过期的点数,最大化点数利用率,减少用户损失。
🎯 设计目标
用户体验优化
- 减少点数过期浪费
- 透明的消费优先级
- 合理的资源利用
系统性能优化
- 高效的消费计算
- 最小化数据库查询
- 保证数据一致性
🔧 算法原理
基本思路
- 获取账本: 查询用户所有活跃账本
- 时间排序: 按过期时间升序排列
- 逐个扣减: 从最早账本开始消费
- 状态更新: 更新账本余额和状态
核心流程
FIFO 消费算法流程:
- 接收消费请求:接收点数消费请求
- 查询用户活跃账本:查询用户所有活跃状态的点数账本
- 按过期时间排序:将账本按过期时间升序排列
- 计算总余额:计算所有账本的总余额
- 余额充足判断:
- 余额不足 → 返回余额不足错误
- 余额充足 → 开始 FIFO 消费
- FIFO 消费:
- 从最早过期的账本开始扣减
- 判断是否还需继续消费
- 如果还需消费 → 继续下一个账本
- 如果消费完成 → 创建消费记录
- 创建消费记录:记录本次消费的详细信息
- 更新账本状态:更新相关账本的余额和状态
- 返回消费结果:返回消费成功的结果
💻 实现细节
数据结构
type UserPointLedger struct {
ID string
UserID string
Balance int64
ExpiresAt time.Time
Status LedgerStatus
}
type LedgerDeduction struct {
LedgerID string
Amount int64
}
核心算法
func (uc *PointUseCase) ConsumePoints(
ctx context.Context,
userID, tenantID, productID, description string,
requiredAmount int64,
) (*PointConsumptionRecord, *PointTransaction, error) {
// 1. 获取活跃账本(按过期时间排序)
ledgers, err := uc.userPointLedgerRepo.ListActiveUserPointLedgers(ctx, userID)
if err != nil {
return nil, nil, err
}
// 2. 计算总余额
var totalBalance int64
for _, ledger := range ledgers {
totalBalance += ledger.Balance
}
// 3. 检查余额是否充足
if totalBalance < requiredAmount {
return nil, nil, ErrorInsufficientBalance("余额不足")
}
// 4. FIFO消费逻辑
remainingAmount := requiredAmount
var deductions []LedgerDeduction
for _, ledger := range ledgers {
if remainingAmount <= 0 {
break
}
// 计算本次扣减金额
deductionAmount := min(ledger.Balance, remainingAmount)
deductions = append(deductions, LedgerDeduction{
LedgerID: ledger.ID,
Amount: deductionAmount,
})
// 更新账本余额
newBalance := ledger.Balance - deductionAmount
status := UserPointLedgerStatusActive
if newBalance == 0 {
status = UserPointLedgerStatusFullyConsumed
}
err = uc.userPointLedgerRepo.UpdateBalance(ctx, ledger.ID, newBalance, status)
if err != nil {
return nil, nil, err
}
remainingAmount -= deductionAmount
}
// 5. 创建消费记录和交易记录
record := &PointConsumptionRecord{
UserID: userID,
TenantID: tenantID,
ProductID: productID,
ConsumedAmount: requiredAmount,
LedgerDeductions: marshalDeductions(deductions),
Description: description,
CreatedAt: time.Now(),
}
// ... 保存记录并返回结 果
}
📊 性能优化
数据库优化
-- 优化查询索引
CREATE INDEX idx_user_ledger_expires ON user_point_ledgers(user_id, expires_at);
CREATE INDEX idx_user_ledger_status ON user_point_ledgers(user_id, status);
-- 高效的账本查询
SELECT id, balance, expires_at
FROM user_point_ledgers
WHERE user_id = $1 AND status = 'ACTIVE'
ORDER BY expires_at ASC;
缓存策略
- 热点数据缓存: 用户总余额
- 查询结果缓存: 账本列表
- 失效策略: 账本变更时清除
批量处理
// 批量更新账本余额
func (r *UserPointLedgerRepo) BatchUpdateBalance(
ctx context.Context,
updates []BalanceUpdate,
) error {
// 使用事务批量更新
return r.client.Transaction(ctx, func(tx *ent.Tx) error {
for _, update := range updates {
err := tx.UserPointLedger.
UpdateOneID(update.LedgerID).
SetBalance(update.NewBalance).
SetStatus(update.Status).
Exec(ctx)
if err != nil {
return err
}
}
return nil
})
}