redis学习笔记36——redis支撑秒杀场景的关键技术和实践都有哪些(代码片段)

qq_34132502 qq_34132502     2023-02-17     652

关键词:

秒杀场景包含了多个环节,可以分成秒杀前、秒杀中和秒杀后三个阶段,每个阶段的请求处理需求并不相同,Redis 并不能支撑秒杀场景的每一个环节。

秒杀场景的负载特征对支撑系统的要求

第一个特征是瞬时并发访问量非常高

一般数据库每秒只能支撑千级别的并发请求,而 Redis 的并发处理能力(每秒处理请求数)能达到万级别,甚至更高。所以,当有大量并发请求涌入秒杀系统时,我们就需要使用 Redis 先拦截大部分请求,避免大量请求直接发送给数据库,把数据库压垮

第二个特征是读多写少,而且读操作是简单的查询操作

在秒杀场景下,用户需要先查验商品是否还有库存(也就是根据商品 ID 查询该商品的库存还有多少),只有库存有余量时,秒杀系统才能进行库存扣减和下单操作。

库存查验操作是典型的键值对查询,而 Redis 对键值对查询的高效支持,正好和这个操作的要求相匹配。

不过,秒杀活动中只有少部分用户能成功下单,所以,商品库存查询操作(读操作)要远多于库存扣减和下单操作(写操作)。

Redis可以在秒杀场景的哪些环节发挥作用

秒杀前

在这个阶段,用户会不断刷新商品详情页,这会导致详情页的瞬时请求量剧增。这个阶段的应对方案,一般是尽量把商品详情页的页面元素静态化,然后使用 CDN 或是浏览器把这些静态化的元素缓存起来。这样一来,秒杀前的大量请求可以直接由 CDN 或是浏览器缓存服务,不会到达服务器端了,这就减轻了服务器端的压力。

在这个阶段,有 CDN 和浏览器缓存服务请求就足够了,我们还不需要使用 Redis。

秒杀中

此时,大量用户点击商品详情页上的秒杀按钮,会产生大量的并发请求查询库存。一旦某个请求查询到有库存,紧接着系统就会进行库存扣减。然后,系统会生成实际订单,并进行后续处理,例如订单支付和物流服务。如果请求查不到库存,就会返回。用户通常会继续点击秒杀按钮,继续查询库存。

简单来说,这个阶段的操作就是三个:库存查验、库存扣减和订单处理。因为之后查询到有库存余量之后,后续的操作才会执行,所以这个阶段最大的并发压力都在库存查验操作上。

为了支撑大量高并发的库存查验请求,我们需要在这个环节使用 Redis 保存库存量,这样一来,请求可以直接从 Redis 中读取库存并进行查验。

那么,库存扣减和订单处理是否都可以交给后端的数据库来执行呢?

订单处理可以,但库存扣减不行。

因为,订单处理会涉及支付、商品出库、物流等多个关联操作,这些操作本身涉及数据库中的多张数据表,要保证处理的事务性,需要在数据库中完成。而且,订单处理时的请求压力已经不大了,数据库可以支撑这些订单处理请求。

那,库存扣减为什么不行?

  1. 额外的开销。Redis 中保存了库存量,而库存量的最新值又是数据库在维护,所以数据库更新后,还需要和 Redis 进行同步,这个过程增加了额外的操作逻辑,也带来了额外的开销。
  2. 下单量超过实际库存量,出现超售。由于数据库的处理速度较慢,不能及时更新库存余量,这就会导致大量库存查验的请求读取到旧的库存值,并进行下单。此时,就会出现下单数量大于实际的库存量,导致出现超售。

最后就是秒杀活动结束后。这时用户请求量已经下降很多了,一般服务器都可以支撑。

Redis 的哪些方法可以支撑秒杀场景

秒杀场景对 Redis 操作的根本要求有两个。

支持高并发

这个很简单,Redis 本身高速处理请求的特性就可以支持高并发。而且,如果有多个秒杀商品,我们也可以使用切片集群,用不同的实例保存不同商品的库存,这样就避免,使用单个实例导致所有的秒杀请求都集中在一个实例上的问题了。不过,需要注意的是,当使用切片集群时,我们要先用 CRC 算法计算不同秒杀商品 key 对应的 Slot,然后,我们在分配 Slot 和实例对应关系时,才能把不同秒杀商品对应的 Slot 分配到不同实例上保存。

保证库存插眼和库存扣减的原子性执行

针对这条要求,我们就可以使用 Redis 的原子操作或是分布式锁这两个功能特性来支撑了。

基于原子操作支撑秒杀场景

原子操作可以是 Redis 自身提供的原子命令,也可以是 Lua 脚本。因为库存查验和库存扣减是两个操作,无法用一条命令来完成,所以,我们就需要使用 Lua 脚本原子性地执行这两个操作。

#获取商品库存信息            
local counts = redis.call("HMGET", KEYS[1], "total", "ordered");
#将总库存转换为数值
local total = tonumber(counts[1])
#将已被秒杀的库存转换为数值
local ordered = tonumber(counts[2])  
#如果当前请求的库存量加上已被秒杀的库存量仍然小于总库存量,就可以更新库存         
if ordered + k <= total then
    #更新已秒杀的库存量
    redis.call("HINCRBY",KEYS[1],"ordered",k)                              return k;  
end               
return 0

基于分布式锁来支撑秒杀场景

使用分布式锁来支撑秒杀场景的具体做法是,先让客户端向 Redis 申请分布式锁,只有拿到锁的客户端才能执行库存查验和库存扣减。这样一来,大量的秒杀请求就会在争夺分布式锁时被过滤掉。而且,库存查验和扣减也不用使用原子操作了,因为多个并发客户端只有一个客户端能够拿到锁,已经保证了客户端并发访问的互斥性。

//使用商品ID作为key
key = itemID
//使用客户端唯一标识作为value
val = clientUniqueID
//申请分布式锁,Timeout是超时时间
lock =acquireLock(key, val, Timeout)
//当拿到锁后,才能进行库存查验和扣减
if(lock == True) 
   //库存查验和扣减
   availStock = DECR(key, k)
   //库存已经扣减完了,释放锁,返回秒杀失败
   if (availStock < 0) 
      releaseLock(key, val)
      return error
   
   //库存扣减成功,释放锁
   else
     releaseLock(key, val)
     //订单处理
   

//没有拿到锁,直接返回
else
   return

我们可以使用切片集群中的不同实例来分别保存分布式锁和商品库存信息。使用这种保存方式后,秒杀请求会首先访问保存分布式锁的实例。如果客户端没有拿到锁,这些客户端就不会查询商品库存,这就可以减轻保存库存信息的实例的压力了。

小结

对于秒杀场景来说,只用 Redis 是不够的。秒杀系统是一个系统性工程,Redis 实现了对库存查验和扣减这个环节的支撑,除此之外,还有 4 个环节需要我们处理好。

  1. 前端静态页面的设计。秒杀页面上能静态化处理的页面元素,我们都要尽量静态化,这样可以充分利用 CDN 或浏览器缓存服务秒杀开始前的请求。
  2. 请求拦截和流控。在秒杀系统的接入层,对恶意请求进行拦截,避免对系统的恶意攻击,例如使用黑名单禁止恶意 IP 进行访问。如果 Redis 实例的访问压力过大,为了避免实例崩溃,我们也需要在接入层进行限流,控制进入秒杀系统的请求数量。
  3. 库存信息过期时间处理。Redis 中保存的库存信息其实是数据库的缓存,为了避免缓存击穿问题,我们不要给库存信息设置过期时间。
  4. 数据库订单异常处理。如果数据库没能成功处理订单,可以增加订单重试功能,保证订单最终能被成功处理。

redis学习笔记36——redis支撑秒杀场景的关键技术和实践都有哪些(代码片段)

秒杀场景包含了多个环节,可以分成秒杀前、秒杀中和秒杀后三个阶段,每个阶段的请求处理需求并不相同,Redis并不能支撑秒杀场景的每一个环节。秒杀场景的负载特征对支撑系统的要求第一个特征是瞬时并发访问... 查看详情

day762.redis秒杀场景-redis核心技术与实战(代码片段)

Redis秒杀场景Hi,我是阿昌,今天学习记录的是关于Redis秒杀场景的内容。秒杀是一个非常典型的活动场景,比如,在双11、618等电商促销活动中,都会有秒杀场景。秒杀场景的业务特点是限时限量,业务系... 查看详情

day762.redis秒杀场景-redis核心技术与实战(代码片段)

Redis秒杀场景Hi,我是阿昌,今天学习记录的是关于Redis秒杀场景的内容。秒杀是一个非常典型的活动场景,比如,在双11、618等电商促销活动中,都会有秒杀场景。秒杀场景的业务特点是限时限量,业务系... 查看详情

尚硅谷redis学习笔记--redis秒杀案例(代码片段)

一、解决计数器和人员记录的事务操作二、Redis事务–秒杀并发模拟使用工具ab模拟测试1、联网:yuminstallhttpd-tools2、无网络(1)进入cd/run/media/root/CentOS7x86_64/Packages(路径跟centos6不同)(2)顺序安装apr... 查看详情

redis个人笔记:redis应用场景,redis常见命令,reids缓存击穿穿透,redis分布式锁实现方案,秒杀设计思路,redis消息队列,reids持久化,redis主从哨兵分片集群

Redis常见数据结构及高级数据结构使用场景,Redis应用场景,Redis常见命令,Reids缓存击穿穿透,Redis分布式锁实现方案,秒杀设计思路,Redis消息队列,Reids持久化,Redis主从哨兵分片集群Redis常见数据结构及高级数据结构使用场景... 查看详情

redis在秒杀场景的作用(代码片段)

...显负载特征:1.1瞬时并发访问量很高一般DB每秒只能支撑k级并发,而Redis并发能达到w级。所以,当大量并发请求涌入秒杀系统时,要使用Redis先拦截大部分请求,避免大量请求直接发给DB1.2读多写少读还是简单... 查看详情

redis在秒杀场景的作用(代码片段)

秒杀业务特点:限时限量,业务系统要处理瞬时高并发请求,Redis是必需品。秒杀可分成秒杀前、秒杀中和秒杀后三阶段,每个阶段的请求处理需求不同,Redis具体在秒杀场景的哪个环节起到作用呢?1秒杀... 查看详情

redis学习笔记总结(代码片段)

文章目录1.为什么使用NoSQL数据库?1.1技术划分1.2使用nosql数据库来解决CPU及内存压力1.3使用nosql数据库解决IO压力2.NoSQL数据库2.1什么是NoSQL数据库?2.2NoSQL的适用场景和不适用场景2.3几种NoSQL数据库的介绍3.行式或列式存储... 查看详情

基于redis分布式锁实现“秒杀”(代码片段)

最近在项目中遇到了类似“秒杀”的业务场景,在本篇博客中,我将用一个非常简单的demo,阐述实现所谓“秒杀”的基本思路。业务场景所谓秒杀,从业务角度看,是短时间内多个用户“争抢”资源,这里的资源在大部分秒杀... 查看详情

对秒杀场景的学习

当并发量很大时,秒杀的商品的库存已经为零,这个时候如果再去Redis里面查库存,这样就会影响效率1.可以在代码的逻辑上面加一个concurrenthashmap的值,这样就可以对其里面的值做一个判断。2.如果是集群部署,当一个服务器发... 查看详情

redis学习redis事务秒杀案例(代码片段)

文章目录一、前期准备二、开始模拟三、连接池解决超时问题四、乐观锁解决超卖问题五、LUA脚本解决库存遗留问题1.LUA脚本2.LUA脚本在Redis中的优势3.LUA脚本实现一、前期准备为了模拟Redis事务中的并发操作,需要安装工具Cen... 查看详情

redis学习redis事务秒杀案例(代码片段)

文章目录一、前期准备二、开始模拟三、连接池解决超时问题四、乐观锁解决超卖问题五、LUA脚本解决库存遗留问题1.LUA脚本2.LUA脚本在Redis中的优势3.LUA脚本实现一、前期准备为了模拟Redis事务中的并发操作,需要安装工具Cen... 查看详情

redis学习redis分布式锁实现秒杀业务(乐观锁悲观锁)(代码片段)

####1、业务场景所谓秒杀,从业务角度看,是短时间内多个用户“争抢”资源,这里的资源在大部分秒杀场景里是商品;将业务抽象,技术角度看,秒杀就是多个线程对资源进行操作,所以实现秒杀ÿ... 查看详情

redis学习笔记—哨兵(redissentinel)(代码片段)

Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址,对于很多应用场景这种故障处理的方式是无法接受的。Redis从2.8开始正式提供了... 查看详情

redis场景拓展秒杀问题-全局唯一id生成策略(代码片段)

...码(日期)开发全局唯一ID,并进行生成速度测试。每1~2周学习整理redis中的知识点和场景实现,希望有所输入输出,每天进步一点点。全局唯一ID为什么要使用全局唯一ID:当用户抢购时,就会生成订单并保存到订单表中,而订单... 查看详情

redis事务系列之三redis乐观锁实现秒杀(代码片段)

...dis实现乐观锁本章我们继续在上面的基础上用乐观锁实现秒杀。二、秒杀的实现我们先来设置一个场景,假设有50个商品,1000个人抢购。那么最终会有50个人买到商品。在实现上我们配合着线程池来实现。具体代码如下&# 查看详情

redis学习笔记38——通信开销:限制rediscluster规模的关键因素(代码片段)

RedisCluster能保存的数据量以及支撑的吞吐量,跟集群的实例规模密切相关。Redis官方给出了RedisCluster的规模上限,就是一个集群运行1000个实例。为何要限制集群规模呢?因为,实例间的通信开销会随着实例规模增... 查看详情

redis学习笔记38——通信开销:限制rediscluster规模的关键因素(代码片段)

RedisCluster能保存的数据量以及支撑的吞吐量,跟集群的实例规模密切相关。Redis官方给出了RedisCluster的规模上限,就是一个集群运行1000个实例。为何要限制集群规模呢?因为,实例间的通信开销会随着实例规模增... 查看详情