Raḥimahu Allāh

愿真主怜悯他

Posted by BX on Tue, May 6, 2025

🎯 一、积分兑换系统全景总结(含同步与异步版本对比、设计哲学)

里程兑换又上新了。真棒。又是怀念我J总、HB老板和H哥的一天。

系统关键目标

  • 高并发下安全的积分扣减和商品发放
  • 明确交易状态,确保最终一致性
  • 兼顾用户体验与系统可维护性

核心流程图(异步落账版本,商品同步)

 1[用户请求]
 2 3[前置校验](风控、兑换资格、次数)
 4 5[积分预扣阶段(事务)]
 6    - 查询总表判断积分是否足够
 7    - Redis DECR 次数、疲劳度
 8    - 乐观锁扣减积分表 + 插入 right_record = prelock
 9        - UPDATE user_points WHERE points = 原值
10        - INSERT right_record
1112[调用商品服务(同步)]
1314  ┌────────────┬──────────────┐
15  │            │              │
16成功         失败(或超时)   异常场景
17  │            │              │
18  ↓            ↓              ↓
19[更新 right_record = pending]   [更新为 cancel + 回滚 Redis + 补偿积分表]
20[发布积分核销事件]
2122[异步 Worker 消费事件]
2324[积分明细落账处理(事务)]
25    - 删除 & 拆分明细
26    - 更新 right_record = success

核心状态机(right_record.status)

状态含义
prelock积分预扣成功,未发货
pending商品发货成功,积分待落账
success核销完成,交易完成
cancel任意失败,整体回滚

🔍 核心设计点说明

1. 乐观锁的使用(积分总表)

  • 查询积分后在更新时加 WHERE points = 原值
  • 防止并发下出现双重扣减或超发
  • 未命中时直接视为失败,避免锁竞争

2. 商品服务为什么保留同步?

  • 商品发放是交易中最关键的操作:一旦发货不能撤回

  • 异步发货会带来以下风险:

    • 无法准确确认是否发出
    • 回查接口不一定可靠
    • 需要复杂补偿逻辑
  • 保留同步调用可以:

    • 明确交易分界点
    • 避免二次发货
    • 提高可控性

Trade-off:牺牲部分吞吐换来交易确定性

3. 异步化为什么只做积分落账?

  • 原因:明细落账是数据库中最耗时的操作

    • 查询 + 多条删除 + 拆分更新
  • 用户感知度低(核销可晚点完成)

  • 商品已发货不可撤销,因此积分落账放后异步处理

4. right_record 的角色

  • 交易锚点:状态全由它驱动
  • 幂等保证:无论同步/异步失败后重试都可以靠它定位
  • 监控与补偿:系统只需扫描 prelock / pending 记录即可

各阶段事务说明

阶段是否事务内容
积分预扣阶段Redis 扣次数(非事务) + MySQL 扣积分、插入记录(事务)
商品服务调用同步调用,决定是否进入异步落账
积分明细落账删除、拆分明细,更新状态
回滚流程部分事务积分表回加(事务),Redis 回滚(非事务)

补偿机制 & 幂等处理

  • 补偿扫描:right_record where status in (‘prelock’, ‘pending’)
  • 异步落账幂等处理:检查 right_record.status 是否已为 success
  • Redis 回滚失败记录:补偿任务执行 Lua 脚本自动恢复
  • 商品服务失败:走 retry + cancel 路径

Trade-off 一览

决策价值代价
商品服务同步处理明确交易边界,确保发货安全牺牲部分吞吐
落账异步处理减少用户等待,提升 RT增加补偿和监控复杂度
乐观锁扣积分无需锁表,抗并发强需配合幂等防止重复扣减
不冻结明细实现简单需 fallback 容忍部分过期冲突

推荐优化路径(未来演进)

  • 使用 Kafka / NSQ 替代内建 eventBus,实现跨服务可靠消费
  • right_record 支持多子状态:落账中、补偿中等
  • 明细层面增加“冻结”状态,防止核销中被后台清除
  • 积分总表刷新的 worker 做 checksum 与审计校验
  • 引入 trace ID 全链路追踪

总结:这是一套精心平衡“吞吐、准确性、可控性”的积分系统架构

  • 发货必须成功 → 同步,确认边界
  • 落账可以稍后 → 异步,提升性能
  • 异步机制 + 状态机 + 幂等机制确保最终一致
  • 整体设计允许局部故障,但能最终收敛、可补偿

🎯 二、弹幕系统设计方案(高并发、低延迟、4个9高可用)

这面试官长得可真像QHY,怀念我曾经好兄弟的一天。

一、目标与挑战

  • 支持百万级并发连接,低延迟实时消息传输
  • 架构具备高可用、易扩展、易维护
  • 核心关注推送链路(接收弹幕)性能与稳定性

二、整体架构简述(逻辑层次)

  1. 客户端 WebSocket 长连接接入
  2. WebSocket 网关负责连接池管理和消息推送
  3. 发弹幕走普通业务链路(HTTP / RPC)
  4. 业务服务处理逻辑、异步持久化、消息入队
  5. 消息队列(MQ)做弹幕广播中转
  6. 推送服务消费 MQ 消息,按房间广播推送

三、链路设计

1. 发弹幕链路(客户端 → 服务端)

  • 客户端发弹幕使用 HTTP / RPC 请求
  • 业务服务校验 + 异步写库 + 投递到 MQ

2. 弹幕广播链路(服务端 → 客户端)

  • MQ 作为消息中心(Kafka / 内存 MQ)
  • 推送服务消费后 fanout 到房间内用户
  • 网关根据房间连接列表,将消息通过 WebSocket 推送

四、关键模块设计

WebSocket 网关

  • 长连接维护,支持水平扩展(如 Redis + 一致性哈希)

消息队列(MQ)

  • 每个房间一个逻辑 Topic,支持批量、压缩、高吞吐

推送服务

  • 消费 MQ 消息,批量广播到活跃连接

五、性能与可用性优化

优化点策略
发弹幕延迟异步落库
广播效率批量推送、合并帧
高可用性多实例部署,跨 AZ 容灾

六、设计取舍说明:发弹幕为何不走 WebSocket?

  • 单用户发弹幕为低频操作,适合走 HTTP 请求
  • WebSocket 专注于广播推送链路,职责更清晰
  • 便于限流、鉴权、打点等通用请求控制策略

七、精华答题版(2 分钟表达)

我会把弹幕系统分为两个核心链路:发弹幕推弹幕

  • 发弹幕我采用常规的 HTTP/RPC 链路,因为用户发弹幕是低频行为,不需要长连接,走短链路更适合做限流、鉴权、埋点,也易于维护。

  • 推弹幕走 WebSocket + 消息队列(MQ)链路。客户端与网关维持长连接,服务端通过业务逻辑将弹幕写入 MQ,推送服务按房间消费后,fanout 给所有在线用户。这样能实现毫秒级推送。

  • 架构上我做了解耦:客户端连网关,业务服务异步处理并落库,推送层独立消费推送。WebSocket 网关支持水平扩展,通过连接状态表或 Redis 路由。

  • 为了高可用,所有服务都可无状态部署,多实例部署在多机房,结合心跳、重连机制和状态热备保证整体 SLA。

这种设计能保证大规模并发下的 低延迟、稳定性与弹性扩展性,同时逻辑清晰,职责划分明确,适合生产环境落地。

🎯 三、秒杀系统设计:同步与异步双版本对比总结

面向高并发场景下的限时抢购/抽奖系统,强调稳定性、并发控制、正确性与用户体验。

  • 支撑高并发瞬时请求(10w ~ 1000w QPS)
  • 避免超卖 / 重复抢购
  • 快速反馈、良好用户体验
  • 系统可扩展、可降级、可回放

🚀 同步秒杀系统设计(轻量高效型)

关键特征:

  • 用户请求同步返回“是否抢购成功”
  • 所有核心逻辑在内存/缓存内完成,响应超快
  • 适合中小型促销、低延迟要求

核心机制:

步骤说明
1. 鉴权 + 限流用户身份校验、IP限速、QPS控制(网关层 + Nginx + Redis)
2. 秒杀时间判断服务层快速校验是否在允许时间窗口内
3. Redis 原子校验脚本Lua 脚本判断库存、用户是否参与,扣减库存并记录成功
4. 返回结果立即告诉用户抢购成功/失败/重复
5. 异步补偿(可选)成功用户写入异步队列做落单、通知、物流等操作

🚀 大促型异步秒杀系统设计(弹性高可用型)

关键特征:

  • 请求快速入队,用户收到“排队中”/“提交成功”响应
  • 处理链路异步化,松耦合、支持削峰填谷、系统隔离
  • 用户状态查询/推送分离于主链路

典型处理链路

 1[用户请求]
 2 3[网关/接入层] —— 限流 + 灰度 + token校验
 4 5[预处理服务]
 6    ├─ 检查用户参与资格 + 幂等
 7    ├─ Redis 判断库存是否可扣(可选预扣)
 8    └─ 入队 Kafka(order.create.topic)
 910返回:排队中 / 接收成功
11
12[消息队列消费者]
1314Worker Pool 批处理消息
1516数据库写入订单、扣库存、发券等操作
1718写入结果状态缓存:Redis `ms:result:<uid>:<sku>`
19
20[用户查询接口 / WebSocket 通知]:展示是否成功

设计关键点拆解

入队前防刷 + 幂等

  • 网关限流、IP滑动窗口、验证码校验
  • Redis + SETNX 防重复提交
  • 每次请求带 token,保证唯一性

请求排队削峰

  • 使用 Kafka/RabbitMQ 分区/顺序队列做入队
  • 写入失败时快速返回错误码,不处理

Worker 消费处理

  • 支持批量拉取/写库(节省 DB 压力)
  • 加强幂等处理(如事务外幂等记录表)
  • 落库失败:写入失败队列,后续重试

用户态异步反馈

  • Redis 存储处理状态(成功/失败/处理中)
  • 客户端定时轮询 or WebSocket 收到通知

可回放/补偿机制

  • Kafka 持久化消费位点,支持失败回放
  • 支持人工重试机制,后台运维介入手段

流量打散

  • 抢购开始前 token 预热,接口灰度开放
  • 秒杀时间多批次分片,用户随机分组进入

降级策略

  • Redis 查询失败 → 返回“排队中”,不崩溃
  • Kafka 写入失败 → 限制流量 / fallback 至 Redis List

技术组件选型建议

环节技术建议
消息队列Kafka(高吞吐) / Redis Stream(轻量)
数据层MySQL + 分库分表 / TiDB / OceanBase 等弹性架构
缓存Redis 集群,设置合理 TTL + retry 控制
服务语言Go / Java(高并发处理 + MQ 消费框架成熟)

核心关键设计节点(面试重点)

关键点同步型异步型(大促)
用户是否即时知道结果✅ 是❌ 否(排队中)
请求入口限流✅ 必须✅ 必须
是否用消息队列❌ 否✅ 是
Redis 作用原子库存扣减 + 幂等判断同左 + 入队前判断
幂等控制Redis SETNX 或 Lua同左 + MQ 消费幂等处理
如何落单同步写库 或 异步任务后台消费队列落库
如何通知用户HTTP 同步返回WebSocket / 轮询接口
最大并发能力中高(~10万QPS)极高(~百万QPS+)
错误处理失败即返回延迟补偿 / 重试队列 / offset 回放

总结(面试+实战双向准备)

  • 小型活动/中小企业:同步秒杀架构 + Redis 即可,实现成本低,用户体验好。
  • 大促活动/高并发平台:必须使用 异步削峰 + 队列 + 状态通知 + 幂等机制,配合限流/降级保护。
  • 两种模式可以结合:同步层只做拦截 & 入队,关键写操作交给异步后台处理。

幂等 Token 分发策略(可选增强机制)

用法概述

  • 每次打开秒杀页面时后端生成 UUID token,写入 Redis 并返回前端
  • 请求必须带 token 提交,后台验证 + 原子消耗 token
  • Redis 存储:SET token value EX 60 NX

优势

  • 统一用户行为身份标识(防止伪造/重放)
  • 可前端结合按钮禁用、刷新重载校验、token 过期提示
  • 可观察、可追踪、可设置 TTL

服务端处理逻辑

1val := redis.Get("ms:token:<sku_id>:<uid>")
2if val == "" {
3    return "非法请求(未申请或已消费)"
4}
5if val != clientProvidedToken {
6    return "伪造请求"
7}
8redis.Del("ms:token:<sku_id>:<uid>") // 消费 token

与 Redis 锁的对比权衡

对比项Redis 锁(SETNX sku:uidToken 机制
是否需要生成接口❌ 否✅ 需要 /get_token
是否拦截非法请求⚠️ 不行(请求已打进)✅ 可提前拒绝
是否支持按钮禁用/刷新拦截❌ 否✅ 是(通过 token 控制)
实现复杂度✅ 简单⚠️ 略高(发、校验、消费)
防脚本攻击能力⚠️ 弱✅ 强(可配合验证码)
适用场景单阶段抢购请求多步骤交互、确认页、灰度控制等

综合建议

  • 对于简洁秒杀系统,Redis 锁已足够防重、限量 ✅
  • 若系统需支持更复杂交互(支付确认、预下单、抽奖流程等)或需要精细化防刷管控,则可引入 token ✅
  • 两者可结合使用,token 控制访问门槛,Redis 锁处理最终幂等逻辑

异常补偿机制

  • 消息消费失败(如 DB 超时)→ 推送到重试队列(或 DLQ 死信队列)
  • 数据落库成功但缓存写失败 → 依赖定时任务补偿修复状态
  • 订单状态一致性问题 → 定期任务核对 Redis + DB 数据是否一致
  • 运维侧提供手动补单、回查工具(如基于 Kafka offset replay)

二、Hadoop vs 阿里云 ODPS 功能对照表

类别Hadoop/开源生态组件阿里云/ODPS 平台对应组件说明 / 差异重点
分布式存储HDFS(Hadoop Distributed File System)MaxCompute 表底层存储 / OSSODPS 底层采用专有高性能列存引擎,用户不直接操作路径
离线计算MapReduce / Hive SQLMaxCompute SQL / PyODPSODPS 用优化后的执行引擎替代 MR,性能远超 Hive
资源调度YARN(资源分配)阿里云 MaxCompute Scheduler用户无需管理资源,ODPS 是 Serverless 模型
元数据管理Hive MetastoreODPS Project/Table/Partition 管理ODPS 提供统一的项目级元数据权限控制
流式计算Flink / Spark StreamingDataWorks 实时计算/Flink on EMRFlink 可以部署在阿里云上,与 ODPS 联动
脚本调度Azkaban / Oozie / AirflowDataWorks(调度编排平台)图形化任务依赖管理,支持 SQL/Python 等混合任务
日志采集Flume / Kafka / LogstashSLS(日志服务)/ TT(日志总线)更适合业务埋点、日志实时采集到数据平台
NoSQL 存储HBase(KV 数据库)HBase on 云原生 EMR / Tablestore需要低延迟读写时选用;ODPS 不适合频繁写
可视化 BISuperset / Tableau / RedashFBI(QuickBI / 自助取数)BI 展示和运营自助查询分析平台
你常做的事标准术语(大数据团队)
埋点、日志打 TT / Kafka数据采集(Data Ingestion)
Flink 实时清洗打点数据实时 ETL / 流式 ETL(Streaming ETL)
MySQL 数据 T+1 导入 ODPS离线采集 / 离线同步(Batch Ingestion)
SQL 聚合、统计、Join 运算离线计算 / 指标建模(Batch Computation / Metric Modeling)
pyodps 调 SQL、脚本定时跑任务调度(Scheduler,例如调度系统为 Zeus、Airflow 等)
Flink 计算写 TT 表或落回 ODPS实时入库(Real-time Sink)
ODPS 表结果供 FBI 平台查看自助 BI / 可视化查询平台(BI平台,如 FBI、QuickBI)
FBI 展示指标、用户留存、趋势等数据服务(Data Service)