【进阶篇】礼物系统/打赏系统的高并发架构拆解

0 阅读6分钟

礼物打赏是很多社交App的核心变现方式。但一场直播中,几万人同时送礼物,怎么保证系统不崩?怎么保证礼物不丢?怎么保证数据准确?这篇文章拆解高并发场景下礼物系统的技术架构。

一、礼物系统的业务模型

1.1 核心步骤

用户A送礼物给主播B:
 1. 扣减A的余额  
 2. 生成礼物记录  
 3. 增加B的收入  
 4. 全屏特效播放  
 5. 排行榜更新

1.2 关键业务规则

规则说明
幸运礼物随机倍数返还,有概率触发大奖
连击礼物连续送N个礼物,触发连击特效
全服广播昂贵礼物全服通知
排行榜实时更新贡献榜、收到礼物榜
分成结算平台抽成、主播分成

1.3 技术挑战

挑战说明
高并发热门主播,几万人同时送礼物
数据一致性余额扣减、礼物记录、主播收入必须一致
实时性礼物特效、排行榜要实时更新
准确性每一笔礼物金额必须准确无误

二、架构设计

2.1 整体架构

在这里插入图片描述

2.2 服务拆分

服务职责技术要点
礼物服务礼物发送、礼物查询核心业务逻辑
账户服务余额管理、扣款、充值事务一致性
排行榜服务实时排行、贡献榜Redis Sorted Set
通知服务全服广播、特效触发消息推送

三、高并发设计

3.1 异步化处理

问题: 同步处理礼物请求,数据库压力大,响应慢。

解决方案: 核心流程同步,非核心流程异步。

同步流程(必须立即完成):  
1. 扣减余额(Redis原子操作)  
2. 生成礼物流水(返回成功)  
3. 播放特效(客户端本地播放)  
  
异步流程(可延迟处理):  
1. 主播收入增加  
2. 排行榜更新  
3. 数据库持久化  
4. 分成结算

3.2 消息队列削峰

消息队列削峰

队列设计:

礼物消息队列:  
1. gift_queue:high    # 高价值礼物,优先处理

2. gift_queue:normal   # 普通礼物

3. gift_queue:low     # 低价值礼物,批量处理

3.3 批量处理

连击礼物合并:

用户连续送100个礼物,不发送100次请求,合并为1次请求 。

{
    gift_id: 1001,
    count: 100,
    total_amount: 100 * 单价
 }

四、数据一致性设计

4.1 余额扣减

问题: 并发扣款时,可能扣成负数。

解决方案:Redis原子操作 + Lua脚本

-- 扣款Lua脚本  
local balance = redis.call('GET', KEYS[1])  
if not balance then  
    return -1  -- 用户不存在  
end  
  
local amount = tonumber(ARGV[1])  
if tonumber(balance) < amount then  
    return -2  -- 余额不足  
end  
  
redis.call('DECRBY', KEYS[1], amount)  
return 1  -- 成功

4.2 分布式事务

问题: 扣款成功、礼物记录成功、主播收入增加失败,数据不一致。

解决方案:本地消息表 + 最终一致性

1. 扣减用户余额(Redis + MySQL)  
2. 写入礼物记录  
3. 写入本地消息表(待处理状态)  
4. 返回成功  

异步任务:  
5. 读取本地消息表  
6. 增加主播收入  
7. 更新消息表状态为已完成

消息表设计:

CREATE TABLE gift_message (  
    id BIGINT PRIMARY KEY AUTO_INCREMENT,  
    gift_id BIGINT NOT NULL,  
    from_user_id BIGINT NOT NULL,  
    to_user_id BIGINT NOT NULL,  
    amount DECIMAL(10,2) NOT NULL,  
    status TINYINT DEFAULT 0 COMMENT '0-待处理 1-已完成 2-失败',  
    retry_count INT DEFAULT 0,  
    created_at DATETIME,  
    INDEX idx_status (status)  
);

4.3 幂等性保证

问题: 网络重试导致礼物重复发送。

解决方案: 礼物请求唯一ID

# 礼物去重  
gift_dedup:{user_id}:{request_id}  
  
# 存在则返回已有结果  
# 不存在则处理请求

五、排行榜设计

5.1 Redis Sorted Set

# 主播收礼物排行榜  
rank:gift:daily:{date}    # 日榜  
rank:gift:weekly:{week}   # 周榜  
rank:gift:monthly:{month} # 月榜  
rank:gift:total           # 总榜  
  
# 用户贡献榜  
rank:contribution:{anchor_id}  # 某主播的贡献榜

5.2 更新操作

# 增加贡献值  
ZINCRBY rank:gift:daily:20240101 100 "anchor_123"  
  
# 获取Top 100  
ZREVRANGE rank:gift:daily:20240101 0 99 WITHSCORES  
  
# 获取用户排名  
ZREVRANK rank:gift:daily:20240101 "anchor_123"

5.3 排行榜优化

优化点方案
冷数据清理定时任务清理过期的排行榜数据
分页查询只查询Top N,不查询全量
缓存热点排行榜结果缓存,减少查询
异步更新礼物发送后异步更新排行榜

六、幸运礼物设计

6.1 业务逻辑

用户送幸运礼物 :

  1. 扣减用户余额
  2. 随机计算返还倍数(1x-100x)
  3. 增加用户余额(返还部分)
  4. 播放特效
  5. 全服通知(如果大奖)

6.2 随机算法

def calculate_lucky_multiplier(gift_amount):  
    """  
    根据礼物金额计算幸运倍数  
    返还金额 = 礼物金额 × 倍数  
    """  
    random_value = random.random()  
      
    if random_value < 0.0001:      # 0.01% 概率 100x  
        return 100  
    elif random_value < 0.001:     # 0.1% 概率 50x  
        return 50  
    elif random_value < 0.01:      # 1% 概率 10x  
        return 10  
    elif random_value < 0.1:       # 10% 概率 2x  
        return 2  
    else:                          # 90% 概率 0x  
        return 0

6.3 公平性保证

问题: 如何保证随机结果公平,不能被操控?

解决方案:

· 服务端计算,客户端只展示结果

· 随机种子基于礼物ID + 时间戳,不可预测

· 记录所有随机结果,可审计

七、监控与告警

7.1 关键指标

指标说明告警阈值
礼物发送QPS每秒礼物请求数>10000
礼物成功率成功/总数<99%
扣款失败率余额不足等>5%
消息队列积压待处理消息数>10000
排行榜更新延迟更新耗时>1s

7.2 数据对账

定期对账任务:

每小时执行:

  1. 统计礼物总金额
  2. 统计用户扣款总额
  3. 统计主播收入总额
  4. 校验三者是否一致,不一致 → 告警 + 人工处理

八、容灾设计

8.1 服务降级

场景降级策略
消息队列积压礼物记录先写Redis,异步同步
排行榜服务故障排行榜暂停更新,显示缓存数据
数据库压力大批量写入,减少单条写入

8.2 故障恢复

服务恢复后:

  1. 处理消息队列中的积压消息
  2. 同步Redis数据到MySQL
  3. 重建排行榜
  4. 执行数据对账

九、架构演进路径

阶段一:简单架构 单服务 + 同步处理 + 支持1000 QPS

阶段二:异步架构 服务拆分 + 消息队列 + 支持10000 QPS

阶段三:高并发架构 Redis原子操作 + 分库分表 + 多机房部署 + 支持50000+ QPS


下篇预告: 《社交推荐系统:从协同过滤到实时推荐》——让用户发现更多有趣的人和内容。

持续输出社交App开发实战经验,关注我,一起成长。