概述
大量的并发读和并发写
4 要 1 不要”
- 数据要尽量少
- 请求数要尽量少(页面依赖的 CSS/JavaScript、图片,以及 Ajax 请求等等都定义为“额外请求,把多个 JavaScript 文件合并成一个文件)
- 路径要尽量短(多个相互强依赖的应用合并部署在一起,把远程过程调用(RPC)变成 JVM 内部之间的方法调用)
- 依赖要尽量少(要完成一次用户请求必须依赖的系统或者服务)
- 不要有单点(避免将服务的状态和机器绑定,即把服务无状态化)
6. 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面,而只需要点击抢宝按钮,借此把页面刷新的数据降到最少;
7. 本地缓存
8. 限流
不同QPS量级下瓶颈也会不一样,10w级别可能瓶颈就在数据读取上,通过增加缓存一般就能解决,如果要到100w那么,可能服务端的网络可能都是瓶颈,所以要把大部分的静态数据放到cdn上甚至缓存在浏览器里
动静分离
所谓“动静分离”,其实就是把用户请求的数据(如 HTML 页面)划分为“动态数据”和“静态数据”。
所谓“动态”还是“静态”,并不是说数据本身是否动静,而是数据中是否含有和访问者相关的个性化数据。
静态数据做缓存
- 存到离用户最近的地方(用户浏览器里、CDN 上或者在服务端的 Cache 中)
- 静态化改造就是要直接缓存 HTTP 连接(Web 代理服务器根据请求 URL,直接取出对应的 HTTP 响应头和响应体然后直接返回,)
- 谁来缓存静态数据也很重要。不同语言写的 Cache 软件处理缓存数据的效率也各不相同(nginx)
步骤
- 分离出动态数据
- 架构方案
还可以把缓存数据从 CDN 上移到用户的浏览器里
处理好热点数据
发现热点数据
处理热点数据
缓存
热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求始终得不到服务器的处理资源。
热点数据隔离出来,不要让 1% 的请求影响到另外的 99%,隔离出来后也更方便对这 1% 的请求做针对性的优化。
- 业务隔离。把秒杀做成一种营销活动,
- 系统隔离。系统隔离更多的是运行时的隔离(用单独的系统,避免影响现有业务)
- 数据隔离(不想 0.01% 的数据有机会影响 99.99% 数据)
流量削峰
削峰从本质上来说就是更多地延缓用户请求的发出,以便减少和过滤掉一些无效请求,它遵从“请求数要尽量少”的原则。
方案
- 排队(消息队列)
- 答题(防秒杀器刷,延缓请求)
- 分层过滤(在不同的层次尽可能地过滤掉无效请求,让“漏斗”最末端的才是有效请求。)
提升性能(提高qps)
提升性能我们就要减少 CPU 的执行时间,另外就是要设置一个合理的并发线程数
线程数 = 2 * CPU 核数 + 1
发现瓶颈
缓存系统而言,制约它的是内存
存储型系统来说 I/O 更容易是瓶颈
秒杀,它的瓶颈更多地发生在 CPU 上
优化瓶颈
- 减少编码
在很多场景下,只要涉及字符串的操作(如输入输出操作、I/O 操作)都比较耗 CPU 资源,不管它是磁盘 I/O 还是网络 I/O,因为都需要将字符转换成字节,而这个转换必须编码。即用 resp.getOutputStream() 函数写数据,把一些静态的数据提前转化成字节,等到真正往外写的时候再直接用 OutputStream() 函数写,就可以减少静态数据的编码转换。 - 减少序列化(减少 RPC 就可以减少序列化,多个关联性比较强的应用进行“合并部署”)
- Java 极致优化
- 直接使用 Servlet 处理请求。避免使用传统的 MVC 框架,这样可以绕过一大堆复杂且用处不大的处理逻辑)
- 直接输出流数据。使用 resp.getOutputStream() 而不是 resp.getWriter() 函数,可以省掉一些不变字符数据的编码
- 并发读优化(集中式缓存,虽然单台缓存机器也能支撑 30w/s 的请求,不够,是使用LocalCache)
减库存
- 下单减库存:(问题:恶意下单,下单了不付款,从而影响卖家的商品销售)
- 付款减库存:(下单成功,支付失败,体验差)
- 预扣库存方案:(下单后一般都有个“有效付款时间”,超过这个时间订单自动释放)
秒杀使用下单减库存,因为抢到就是赚到
保证数据不能为负即可
- 一种是在应用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚;
- 另一种办法是直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行 SQL 语句来报错;
- 再有一种就是使用 CASE WHEN 判断语句,例如这样的 SQL 语句:
UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END
能否把秒杀商品减库存直接放到缓存系统中实现,也就是直接在缓存中减库存或者在一个带有持久化功能的缓存系统(如 Redis)中完成呢?
如果你的秒杀商品的减库存逻辑非常单一,比如没有复杂的 SKU 库存和总库存这种联动关系的话,我觉得完全可以。但是如果有比较复杂的减库存逻辑,或者需要使用事务,你还是必须在数据库中完成减库存。
并发写
- 独立数据库,否则大量线程来竞争 InnoDB 行锁,影响正常业务
- 应用层做排队。按照商品维度设置队列顺序执行
- 数据库层做排队。应用层只能做到单机的排队
兜底方案
- 降级(降级的核心目标是牺牲次要的功能和用户体验来保证核心业务流程的稳定,是一个不得已而为之的举措。)
- 限流(当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。)
- 拒绝服务
解决大并发读问题,可以采用 LocalCache(即在秒杀系统的单机上缓存商品相关的数据)和对数据进行分层过滤的方式