「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」
概述
随着互联网电商的兴起,各种活动层出不穷。秒杀活动作为一种经典活动具有瞬时并发大的特点,同时秒杀设计也是面试常考题之一,本文以单机为示例设计开发秒杀系统。
源码地址: gitee.com/tech-famer/…
效果展示
系统分析
- 秒杀页面静态化
- 倒计时时间服务器中获取
- 秒杀活动开始前隐藏秒杀链接
- 秒杀限流
- 秒杀商品redis缓存
- 防止超卖
系统设计
表结构设计
1 | less复制代码 |
页面静态化
大型电商平台的页面一般在做活动的时候会租用CDN服务,将静态资源放到CDN服务来避免每次用户刷新页面请求服务器带来压力。静态页面会利用浏览器缓存静态资源,在用户持续刷新页面时,浏览器直接读取缓存而非直接请求链接获取。
单机具体做法如下:
1. 项目配置文件中添加静态资源相关配置
1 | javascript复制代码spring: |
2. 将静态资源放入静态资源目录下
倒计时设计
活动页面倒计时一般是活动开始时间-当前时间计算得出,前端页面通过setTimeout函数动态定时更新倒计时。此方法中当前时间取得是客户端时间,聪明的用户通过修改系统时间绕过倒计时直接参加活动,为避免发生这种情况当前时间也从服务器获取即可。具体做法如下:
在服务端直接计算好活动开始时间-当前时间值,前端只需要将服务器计算好的差值通过setTimeout函数倒数至0即可开始活动。
1 | less复制代码@PostMapping("/goodsDetail") |
活动开始前隐藏秒杀链接
为避免黄牛党通过机器刷秒杀接口,在活动开始前不暴露秒杀接口,所有参与秒杀的用户只能在活动开始后通过页面秒杀按钮抢购。为此做两点设计。
1. 增加获取秒杀链接的接口
所有用户只能通过此接口获取秒杀链接,此接口只在活动开始才返回秒杀链接,秒杀链接包含加密信息
1 | less复制代码@PostMapping("/goodsOrderUrl") |
2. 对前端js进行加密混淆
秒杀限流设计
1. 前端秒杀按钮在秒杀开始前置灰,且不可连续点击
其中变量canBuy
用来标识按钮是否可点击,默认为false,活动倒计时结束置为true,用户点击后置为false
1 | javascript复制代码//用户点击秒杀按钮,获取秒杀链接 |
2. 服务器端限流
通常服务器端的限流月前置越好,若在系统层限流测服务器系统可用连接在可承受范围就限流了;若限流在应用层,只会对应用服务高并发坐限流,系统还是会有大量的连接,当大量连接占满系统后,则系统不可用,此时服务也不可用。所以在nginx层等更高层都要做好限流配置。
本机演示只在应用服务成做限流,基于guava的RateLimiter利用spring的aop原理做封装。
1. 定义自定义注解 @FarmerLimiter
其中value默认值20,表示并限流数。
1 | less复制代码@Documented |
2. 编写aop处理逻辑
达到限流提示“活动太火爆,稍后再试”
1 | arduino复制代码@Component |
3. 秒杀接口添加限流功能
1 | less复制代码@PostMapping("/{md5}/order") |
秒杀商品redis缓存
为秒杀系统设计三种缓存:
1. 秒杀活动列表缓存,使用redis的list数据结构存储
通过定时任务,定时将有效的活动添加到redis缓存中,失效的删除。
1 | ini复制代码@Scheduled(cron = "0/5 * * * * *") |
2. 秒杀活动详情缓存,使用redis的hash数据结构存储
在后台添加秒杀活动时,将秒杀商品信息添加redis缓存,其中hash key为字段名称,方便查询秒杀商品详情及对商品库存hincrby
原子减扣
1 | less复制代码@PostMapping("add") |
3. 秒杀活动用户已购商品数量缓存,使用redis的string数据结构
设置过期时间到活动结束+一个小时以内随机,避免大量redis的key过期引起redis卡顿。
1 | scss复制代码redisTemplate.opsForValue().set(limitKey,1); |
防止超卖
利用mysql数据库事务隔离性,及乐观锁减扣库存来防治超卖。
1 | bash复制代码<update id="decrement"> |
Jmeter并发测试
本文转载自: 掘金