E. 业务场景 — 秒杀系统
概要
- 核心问题
- 并发读
- 并发写
- 秒杀的整体架构可以概括为“稳、准、快”几个关键字
- 所谓“稳”,就是整个系统架构要满足高可用
- 就是“准”,就是秒杀 10 台 iPhone,那就只能成交10 台,多一台少一台都不行。
- 最后再看“快”,“快”其实很好理解,它就是说系统的性能要足够高,否则你怎么支撑这么大的流量呢?
架构设计原则
- 数据要尽量少
- 首先是指用户请求的数据能少就少
- 其次,“数据要尽量少”还要求系统依赖的数据能少就少
- 请求数要尽量少
- 路径要尽量短
- 依赖要尽量少
- 系统降级减少依赖,我们可以给系统进行分级,比如 0 级系统、1 级系统、2 级系统、3 级系统
- 不要有单点
样例概述
- 第一阶段
- 把秒杀系统独立出来单独打造一个系统,这样可以有针对性地做优化
- 在系统部署上也独立做一个机器集群,这样秒杀的大流量就不会影响到正常的商品购买集群的机器负载
- 将热点数据(如库存数据)单独放到一个缓存系统中,以提高“读性能”
- 增加秒杀答题,防止有秒杀器抢单
- 第二阶段
- 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面
- 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统调用,而且能够避免压垮公共缓存集群
- 增加系统限流保护,防止最坏情况发生
高性能/高可用
- 关注点
- 一点是提高单次请求的效率
- 一点是减少没必要的请求
- 要点
- 动静分离也就是所谓“动态”还是“静态”,并不是说数据本身是否动静,而是数据中是否含有和访问者相关的个性化数据。
- 静态数据的处理
- 你应该把静态数据缓存到离用户最近的地方
- 浏览器
- CDN
- 服务器的Cache
- 静态化改造就是要直接缓存 HTTP 连接Web 代理服务器根据请求 URL,直接取出对应的 HTTP响应头和响应体然后直接返回,这个响应过程简单得连 HTTP 协议都不用重新组装,甚至连 HTTP 请求头也不需要解析。
- 让谁来缓存静态数据也很重要
- 你应该把静态数据缓存到离用户最近的地方
- 动态内容改造
- URL 唯一化
- 分离浏览者相关的因素浏览者相关的因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。
- 分离时间因素。服务端输出的时间也通过动态请求获取。
- 异步化地域因素。详情页面上与地域相关的因素做成异步方式获取,当然你也可以通过动态请求方式获取,只是这里通过异步获取更合适。
- 去掉 Cookie
- 动态数据的处理
- ESI 方案(或者 SSI):即在 Web 代理服务器上做动态内容请求,并将请求插入到静态页面中,当用户拿到页面时已经是一个完整的页面了。这种方式对服务端性能有些影响,但是用户体验较好。
- CSI 方案。即单独发起一个异步 JavaScript 请求,以向服务端获取动态内容。这种方式服务端性能更佳,但是用户端页面可能会延时,体验稍差。
- 架构方案
- 单机部署这种方案是将虚拟机改为实体机,以增大 Cache 的容量,并且采用了一致性 Hash 分组的方式来提升命中率。
- 优点
- 没有网络瓶颈,而且能使用大内存;
- 既能提升命中率,又能减少 Gzip 压缩;
- 减少 Cache 失效压力,因为采用定时失效方式,例如只缓存 3 秒钟,过期即自动失效;
- 缺点
- 一定程度上也造成了 CPU 的浪费,因为单个的 Java 进程很难用完整个实体机的 CPU。
- 一个实体机上部署了 Java 应用又作为 Cache 来使用,这造成了运维上的高复杂度
- 优点
- 统一Cache:分布式缓存
- 优点
- 单独一个 Cache 层,可以减少多个应用接入时使用Cache 的成本。这样接入的应用只要维护自己的 Java 系统就好,不需要单独维护 Cache,而只关心如何使用即可。
- 统一 Cache 的方案更易于维护,如后面加强监控、配置的自动化,只需要一套解决方案就行,统一起来维护升级也比较方便。
- 可以共享内存,最大化利用内存,不同系统之间的内存可以动态切换,从而能够有效应对各种攻击。
- 缺点
- Cache 层内部交换网络成为瓶颈;
- 缓存服务器的网卡也会是瓶颈;
- 机器少风险较大,挂掉一台就会影响很大一部分缓存数据。
- 优点
- CDN
- 问题
- 失效问题
- 命中率问题
- 发布更新问题
- 解决方案
- 靠近访问量比较集中的地区;
- 离主站相对较远;
- 节点到主站间的网络比较好,而且稳定;
- 节点容量比较大,不会占用其他 CDN 太多的资源;
- 问题
- 单机部署这种方案是将虚拟机改为实体机,以增大 Cache 的容量,并且采用了一致性 Hash 分组的方式来提升命中率。
- 静态数据的处理
- 热点数据
- 什么是热点
- 热点操作
- 读请求
- 写请求
- 热点数据
- 静态数据热点:能够提前预测的数据例如,我们可以通过卖家报名的方式提前筛选出来,通过报名系统对这些热点商品进行打标。比如我们分析历史成交记录、用户的购物车记录,来发现哪些商品可能更热门、更好卖
- 动态数据热点:系统运行过程总产生的热点例如,卖家在抖音上做了广告,然后商品一下就火了,导致它在短时间内被大量购买。
- 热点操作
- 问题
- 首先,热点请求会大量占用服务器处理资源,虽然这个热点可能只占请求总量的亿分之一,然而却可能抢占 90% 的服务器资源,如果这个热点请求还是没有价值的无效请求,那么对系统资源来说完全是浪费
- 其次,即使这些热点是有效的请求,我们也要识别出来做针对性的优化,从而用更低的代价来支撑这些热点请求。
- 发现热点
- 发现静态热点数据
- 静态热点数据可以通过商业手段,例如强制让卖家通过报名参加的方式提前把热点商品筛选出来
- 还可以通过技术手段提前预测,例如对买家每天访问的商品进行大数据计算,然后统计出 TOP N 的商品,我们可以认为这些 TOP N 的商品就是热点商品。
- 发现动态热点数据
- 步骤
- 构建一个异步的系统,它可以收集交易链路上各个环节中的中间件产品的热点 Key
- 建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(包括详情、购物车、交易、优惠、库存、物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提前做好保护。
- 将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道哪些商品会被频繁调用,然后做好热点保护
- 注意事项
- 这个热点服务后台抓取热点数据日志最好采用异步方式,因为“异步”一方面便于保证通用性,另一方面又不影响业务系统和中间件产品的主流程
- 热点服务发现和中间件自身的热点保护模块并存,每个中间件和应用还需要保护自己。
- 热点发现要做到接近实时(3s 内完成热点数据的发现)
- 步骤
- 处理热点数据
- 优化:对于热点数据,直接缓存
- 限制:可以把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求始终得不到服务器的处理资源
- 隔离
- 业务隔离
- 系统隔离
- 数据隔离
- 发现静态热点数据
- 什么是热点
- 削峰
- 我们知道服务器的处理资源是恒定的,你用或者不用它的处理能力都是一样的,所以出现峰值的话,很容易导致忙到处理不过来,闲的时候却又没有什么要处理。但是由于要保证服务质量,我们的很多处理资源只能按照忙的时候来预估,而这会导致资源的一个浪费
- 好处
- 一是可以让服务端处理变得更加平稳
- 二是可以节省服务器的资源成本
- 解决思路
- 排队
- 消息队列
- 线程池
- 先进先出、先进后出等常用的内存排队算法的实现方式
- 答题
- 第一个目的是防止部分买家使用秒杀器在参加秒杀时作弊
- 第二个目的其实就是延缓请求,起到对请求流量进行削峰的作用,从而让系统能够更好地支持瞬时的流量高峰。
- 过滤:在不同的层次尽可能地过滤掉无效请求,让“漏斗”最末端的才是有效请求
- 大部分数据和流量在用户浏览器或者 CDN 上获取,这一层可以拦截大部分数据的读取;
- 经过第二层(即前台系统)时数据(包括强一致性的数据)尽量得走 Cache,过滤一些无效的请求;
- 再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,这样数据量和请求就进一步减少;
- 最后在数据层完成数据的强一致性校验
- 排队
- 动静分离也就是所谓“动态”还是“静态”,并不是说数据本身是否动静,而是数据中是否含有和访问者相关的个性化数据。
一致性
- 减库存的方式
- 下单减库存:但是你要知道,有些人下完单可能并不会付款
- 付款减库存:有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了
- 预扣库存:买家下单后,库存为其保留一定的时间(如 10 分钟)
- 库存超卖
- 对普通的商品下单数量超过库存数量的情况,可以通过补货来解决;
- 但是有些卖家完全不允许库存为负数的情况,那只能在买家付款时提示库存不足
- 库存优化
- 在缓存减库存
- 数据库并发锁问题
- 热点数据隔离
- 应用层做排队
- 数据库层做排队