Overview
之前看到过关于积分系统的文章1, 而这周又看到一个类似的文章3. 所以就总结一下关于这4个系统设计面试题,
- 设计一个电商平台积分兑换系统
- 设计一个秒杀系统
- 设计一个微信红包系统
- 设计一个支撑百万用户的IM消息系统
通过对上面的问题的理解, 举一反三, 那么后续的system design应该就比较有把握. 因为上述4个问题大概围绕着缓存, 事务, 中间件, 具体业务场景结合.
设计一个电商平台积分兑换系统
数据结构
基石是数据的转移, 数据是使用表来保存, 使用以表
为设计单元,
-- 记录了谁拥有多少积分
积分表:
id (自增id主键)
user_id (用户id)
credit (积分)
-- 记录了谁用了多少积分换了什么物品
积分兑换表:
id (自增id主键)
user_id (积分表的用户id)
exchanged_credit (用于兑换的积分)
product_id (兑换的商品id)
-- 记录了谁用了多少积分换了什么物品
发货申请表:
id (自增id主键)
credit_exchange_id (积分兑换表的id)
type (发货类型,1:购买,2:积分兑换)
product_id (要发货的商品id)
product_num (要发货的商品数量)
express_id (物流单号)
技术实现 上面是基本的业务需求和流程梳理, 有了这个业务框架之后, 接下来就是分析其技术实现.
比如说用户要开始一轮新的兑换, 那么就是三个表同时有动作.
积分服务 事务 {
-> 扣减积分
-> 新增积分兑换记录
-> 新增物品发货(这一步可以不同时, 比如说稍晚再发货)
}
所以上述就是引入了MQ
来解耦三个动作,
在MQ中一般有三种语义保证消息的传递.
- 如果是at least once, 那么就需要考虑重复发货问题
- 针对漏发问题, 可以通过引入retry来解决
- 如果是at most once, 那么就需要考虑漏发货问题
- 针对重发问题, 可以通过引入idempotent来解决
- exactly once2就是2者综合
- 可以用
credit_exchange_id
建唯一索引, 保证其有且仅有一条记录(幂等)
- 可以用
from 1
设计一个秒杀系统3,4
- 超卖问题
- 高并发
- 服务单一化, 挂这不挂那
- 单机redis的max-qps约40k, 升级为哨兵; 预加载
- nginx负载均衡, session/cookie拦截
- 资源静态化
- 限流(前端与后端)
- 异步下单
- 服务降级
- 接口防刷
- 黄牛
- 秒杀url5
- 动态生成随机下单页面url, 在下单页面url中追加服务端生成的随机数作为参数
- 这个参数只能在秒杀活动开始时才能获取。这样即使是网站开发者也无法在秒杀前得到正确的下单url地址
- 数据库设计
- 单独表格, 隔离性
- 一张是秒杀订单表,一张是秒杀货品表
from 3
设计一个微信红包系统
- 红包类型
- 个人
- 群发
- 表结构
-- 记录了谁用了多少积分换了什么物品 红包计数表: id 红包总金额 剩余红包金额 红包总个数 剩余红包个数
- 业务逻辑
- 新建/包红包, insert one record
- 抢红包, 访问cache>0
- 拆红包
sql.excute("update '红包计算表' set remainCount=${total-getAmount}, remainCount=${remainCount-1} where remainCount=${remainCount} and id=${id}")
, remainCount=${remainCount}这里可以避免超卖?
set化, 红包id尾部consistent hashing, 各个set之间相互独立,互相解耦。并且同一个红包id的所有请求,垂直流入到同一个set内处理,高度内聚, from 7,8
设计一个支撑百万用户的IM消息系统9
类似twitter feed, 但是时效性要求更高
群聊类似kafka的consumergroup(一个user一个cgid)
每个会话对应一个Timeline进行消息存储(这里timeline类似list)
core,
- 消息同步, send -> 多端receive, i.e., 多端同步
- 消息存储, 持久化, i.e., 消息漫游
- 消息检索, search, i.e., 在线检索
表结构
-- receiver
接受者表:
receiver_id
sender_ids
endpoint_type(pc, mob, web)
offset
time
primary key(receiver_id, sender_id, endpoint_type) // 最后还是需要全端同步
-- sender
发送者表:
sender_id
receiver_ids
endpoint_type(pc, mob, web)
msg_list
offset
primary key(sender_id, receiver_ids, endpoint_type) // sender在不同endpoint登录之后, 根据offset来拉取最新msg_list
读放大即pull, 写放大即push问题,
类似twitter celebrities, 大名会有million的followers, 如果大名(类似trump)发一条消息, 而这条消息在写模式下会被push到所有followers的feed list, 在很多僵尸粉的情况下, 会极为奢侈.
在这样的情况下, 让active 的follower主动去pull这条msg更为合理. 但是在读模式下, 如果该user follow了很多人, 那么user一上线就要去pull million的user.
哪个模式取决该用户的关注数与被关注数.
- 如果一个用户被很多人关注了, 即其被关注数很多, 那么用pull
- 如果一个用户关注了很多人, 那么用push, 其关注者每发一条msg都放到其待读list
comparison, from 9
from 9