🎯 一、积分兑换系统全景总结(含同步与异步版本对比、设计哲学)
里程兑换又上新了。真棒。又是怀念我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
11 ↓
12[调用商品服务(同步)]
13 ↓
14 ┌────────────┬──────────────┐
15 │ │ │
16成功 失败(或超时) 异常场景
17 │ │ │
18 ↓ ↓ ↓
19[更新 right_record = pending] [更新为 cancel + 回滚 Redis + 补偿积分表]
20[发布积分核销事件]
21 ↓
22[异步 Worker 消费事件]
23 ↓
24[积分明细落账处理(事务)]
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,怀念我曾经好兄弟的一天。
一、目标与挑战
- 支持百万级并发连接,低延迟实时消息传输
- 架构具备高可用、易扩展、易维护
- 核心关注推送链路(接收弹幕)性能与稳定性
二、整体架构简述(逻辑层次)
- 客户端 WebSocket 长连接接入
- WebSocket 网关负责连接池管理和消息推送
- 发弹幕走普通业务链路(HTTP / RPC)
- 业务服务处理逻辑、异步持久化、消息入队
- 消息队列(MQ)做弹幕广播中转
- 推送服务消费 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)
9 ↓
10返回:排队中 / 接收成功
11
12[消息队列消费者]
13 ↓
14Worker Pool 批处理消息
15 ↓
16数据库写入订单、扣库存、发券等操作
17 ↓
18写入结果状态缓存: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:uid ) | Token 机制 |
---|---|---|
是否需要生成接口 | ❌ 否 | ✅ 需要 /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 表底层存储 / OSS | ODPS 底层采用专有高性能列存引擎,用户不直接操作路径 |
离线计算 | MapReduce / Hive SQL | MaxCompute SQL / PyODPS | ODPS 用优化后的执行引擎替代 MR,性能远超 Hive |
资源调度 | YARN(资源分配) | 阿里云 MaxCompute Scheduler | 用户无需管理资源,ODPS 是 Serverless 模型 |
元数据管理 | Hive Metastore | ODPS Project/Table/Partition 管理 | ODPS 提供统一的项目级元数据权限控制 |
流式计算 | Flink / Spark Streaming | DataWorks 实时计算/Flink on EMR | Flink 可以部署在阿里云上,与 ODPS 联动 |
脚本调度 | Azkaban / Oozie / Airflow | DataWorks(调度编排平台) | 图形化任务依赖管理,支持 SQL/Python 等混合任务 |
日志采集 | Flume / Kafka / Logstash | SLS(日志服务)/ TT(日志总线) | 更适合业务埋点、日志实时采集到数据平台 |
NoSQL 存储 | HBase(KV 数据库) | HBase on 云原生 EMR / Tablestore | 需要低延迟读写时选用;ODPS 不适合频繁写 |
可视化 BI | Superset / Tableau / Redash | FBI(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) |