秒杀系统设计

秒杀系统设计

秒杀其实主要解决两个问题,一个是并发读,一个是并发写。并发读的核心优化理念是尽量减少用户到服务端来“读”数据,或者让他们读更少的数据;并发写的处理原则也一样,它要求我们在数据库层面独立出来一个库,做特殊的处理。另外,我们还要针对秒杀系统做一些保护,针对意料之外的情况设计兜底方案,以防止最坏的情况发生。

秒杀系统架构原则

  • 数据尽量少: 可以简化秒杀页面的大小,去掉不必要的页面装修效果,等等。
  • 请求数尽量少: 减少请求数最常用的一个实践就是合并 CSS 和 JavaScript 文件,把多个 JavaScript 文件合并成一个文件,在 URL 中用逗号隔开。
  • 路径要尽量短: 缩短访问路径有一种办法,就是多个相互强依赖的应用合并部署在一起,把远程过程调用(RPC)变成 JVM 内部之间的方法调用。
  • 依赖要尽量少: 0 级系统要尽量减少对 1 级系统的强依赖,防止重要的系统被不重要的系统拖垮。例如支付系统是 0 级系统,而优惠券是 1 级系统的话,在极端情况下可以把优惠券给降级,防止支付系统被优惠券这个 1 级系统给拖垮。
  • 不要有单点: 应用无状态化。

动静分离

热点数据

流量削峰

排队

答题

系统优化

配置线程数

很多多线程的场景都有一个默认配置,即 “线程数 = 2 * CPU 核数 + 1” 。除去这个配置,还有一个根据最佳实践得出来的公式:线程数 = [(线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间] × CPU 数量

最好的办法是通过性能测试来发现最佳的线程数。

发现 CPU 瓶颈

JProfiler 和 Yourkit 这两个工具。

减库存

兜底方案

降级

当秒杀流量达到 5w/s 时,把成交记录的获取从展示 20 条降级到只展示 5 条。“从 20 改到 5”这个操作由一个开关来实现,也就是设置一个能够从开关系统动态获取的系统参数。

限流

限流既可以是在客户端限流,也可以是在服务端限流。此外,限流的实现方式既要支持 URL 以及方法级别的限流,也要支持基于 QPS 和线程的限流。

在限流的实现手段上来讲,基于 QPS 和线程数的限流应用最多,最大 QPS 很容易通过压测提前获取,例如我们的系统最高支持 1w QPS 时,可以设置 8000 来进行限流保护。线程数限流在客户端比较有效,例如在远程调用时我们设置连接池的线程数,超出这个并发线程请求,就将线程进行排队或者直接超时丢弃。

拒绝服务

当系统负载达到一定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2 * CPU 核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的系统保护方式。

在最前端的 Nginx 上设置过载保护,当机器负载达到某个值时直接拒绝 HTTP 请求并返回 503 错误码,在 Java 层同样也可以设计过载保护。