本文共 2366 字,大约阅读时间需要 7 分钟。
1、概述
秒杀其实主要解决的是两个问题,一个是并发读,一个是并发写。秒杀系统本质上是一个满足大并发、高性能和高可用的分布式系统。
在整个用户请求路径上从浏览器到服务端我们要遵循几个原则:
- 用户请求的数据尽量少,从而减少cpu消耗
- 请求数尽量少:合并CSS和JavaScript文件
- 路径尽量短,减少节点消耗:多个强依赖的应用合并部署,简化调用方式
- 依赖尽量少:高优先级的系统减少对低优先级系统的依赖,方式重要的系统被不重要的系统拖垮。
- 不要有单点,要有备份
具体措施:
- 将热点数据(如库存数据)单独放在一个缓存系统中,以提高读性能。静态数据可以放在机器内存。
- 增加秒杀答题,防止有秒杀器抢单。
- 动静分离,局部刷新
- 增加系统限流保护,防止最坏情况发生。
2、动静分离
静态数据如何做缓存?
- 把静态数据缓存到离用户近的地方:用户浏览器、CDN、服务端Cache。
- 静态化改造就是要直接缓存HTTP连接,相比于java,像Nginx、Apache和Varnish更擅长处理大并发静态文件请求。
如何把动态页面改造成适合缓存的静态页面?
- URL唯一化,这样就可以用URL作为key来缓存整个HTTP连接
- 分离浏览者相关的因素,如是否登录、登录身份。可以用动态请求获取。
- 分离时间因素,服务器输出的时间也动态获取
- 异步化地域因素,与地域相关的因素异步动态获取
- 去掉Cookie,在缓存的静态数据中不含有Cookie
动态内容的处理方案?
- ESI:在web代理服务器上做动态内容请求,并将请求插入到静态页面中。这种对服务器性能有些影响。
- CSI:单独发起JavaScript请求,向服务器请求动态内容。这种页面可能延时
动静分离架构方案:
- 实体机单机部署,以增大cache容量,并采用一致性Hash分组的方式来提升命中率。但是会造成CPU的浪费。
- 统一Cache层,就是将单机的cache分离出来形成cache集群。可以减少运维成本,也方便其他系统接入。
- 上CDN,CDN离用户最近,效果更好。
3、热点数据
热点:分为热点操作和热点数据,热点数据分为静态热点数据和动态热点数据。
如何发现热点数据?
- 发现静态热点数据:(1)卖家报名,把参加活动的商品打标。(2)大数据计算平时热卖商品topN
- 发现动态热点数据:(1)日志聚合、分析,如nginx可以检测那些url被高频访问。
如何处理热点数据?
- 优化:临时缓存热点数据,采用LRU淘汰算法替换。
- 限制:对商品ID做一致性HASH,分桶,每个分桶一个处理队列。
- 隔离:(1)业务隔离(2)系统隔离,申请单独域名(3)数据隔离,使用单独的Cache集群或者数据库
4、流量削峰
流量削峰方式:
- 排队,(1)使用消息队列(2)线程池加锁等待(3)请求序列化到文件,然后顺序地读文件来恢复请求
- 答题,避免秒杀器作弊,延缓请求。
- 分层过滤:在不同的层次尽可能地过滤掉无效请求,让漏斗最末端的才是有效请求。
5、服务端性能优化
QPS:每秒请求数、RT:响应时间。
总QPS=(1000ms/响应时间)*线程数量
线程数=[(线程等待时间+线程CPU时间)/线程CPU时间]*cpu数量
如何优化系统:
- 减少编码:java编解码很慢,可以把一些静态的数据提前转化成字节缓存起来,等到真正往外写的时候直接用OutputStream()函数写,就可以减少静态数据的编码转换。Velocity就是这么做的。
- 减少序列化:减少RPC,将关联性比较强的应用进行合并部署,比如说部署在同一个tomcat容器中,且不走本机的socket。
- Java极致优化:(1)直接用Servlet处理请求,不用框架(2)直接输出流数据,使用resp.getOutputStream而不是resp.getWriter()函数,可以省掉一些不便字符的编码。数据输出使用JSON而不是模板引擎。
- 并发读优化:静态数据秒杀前全量推送到秒杀机器上,一直缓存到秒杀结束。动态数据设置缓存TTL,失效后拉取最新数据。
6、减库存的设计
减库存的方式:
- 下单减库存
- 付款减库存
- 预扣库存:买家下单,库存为其保留一定时间,超过时间,库存自动释放。付款后实际地减去库存。
第三种比较好,但是不能避免恶意下单不付款。可以采取以下措施:
- 设置每人最大购买件数
- 重复下单不付款的次数限制
- 超卖补货,或者付款时提示库存不足
大型秒杀中如何减库存?
秒杀中多使用下单减库存,业务上要保证大并发请求时库存数据不能为负数,做法有:
- 可以通过事务来判断,为负数就回滚。
- 设置数据库的字段为无符号整数,这样小于0是数据库报错。
- 使用CASE WHEN语句:
UPDATE item SET inventory=CASE WHEN inventory>=xxx THEN inventory-xxx ELSE inventory END
秒杀减库存的极致优化:如果减库存逻辑非常单一,可以考虑直接放到缓存中完成,否则必须在数据库中完成减库存,不过这会导致多个线程竞争数据库行锁,要解决并发锁的问题,有两种办法:
- 应用层做排队,防止热点商品占用太多数据库连接
- 数据库层做排队,阿里的补丁可以在数据库层对单行记录做到并发排队。
7、设计兜底方案
系统的高可用建设涉及项目生命周期的各个阶段,如下图所示:
遇到大流量时,应该从哪些方面来保障系统的稳定运行?
- 降级,当系统容量达到一定程度时,限制或者关闭系统的某些非核心功能
- 限流,当系统容量达到瓶颈时,通过限制一部分流量来保护系统。既要支持URL以及方法级别的限流,也要支持基于QPS和线程的限流,后者比较常用。
- 拒绝服务,可以在Nginx设置过载保护,当机器负载过高时拒绝HTTP请求并返回503错误码(表示服务不可用)。
转载地址:http://nlbii.baihongyu.com/