秒杀系统的那些事(正在更新)

1. 概述

秒杀系统之所以难做,是因为在极短的时间内涌入大量的请求,来同时访问有限的服务资源,从而造成系统负载压力大,甚至导致系统服务瘫痪以及宕机的可能。

2. 例子

例如小米手机每周二的秒杀,可能手机只有1万部,但瞬时进入的流量可能是几百几千万。
又例如12306抢票,亦与秒杀类似,瞬时流量更甚。

3.为什么难

(1)短时间内高并发,系统负载压力大
(2)竞争的资源有限,数据库锁冲突严重
(3)避免对其他业务的影响

4. 常见的互联网分层架构

在这里插入图片描述
常见站点架构如上:
(1)客户端层(秒杀页面): 手机或PC端操作的客户端页面,域名通过DNS解析路由到NG,是系统的最上层,会执行到一些JS代码。
(2)反向代理层: 一般通过NG作为反向代理,将客户端请求均衡路由到后端站点服务,NG也可以水平扩展为多实例,且每个实例可单独部署为主从的高可用方案。
(3)站点层: 站点层可以水平扩展为多个实例部署,以此来均衡来自客户端请求产生的高并发负载,多个web server之间的session信息可以集中存储于分布式缓存服务(Redis,MemCache)中。这一层会访问后端数据,拼html页面返回给浏览器
(4)服务层: 服务层也可水平扩展为多个实例部署,即时下最火的微服务方式。向上游屏蔽底层数据细节
(5)数据库层: 数据库层的常见部署方式,如读写分离,分库分表等

3. 秒杀架构设计理念

传统秒杀系统会挂的原因是将请求都积压到数据库,数据库读写锁冲突严重,导致几乎所有请求都超时。

限流: 将请求尽量拦截在上层。对于秒杀系统来说,系统的瓶颈一般在数据库层,资源是有限的。鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端。

削峰: 对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。(消息队列 可以削峰,将拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理。)

异步处理: 秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。

内存缓存: 秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。秒杀系统都是一个读多写少的场景(比如一辆火车只有2000张票,但是有200w人买,还有的重复刷新,写的比例少于0.1%,读的比例超过99.9%),读多写少的场景适合用缓存。

可拓展: 当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了。像淘宝、京东等双十一活动时会增加大量机器应对交易高峰。

热点隔离:
业务隔离: 如12306的分时段售票,将热点数据分散处理,来降低系统负载压力
系统隔离: 实现系统的软硬隔离,不光是实现软件的隔离,还可以实现硬件的隔离,尽最大限度的减少秒杀带来的高并发安全性问题。

数据隔离:启用单独的cache集群或数据库来存放热点数据

4. 优化细节

4.1 浏览器层请求拦截(防止重复提交)

现象描述:
点击了“查询”按钮之后,系统那个卡呀,进度条涨的慢呀,作为用户,我会不自觉的再去点击“查询”,继续点,继续点,点点点。。。有用么?平白无故的增加了系统负载(一个用户点5次,80%的请求是这么多出来的),怎么整?

1. 产品层面
用户点击之后将按钮置灰,禁止重复提交请求。
2. JS方面
限制用户在x秒之内只能提交一次请求。

4.2 站点层请求拦截和页面缓存

现象描述:
浏览器层的请求拦截,只能拦住小白用户(不过这是99%的用户哟),高端的程序员根本不吃这一套,写个for循环,直接调用你后端的http请求,怎么整?
1. 限流 一般都会采用IP级别的限流,即针对某一个IP,限制单位时间内发起请求数量。或者活动入口的时候增加游戏或者问题环节进行消峰操作
2. 页面缓存 同一个item的查询,例如手机车次,做页面缓存,x秒内到达站点层的请求,均返回同一页面
如此限流,又有99%的流量会被拦截在站点层
3. 扩容
增加机器,增加前端池的整体承载量来抗峰值。利用Nginx反向代理实现web server端的水平扩展
4. 静态化
将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。
5. 动静分离
将几乎不变的静态页面直接通过NG或CDN来路由访问,只有动态变换的页面可以请求到web server端
5. 有损服务
最后一招,在接近前端池承载能力的水位上限的时候,随机拒绝部分请求来保护活动整体的可用性。

4.3 服务层请求拦截与数据缓存

现象描述:
站点层的请求拦截,只能拦住普通程序员,高级黑客,假设他控制了10w台肉鸡(并且假设买票不需要实名认证),这下uid的限制不行了吧?怎么整?
1. 采用消息队列缓存请求:既然服务层知道库存只有100台手机,那完全没有必要把100W个请求都传递到数据库啊,那么可以先把这些请求都写到消息队列缓存一下,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。这样可以控制速度来访问DB,也可以通过消息队列进行一部处理。
2. 利用缓存应对读请求:对类似于12306等购票业务,是典型的读多写少业务,大部分请求是查询请求,所以可以利用缓存分担数据库压力。
3. 利用缓存应对写请求:缓存也是可以应对写请求的,比如我们就可以把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。

注: 库存信息放入Redis缓存的时候最好分为多份放入不同key的缓存中,如库存为10万可以分为10份分别放入不同key的缓存中,这样将数据分散操作可以达到更高的读写性能。

4.4 数据层闲庭信步

到了数据这一层,几乎就没有什么请求了,单机也能扛得住,还是那句话,库存是有限的,小米的产能有限,透这么多请求来数据库没有意义。
1. 读写分离
2. 分表分库
3. 数据库集群

4.6 后端

主要讨论写的问题,读的问题可以很容易解决。()内部是淘宝的解决方案

  1. 首先MySQL自身对于高并发的处理性能就会出现问题,一般来说,MySQL的处理性能会随着并发thread上升而上升,但是到了一定的并发度之后会出现明显的拐点,之后一路下降,最终甚至会比单thread的性能还要差。( 关闭死锁检测,提高并发处理性能)
  2. 其次,超卖的根结在于减库存操作是一个事务操作,需要先select,然后insert,最后update -1。最后这个-1操作是不能出现负数的,但是当多用户在有库存的情况下并发操作,出现负数这是无法避免的。(修改源代码,将排队提到进入引擎层前,降低引擎层面的并发度。)
  3. 最后,当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢InnoDB行锁的问题,导致出现互相等待甚至死锁,从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。(组提交,降低server和引擎的交互次数,降低IO消耗。)

4.7 解决方案

解决方案1:

将存库从MySQL前移到Redis中,所有的写操作放到内存中,由于Redis中不存在锁故不会出现互相等待,并且由于Redis的写性能和读性能都远高于MySQL,这就解决了高并发下的性能问题。然后通过队列等异步手段,将变化的数据异步写入到DB中。
优点: 解决性能问题
缺点: 没有解决超卖问题,同时由于异步写入DB,存在某一时刻DB和Redis中数据不一致的风险。

解决方案2:
引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。
优点: 解决超卖问题,略微提升性能。
缺点: 性能受限于队列处理机处理性能和DB的写入性能中最短的那个,另外多商品同时抢购的时候需要准备多条队列。

解决方案3:
将写操作前移到MC中,同时利用MC的轻量级的锁机制CAS来实现减库存操作。
优点: 读写在内存中,操作性能快,引入轻量级锁之后可以保证同一时刻只有一个写入成功,解决减库存问题。
缺点: 没有实测,基于CAS的特性不知道高并发下是否会出现大量更新失败?不过加锁之后肯定对并发性能会有影响。

解决方案4:
将提交操作变成两段式,先申请后确认。然后利用Redis的原子自增操作(相比较MySQL的自增来说没有空洞),同时利用Redis的事务特性来发号,保证拿到小于等于库存阀值的号的人都可以成功提交订单。然后数据异步更新到DB中。
优点: 解决超卖问题,库存读写都在内存中,故同时解决性能问题。
缺点: 由于异步写入DB,可能存在数据不一致。另可能存在少买,也就是如果拿到号的人不真正下订单,可能库存减为0,但是订单数并没有达到库存阀值。

5. 解决超卖的问题

首先设定一个前提,为了防止超卖现象,所有减库存操作都需要进行一次减后检查,保证减完不能等于负数。(由于MySQL事务的特性,这种方法只能降低超卖的数量,但是不可能完全避免超卖)

update number set x=x-1 where (x -1 ) >= 0;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值