文章较长、直奔重点,欢迎与我交流。
什么是秒杀?
“秒杀”是商家在特定时间点进行促销的一种运营手段,体现在系统层面,是指一个Web系统,在一秒钟收到数以万计的用户请求,来抢购数量有限的促销产品。本质上,秒杀系统就是一个“三高”系统,即高并发、高性能、高可用的分布式系统。本文主要从“高并发”的角度,来看看需要解决的主要问题。
秒杀需要处理并发读,并发写。“并发读”需要通过各种手段减少用户到服务端来读数据,而“并发写”主要需要保证数据的正确性,确保大流量下数据一致。
秒杀系统关注三方面
下面以一个秒杀系统的例子,来看看一个秒杀系统主要关注的三方面问题。
一、高并发访问
秒杀前用户不断刷新页面查看,来获得购买按钮,抢购时集中点击并发购买,核心是对系统数据的高并发读访问,可以通过如下手段进行防护。
访问拦截:从浏览器/反向代理/Web层/服务层/数据层多层拦截流量,尽量把访问拦截在离用户更近的层,减少对后台的压力;
分流:主要通过分布式集群技术,多台机器处理,提高并发能力;
动静分离:将动态数据和静态数据尽量分离,并通过缓存和CDN等技术对数据读取进行加速;
二、数据正确性
秒杀伴随这一系列业务流程,包括浏览商品、进入抢购页、购买、扣减库存、支付。其中并发写主要与扣减库存有关,要保证数据的正确性,防止超卖发生。大概的解决方法有:
减库存:事务判断,热点商品放到单独的热点库,增加并发锁
热点:对核心热点数据进行实时分析,并在系统中进行优化或隔离
异步化:是指把购买请求的接受和处理异步化。购买请求先放到队列中,这个过程非常高效,返回客户信息。
限流降级:请求限流,控制系统的访问;对服务的下游依赖进行降级,保证核心链路
三、防作弊
抢购还需要保证公平性,方舟如购票插件购买火车票、黄牛党等。在技术角度,大概方法有:
答题:为了增加购买的复杂度;延缓请求。
Cache校验:处理用户购买请求时校验缓存中是否已记录此商品的购买。
秒杀系统设计原则
1、前台请求数尽量少
前台请求会增加浏览器的负担,尽量简化或合并页面大小(CSS/JS,图片等),去掉页面装饰;减少DNCS解析
2、后台数据尽量少
后台数据会增加网络传输,压缩和字符编码,消耗CPU。需要梳理后台依赖的数据和服务,关注序列化/反序列化方式,减少不必要的数据交互
3、调用路径要尽量短
分布式调用错综复杂,需要尽量缩短调用链路;减少第三方依赖,尽量弱依赖,并做好应用分级
4、尽量不要有单点
高可用和稳定性角度,消除单点;服务无状态化,解藕服务状态和机器,如机器配置动态化;有状态的存储,通过冗余备份提高可用性。
高并发之“访问拦截”
高并发的访问拦截主要思路是:
从浏览器/反向代理/Web层/服务层/数据层多层拦截流量,尽量把访问拦截在离用户更近的层,尽可能地过滤掉无效请求,让“漏斗”最末端的才是有效请求。同时,用户请求更早的得到处理,也减少了每次请求的RT,进而提高了系统的QPS和并发程度。
浏览器访问拦截
防止用户的不必要的点击,可以在产品层面限制用户的行为,比如点击后按钮不可用,限定时间接受请求。当然,为了防止恶意抢购,还需要在防作弊上做其他尝试,比如前文讲的答题/cache校验等。
CDN加速
CDN的全称是Content Delivery Network,即内容分发网络。简单来讲,就是把原服务器上数据复制到其他服务器上,用户就近访问服务器获取资源。缺点是内容变更生效慢。
反向代理访问拦截
主要是动静分离,静态文件作为web项目的一部分进行发布;另外通过Nginx托管静态文件 ,减轻Web服务的压力。
Web层和Service层访问拦截
主要是动态数据访问,可以采用缓存的策略,减少对下一层的数据访问。缓存又可分为本地缓存和redis、memcache等缓存中间件。
高并发之“分流”
分流主要通过分布式集群技术,多台机器处理,提高并发能力。
DNS轮询:从App/Browser入口,对请求进行分类,静态数据直接去CDN获取,同时Nginx进行集群化,通过DNCS轮询。
Nginx负载均衡:Nginx可以支持10W的并发访问, Nginx支持配置请求的代理策略,把请求路由到多个Web服务器处理。Nginx支持的负载均衡策略包括:轮询,权重,ip_hash,fair,url_hash等
分布式架构负载策略:Web层调用service,以及service之间的调用,每个service都需要部署多份。目前最常用的两个框架技术,spring cloud、dubbo、HSF,都采用客户端负载均衡策略,路由到service的不同实例。
Redis集群:redis单台几十万的QPS,可通过Redis集群,把数据分配到多台服务器上,减轻每台机器的负载。
Mysql读写分离:一般不对写请求分流,也可通过分布式数据库技术对写请求分流。对于读请求,一般采用读写分离的策略。读库应用单独设置索引,提高读性能。
高并发之“动静分离”
动态页面:根据实时数据渲染的页面,需要调用后台,读取速度慢
静态页面:存储在文件系统的文件,不经常变动,读取速度很
为了提升效率,尽可能静态化,用静态页面,替换动态页面。例如,商品信息页,商品信息在发布后,保存下商品信息的静态页面;仅访问必要的数据实时数据。
几个关注点:
1、 静态数据缓存到离用户最近的地方。比如浏览器、CDN、服务端的 Cache
2、 可以直接缓存 HTTP 连接。关键是数据中是否含有和访问者相关的个性化数据,比如输出的数据是否和 URL、浏览者、时间、地域相关,以及是否含有 Cookie 等私密数据。
3、 经常使用Nginx和Web服务器缓存静态文件。Java 系统本身不擅长处理大量连接请求,需要在Nginx和Web侧尽量缓存
4、 动态数据尽量Json化。减少不必要的数据传输和解析
5、 缓存还需要考虑失效问题/命中率问题/发布更新问题/动静数据合并;以失效为例,一般有三种方式:超时更新,定时更新,通知更新。
秒杀之“减库存”
减库存一般有如下方式:下单减库存,付款减库存,预扣库存。
其中,笔者比较推荐“下单减库存”,因为参加秒杀,买到就是赚到,成功下单后却不付款的情况比较少;同时卖家对秒杀商品的库存有严格限制,逻辑上更为简单。
当然“下单减库存”,要保证库存数据不能为负数。
一些方法:
1、事务判断,即保证减后库存不能为负数,否则就回滚;
2、直接设置数据库的字段数据为无符号整数,小于零时会直接报错;
3、热点商品放在单独的热点库
4、并发锁:增加并发锁,比如排队等
5、计数器:在Web层或Service层做一个计数器,技术到达阈值,直接返回抢购失败。
6、按商品路由:把对同一品类商品的抢购路由到同一个service以及DB链接上,减少不必要的锁竞争。
实践中的一些原则:
1. 将读数据缓存在 Web 端,过滤掉无效的数据读;
2. 对读数据不做强一致性校验;
3. 对写数据做基于时间的分片,过滤过期请求;
4. 对写请求做限流保护,过滤超载请求;
5. 对写数据强一致性校验,仅校验最后有效的数据。
秒杀之“热点”
秒杀需要对核心热点数据进行实时分析,处理思路可以从业务/系统/数据不同角度进行优化/限制/隔离,比如:
静态热点数据:提前预测。卖家报名,提前打标
动态热点数据:系统在运行过程中临时产生的热点
也可以增加单独的热点分析组件,总体控制系统业务的热点,并做对应的限制和隔离方法。
秒杀之“异步”
秒杀系统设计上,经常使用消息队列来缓冲瞬时流量,把同步的请求调用转换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地消费消息。
本质上,就是把一步变成两步,其中增加一些机制来缓冲。比如在秒杀优惠券抢购,用户抢码(确定用户是否有资格) 和 系统发码(为有资格用户分发优惠券) ,抢码QPS可能10几万,只需要知道是否有资格;而发码仅针对有资格用户,QPS可能仅仅几千,使用消息队列,可以大大提高效率。
除了消息队列,也可以使用其他方式排队,比如线程池加锁等待、内存排队算法(如FIFO)、文件排队(如基于 MySQL binlog 的同步机制)
Takeaways
-
秒杀系统就是一个“三高”系统,即高并发、高性能和高可用的分布式系统
-
秒杀设计原则:前台请求尽量少,后台数据尽量少,调用链路尽量短,尽量不要有单点
-
秒杀高并发方法:访问拦截、分流、动静分离
-
秒杀数据方法:减库存策略、热点、异步、限流降级
-
访问拦截主要思路:通过CDN和缓存技术,尽量把访问拦截在离用户更近的层,尽可能地过滤掉无效请求。
-
分流主要思路:通过分布式集群技术,多台机器处理,提高并发能力。
最后
秒杀要求对系统的高并发、高性能和高可用都有极高的要求,本文主要从高并发的角度进行了阐述。对于高性能和高可用,感兴趣的同学可以参考我的公众号“架构思轩”相关文章。