一、系统设计

1.1 秒杀系统概述

特点:

1)时间短、瞬间访问量大
2)读多写少的场景。(库存固定则写操作固定,但访问量肯定无限大)

难点:
1)库存只有一份,但大量用户在集中时间对该数据进行读写。
2)秒杀系统之所以挂,是因为请求没有经过上游的过滤与拦截,直接压倒在下游的数据层。

常见的 Java Web 架构:

java 秒杀客户端 java秒杀功能实现_java 秒杀客户端

1.2 优化思路

核心思想:尽量将请求拦截在系统上游;读多写少的场景使用缓存
介绍一些常见的操作:

1)浏览器端的拦截

  • 点击 “购买” 后,按钮置灰,禁止用户重复提交请求。
  • 通过 JS 代码,限制用户在 x 秒之内只能提交一次请求。

2)站点层的拦截

  • 秒杀时间开始前,不暴露秒杀链接并对URL加密。这样部分用户无法使用程序代替人工进行秒杀。
  • 页面缓存。x 秒内到达的请求,直接返回页面缓存。

3)服务层的拦截

  • 将秒杀商品的数据缓存到 Redis 等内存数据库。这样读请求就不需要达到最下层的数据库。
  • 将库存数据缓存到 Redis 中,加上 Redis 事务和 lua 脚本进行查询库存并减库存的操作,放到 Redis 完成。
  • 将写请求放入到写请求队列中,每次让有限的写请求达到数据层,当库存不足就直接让队列里的写请求直接返回 “已售完”

4)其他

  • 启动多个应用实例,并使用 Nginx 进行负载均衡。
  • 分库分表,增加并发度。
  • CDN 缓存

二、系统实现


2.1 业务流程

java 秒杀客户端 java秒杀功能实现_redis_02

2.2 系统架构

当然这里并没有完全实现

java 秒杀客户端 java秒杀功能实现_缓存_03


PS:

  • 图中有四处体现了对秒杀系统的优化,分别是Nginx的负载均衡、CDN缓存、Redis缓存以及分库分表
  • 利用 Nginx 负载均衡功能,将流量均匀分布到各个后端服务器。
  • 分库分表也是提高并发度。
  • Redis 缓存的主要工作:原子变量(Atomicinteger)记录库存,先将减库存的操作记录在 Redis 中,而后异步记录到数据库。
  • CDN 缓存的主要工作:
  • 缓存静态文件,如css,js
  • 将一些页面静态化后,缓存到CDN中。如用户会在秒杀开始前后,疯狂刷新商品页,故此很必要将商品页静态化,然后缓存到CDN中

2.3 优化分析

瓶颈分析
从业务角度分析,高并发主要发生在秒杀商品详情页。进入秒杀商品详情页以及请求涉及到的操作有:

  1. 获取网页资源,包括静态资源和动态资源
  2. 获取服务器时间的请求
  3. 获取秒杀地址的请求
  4. 执行秒杀的请求

1)加载网页资源

  • 优化思路:缓存

2)获取服务器时间的请求

  • 分析:该请求的处理是在内存中 new 一个 Date 对象,访问一次内存的大约是 10ns,得出该请求的 QPS = 1亿/s,所以该请求不是瓶颈所在。

3)获取秒杀地址的请求

  • 分析:该请求的处理主要任务是在内存中生成md5。

4)执行秒杀的请求

  • 过程:insert增加记录、update减库存。两个步骤组成事务。
  • 分析:
  • 如果多个事务对同一商品执行秒杀,由于事务在提交或回滚前都会持有行锁,这就导致多个事务最终变成串行执行。
  • 服务器端和数据库端的分离,使得两个步骤的通信都存在网络延迟,故此增加行锁的持有时间。
  • 这两个操作结果都存储在 JVM 内存中,容易引起GC,也会增加行锁的持有时间。
  • 同时目前回滚的控制权在服务器端中,也会增加行锁的持有时间。
  • 另外,一条update其实QPS有4w/s,已经足够了。所以说并不是数据库操作不够优,而是由于涉及到网络延迟、事务、锁等缘故造成性能瓶颈。【参考并没实践,因为还没学压力测试】

结论:

  • 瓶颈在于执行秒杀的请求。核心优化是减少行锁的持有时间
  • 还可以优化的点:
  • 对资源进行缓存,如 CDN、Redis 缓存
  • 前端控制按钮只能点击一次

优化方案

1)浏览器端采用的拦截
a. 不提前暴露秒杀地址,以及增加 md5 不让用户提前猜到秒杀地址。避免使用机器秒杀
b. 商品详情页静态化。

2)Web层采用的拦截
a. 页面静态化
b. 将静态文件存放到 CDN。【本项目并没有实现】

3)Service层的优化
a. Redis 缓存商品信息,并设置超时时间来维护数据一致性
b. Redis 缓存商品库存,并利用 Redis 事务和 lua 脚本完成写请求。【本项目并没有实现】

4)数据库层的优化
a. 使用 MySQL 的存储过程,减少服务器端和数据库端通信次数,从而降低网络延迟和GC时间。

2.4 URL设计

URL

解释

/list

秒杀商品列表页

/{seckillId}/detail

获取商品的秒杀地址,主要工作内容是生成md5。

/{seckillId}/{md5}/execution

执行秒杀

/time/now

获取服务器时间

PS:

  • 只有当处于秒杀时间内时,才会暴露秒杀地址。避免用户提前知道地址后,使用程序进行秒杀,对其他用户不公平,也防止恶意攻击
  • md5的生成是不可逆的,所以能有效保护执行秒杀的地址,避免提前下单。

2.5 效果展示

java 秒杀客户端 java秒杀功能实现_redis_04

秒杀商品列表页

java 秒杀客户端 java秒杀功能实现_java_05

秒杀商品详情页-三种状态