秒杀、抽奖系统设计
本文最后更新于:23 天前
秒杀系统
问题:
1. 防止用户重复抽奖
方案:
在负载均衡设备中做一些配置,判断如果同一个用户在1分钟之内多次发送请求来进行抽奖,就认为是恶意重复抽奖,或者是脚本刷奖,这种流量一律认为是无效流量,在负载均衡设备层次就给屏蔽掉
2.全部开奖后暴力拦截流量
场景:
假设有50万请求涌入,只需5万请求,后续的几十万流量无效,不需要让它们进入后台系统执行业务逻辑
方案:
必须让抽奖服务跟负载均衡之间有一个状态共享的机制
抽奖服务一旦全部开奖完毕,直接更新一个共享状态。然后负载均衡感知到了之后,后续请求全部拦截掉返回一个抽奖结束的标识
基于Redis
来实现这种共享抽奖状态
3、发放礼品环节进行限流削峰
问题:
假设抽奖服务在2万请求中有1万请求抽中了奖品,那么势必会造成抽奖服务对礼品服务调用1万次。
方案:
- 抽奖之后可以让礼品服务在后台慢慢的把中奖的礼品给发放出去,不需要立马对1万个请求完成礼品的发放逻辑
- 可以在抽奖服务和礼品服务之间,引入消息中间件,进行限流削峰
- 抽奖服务把中奖信息发送到MQ,然后礼品服务,慢慢的从MQ中消费中奖消息
优化方向
秒杀业务,可以使用典型的服务化分层架构:
- 端(浏览器/APP),最上层,面向用户
- 站点层,访问后端数据,拼装html/json返回
- 服务层,屏蔽底层数据细节,提供数据访问
- 数据层,DB存储库存,当然也有缓存
1、端上的请求拦截(浏览器/APP)
- JS层面
可以限制用户在x秒之内只能提交一次请求,从而降低系统负载。
频繁提交,可以友好提示“频率过快”。
- APP层面
可以做类似的事情,虽然用户疯狂的在摇微信抢红包,但其实x秒才向后端发起一次请求。
将请求尽量拦截在系统上游”,浏览器/APP层就能拦截80%+的请求。
端上的拦截只能挡住普通用户(99%的用户是普通用户),程序员firebug一抓包,写个for循环直接调用后端http接口,js拦截根本不起作用
2、站点层的请求拦截
- 用
uid
做唯一标识。 - 在站点层,对同一个uid的请求进行计数和限速,例如:一个uid,5秒只准透过1个请求,这样能拦住99%的for循环请求。
- 缓存,页面缓存,5秒内到达站点层的其他请求,均返回上次返回的页面。
解决方向:
- 站点层水平扩展,通过加机器扩容,一台抗5000,200台搞定;
- 服务降级,抛弃请求,例如抛弃50%;
同一个uid计数与限速,如果担心访问redis带宽成为瓶颈,可以这么优化:
- 计数直接放在内存,这样就省去了网络请求;
- 在nginx层做7层均衡,让一个uid的请求落到同一个机器上;
3、服务层的请求拦截
- 削峰限速
- 假如数据库每秒只能抗500个写请求,就只透传500个(请求队列)
- 读请求优化(不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题。缓存做水平扩展,很容易线性扩容)
4、数据库层
经过前三层的优化:
- 浏览器拦截了80%请求
- 站点层拦截了99%请求,并做了页面缓存
- 服务层根据业务库存,以及数据库抗压能力,做了写请求队列与数据缓存
db无需分库,数据库做一个高可用就行
实现【Redis】
基于Redis实现抽奖业务逻辑
1.初始化:
秒杀商品,将商品以list数据类型存入redis(每个数量为一个元素)
2.购买:
1)购买用户入队列,如果用户队列长度超过指定的排队长度,则返回排队数过多
2)如果用户队列长度小于指定的排队长度,然后生成订单,减去库存。下单完成
示例:
1 |
|
压测
1 |
|
1 |
|
总结
核心思路都是对于这种瞬时超高流量的系统,尽可能在负载均衡层就把99%的无效流量拦截掉(尽量将请求拦截在系统上游)
然后在1%的流量进入核心业务服务后,此时每秒并发还是可能会上万,那么可以基于Redis实现核心业务逻辑 ,抗住上万并发(读多写少用缓存)
最后对于类似秒杀商品发货、抽奖商品发货、红包资金转账之类的非常耗时的操作,完全可以基于MQ消息队列队列来限流削峰,后台有一个服务慢慢执行即可
补充
抽奖的话,可以先在nginx层,或者是网关层,随机拒绝大部分请求,能进入到业务逻辑里面的,都是中了奖的,
业务折中方案
下单流程和支付流程异步
下单成功后,系统占住库存,45分钟之内支付即可
不同地域分时抢购
一旦点击,不管系统是否返回,按钮立刻置灰
降低缓存淘汰率
显示库存会淘汰N次,显示有无只会淘汰1次。更多的,用户关注是否有票,而不是票有几张