目录
0.基础概念
1.新鲜事–消息流的实现方式
2.用户系统(1)–缓存
2.用户系统(2)–好友关系
3.网站系统、API–翻页
3.短网址系统
4.数据库拆分和一致性哈希
5.分布式文件系统
6.分布式数据存储系统
7.聊天系统
8.打车系统
9.分布式计算和爬虫系统
9.推荐系统
问题:秒杀系统
1.明确设计需求
商城某时刻限量出售商品,每人限购一台,先到先得,售完为止。
假设页面QPS为1k左右,秒杀时QPS可能会增加100倍以上,每秒10万次访问。
秒杀系统的几个主要问题:
- 瞬时大流量高并发
- 有限库存,不能超卖
- 脚本恶意请求
- 严格限制每个用户购买数量
2.设计服务
主要服务有:
- 秒杀服务(Seckill Service)
- 商品/库存服务(Commodity/Stock Service)
- 订单服务(Order Service)
- 支付服务(Payment Service)
3.存储结构设计
整个系统采取微服务的结构,各个服务的基础数据库表比较简单,通常使用关系型数据库,只需要包含业务逻辑所需的字段,根据查询的需求建立对应的索引即可,不再赘述。
扣减库存
最基础的做法是先读取判断库存,然后扣减库存
1. 查询余量
SELECT stock FROM stock_info WHERE commodity_id = 123 AND seckill_id = 456;
2. 扣减库存
UPDATE stock_info SET stock = stock - 1 WHERE commodity_id = 123 AND seckill_id = 456;
这种做法在并发时会导致商品超卖,优化方法是在查询过程中加上事务,或者使用UPDATE
语句带的行级锁处理。
// 在查询过程中加上事务
SELECT stock FROM stock_info WHERE commodity_id = 123 AND seckill_id = 456 FOR UPDATE;
// 行级锁
UPDATE stock_info SET stock = stock - 1 WHERE commodity_id = 123 AND seckill_id = 456 AND stock > 0;
这样可以解决超卖问题,但是大量请求都访问数据库,可能导致数据库崩溃,而且对于秒杀系统来说,大部分的请求都是无效的,不需要下沉到数据库。
秒杀过程中的查询/扣减库存操作,可以通过redis来处理,单台redis所能承载的QPS要远高于常用的关系型数据库,将库存信息加载到redis中,就可以直接通过redis进行判断和扣减库存。
库存预热(warm up)
商户端提前查询数据库中的秒杀活动信息,将商品库存预热到redis中,redis的数据结构可以选择哈希表。
// 设置指定key
SET KEY VALUE
SET seckill:456:commodity:123:stock 100
// 获取key存储的值
GET KEY
GET seckill:456:commodity:123:stock
// key存储的数字值减1
DECR KEY
DECR seckill:456:commodity:123:stock
加入redis之后,扣减库存流程如下:
这样的话大部分请求都被redis挡住了,真正下沉到关系型数据库中的请求理论上就是有效请求,可以创建订单。
问题1:由于检查和修改redis库存还是两步操作,并发时仍然会导致超卖。
解决方案:redis放行的请求,在关系型数据库这边同样进行一次检查,这样就可以有效解决超卖的问题
问题2:如果并发量超高,redis放行的请求数量超出关系型数据库能承受的极限,redis就失去了作用
解决方案:redis在2.6版本以前是不支持多个指令执行仍然保持原子性的,2.6版本以后可以通过lua脚本实现原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。
问题3:如果秒杀数量很多,有10万个,这样到关系型数据库的请求数量仍然很大。
解决方案:使用消息队列,进行削峰(Peak Clipping)操作。在通过redis扣减库存以后,到关系型数据库的请求慢下来。对于消息队列可能出现消息投递失败的情况,可以在redis中设置比秒杀数量多一定比例的库存,在关系型数据库端进行准确的数量把控。
如何限购
可以通过订单表中查询是否有该用户的订单,如果有则本次购买请求失败,如果没有就创建一个订单。
同样的,可以使用redis进行数据校验,这里可以使用redis中集合的数据结构。
// 将元素加入集合,已经存在就忽略
SADD KEY VALUE1...VALUEN
SADD seckill:456:commodity:123:user abc
// 判断集合是否包含key
SISMEMBER KEY VALUE
SISMEMBER seckill:456:commodity:123:user abc
数据一致性
从付款到支付、订单、扣减库存,需要跨多个服务,操作不同数据库中的数据,也需要保持强一致性。
这个时候就需要分布式事务,保证数据操作,要么同时成功,要么同时失败。
目前有几种可行的解决方案,可以自行了解。
4.拓展系统
如何防止商品页面被刷爆
可以从前端缓解一部分后端的压力,例如:
- 限制购买按钮,点击一次后设置成一段时间无法点击
- 将部分请求跳转到繁忙页
秒杀服务器挂掉怎么办
服务雪崩
多个微服务调用的时候,假设服务A调用B和C,B和C又分别调用其他微服务,这就是所谓的扇出(Fan-out),如果扇出的链路上某个微服务的调用不可用或者响应时间过长,对服务A的调用就会占用越来越多的系统资源,进而引起系统雪崩。
服务熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务不可用或者响应时间太长时,熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务响应正常后恢复调用链路。
参考方案:
alibaba Sentinel
Netflix Hystrix
如何防止恶意请求或者爬虫
- 验证码机制(verification code)
- 限流机制(rate limit),对ip和用户id进行判断,对于一段时间内多次出现的情况进行限制或者禁用/加入黑名单