redis 秒杀系统
NineSun 人气:01.如何设计一个秒杀系统
在设计任何系统之前,我们首先都需要先理解秒杀系统的业务背景
下面我简单的举一个例子:
在某个时间点,某某电商网站要低价卖某件商品,而且限量1千件,抢购人数超过数十万人。
所以我们面临的第一个秒杀的问题就是:时间极短,然后瞬间流量非常大
我们的系统必须保证秒杀抢购的结果不出错,达到抢购的预期目的。
而且秒杀库存的实现也需要保障秒杀结果的准确性。
总结几个特点就是:
- 高性能:秒杀中有大量的并发读写,所以需要使系统能支撑起高并发访问,这是一个关键点。
- 高可用:藐视瞬间流量非常大,很有可能会导致系统宕机,所以需要从各方面保证系统的可用性。
- 一致性:由于秒杀请求量非常大,此时就需要我们的秒杀结果要准确。因为一旦出错,那么波及面会非常广,损失非常大。
2.秒杀流程
我们先从秒杀的入口开始说起
在秒杀入口的地方会有这些问题需要解决:
2.1 前端处理
静态资源处理
秒杀商品一般都会包含很多静态资源,所以这些图片什么的静态资源一定要放到CDN(Content Delivery Network,即内容分发网络),能放的尽量放进去。让秒杀时后端服务器的压力尽可能小。
说到此处,我重点描述讲解CDN
为了能在传统IP网上发布丰富的宽带媒体内容,提出在现有互联网基础上建立一个内容分发平台专门为网站提供服务。由于CDN是为加快网络访问速度而被优化的网络覆盖层,因此被形象地称为“网络加速器”。
首先要说的是应用服务器和资源服务器应该解耦,也就是应用服务器只处理逻辑,而资源服务器存放内容或者叫资源。
- CDN专注于「内容」,也就是CDN的C所代表的Content,专注于静态资源的分发和访问,比如一张图片,一个文本文件,一个视频,一个CSS,一个JS等等,任何以文件形式存储的,为了提高在互联网上的访问速度和质量,都可以将这个资源部署在CDN这个网络上。
- CDN动作是「分发」,也就是如何让刚才提到的那些「内容」快速的部署在这个网络中,从而快速为用户服务,其实还有一层更重要的含义是用户的快速访问与就近接入,分发的目的是为了用户更好的体验。
- CDN落定于「网络」,是部署于全国或者全世界的一大堆服务器,这些服务器基于当前互联网的基础架构在其上层再构成一个网络,这个网络专为资源分发而生。
那CDN的原理是什么呢?为什么用户可以接入离他最近的服务器呢?
主要是利用了DNS来判断用户位置,再返回给用户最近的机房的服务器的资源地址。
下面我再通俗的解释一下:
肯德基的总部在美国,可是你家楼下也有一家肯德基,并且汉堡包是一模一样的,这就是CDN(这个比喻来自知乎,觉得非常的恰当)。肯德基部署了很多个CDN在世界各地提供服务,用户都是找到最近的店,这个计算过程就是刚才讲的「就近接入」。
什么是CDN的调度呢?
(1)DNS调度是最常用和最通用的调度方案,缺点是存在DNS劫持的风险,调度的精确度也会差一些;
(2)302调度非常适合用在大文件下载和视频点播这两个应用场景,优点是可以提高调度的精确度,缺点是将会增加首包的时延(在大文件下载和视频点播场景下对首包时延不太敏感,而对调度精确度要求更高)
(3)HTTPDNS调度的优点是有较高的安全性(可以规避DNS劫持风险)和调度精确度,但是有个很大的缺点,需要客户端提供支持(例如在手机APP上嵌入SDK),通用性较差。
通俗来讲:
在一个商圈有两家肯德基,有一家组织活动,鸡腿随便吃,所以顾客全部涌到这家店,已经水泄不通,另一家店则门可罗雀。这个时候CDN的调度功能就要发挥作用了,另一家店也发布了一个消息说,买一个汉堡,打五折。这个时候,在第一家店抢不上鸡腿的顾客,马上跑去了第二家店,这个时候两个店的流量处于均衡状态。这就是CDN的调度。
恶意访问行为的处理
当我们推出秒杀活动后,还需要考虑黄牛党们开发出的各种秒杀器,可以自动填单,自动回答各种问题,以及自动模拟点击等,令我们防不胜防。
针对这些我们可以通过各种工作来限制和识别这些恶意访问。
- 例如限制IP的提交次数
- 提高各种动态验证码及问题的难度
- 增加黑名单账户
秒杀链接隐藏
如果稍微懂点程序的人可以提前拿到秒杀链接,那么就可以通过程序在最快的时间发起秒杀请求,这样人家就可以拿到大部分商品了。为了防止这一点,可以使秒杀链接动态化。使用MD5算法等加密随机字符串作为URL的一部分,秒杀开始后才将连接放出来,同时在后台进行校验,此时已经可以防止一大批的羊毛党了。
前端限流
可以在秒杀按钮点击之后灰掉几秒钟,几秒钟之内只能点击一次。
可以使用Nginx用户请求到Nginx的时候将流量分散到多个服务器上,而且也可以针对用户进行一些过滤,将一些请求拦截,保证后端的稳定性。比如1万个商品,最多放进来10万个请求就可以了,其他的用户就只能等着静态页面喽。同时也可以在秒杀预约的时候随机发放一些token,只有拥有这些token的客户才有可能抢购成功。
同时在分布式的架构下,我们也可以通过gateway,redis+lua或者nginx进行限流
2.2 后端处理
后端限流
如果服务的流量到达最大值的时候,新的请求就不能再进来了。
而服务宕机的时候也需要引导请求到备用服务器上面,然后返回一些静态提示页面等。
隔离就要求,秒杀的服务单独部署,只承担其秒杀的单一职责,即使出问题,也不会影响其他的服务。
削峰
秒杀流量在某一个时间点非常高,那么我们让瞬间进来的流量进到一个缓冲池,然后再进行平缓处理。比较多用到的方案就是使用消息队列来处理。
库存预热
由于秒杀的商品的数量一般都是提前已知的,这时我们可以提前将商品的一些数据提前加载到缓存中。并且可以将商品分区来进行秒杀,根据每个大区的用户数量以及活跃程度,为每个大区分配单独的秒杀商品数量。这样也可以分散服务器压力。
使用缓存
高并发的情况下必然会遇到缓存雪崩,缓存击穿,缓存穿透等问题。而且秒杀的场景是读多写少,使用Redis作为缓存非常合适,为了避免单台Redis服务器出问题,导致缓存击穿等问题,升级使用Redis集群是一个比较好的方案。提升可用性的性能也可以大大提高。
下面我简单说一下缓存雪崩,缓存击穿以及缓存穿透
首先我们要先了解一下缓存的处理流程
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
知道什么是缓存之后我们重点来了解一下这三个名次具体指什么,以及如何去解决
缓存穿透
描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿
描述: 缓存击穿是指缓存中没有数据但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:
- 设置热点数据永远不过期。
- 加互斥锁,互斥锁参考代码如下
我简单解释一下代码思路:
我们首先从缓存中获取数据,如果数据不存在,我们则去获取锁,这把锁 只需要能够互斥,可重入即可,最简单的就是redis的setnx来实现,获取锁资源以后,从数据库读取数据,同时将数据更新至缓存,然后释放锁;如果获取锁资源失败,我们就让其隔一段时间之后重新尝试去获取锁资源。
缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。
3.超卖问题
秒杀中一个重要的点就是超卖问题,由于抢购人数多,流量也很大,但是也不能卖多了。
目前常见的解决方案就是:
1.数据库要加唯一索引,减库存的时候要先进行库存数量判断等,数据库锁,加版本号的乐观锁方式等等。2
.采用Redis来维护库存,由于秒杀活动可以预先知道商品的数量,所以可以提前将商品的数据加载到Redis中,如果Redis的库存不足的话则秒杀失败。
3.生成订单的时候将请求放到服务端的异步队列中去处理,可以使用Redis的队列,或者MQ均可。
4.总体思路
其实秒杀方案的总体思路也很简单:
1.尽可能的将请求拦截在上游;
2.后端均要处理限流;
3.尽量减少请求到数据库;
4.多利用缓存;
5.使用异步操作-可以使用队列等;
6.秒杀服务单一职责;
7.尽早失败,让秒杀请求返回
加载全部内容