首页 > 数据库 >【Redis核心知识】实现秒杀的三种方案

【Redis核心知识】实现秒杀的三种方案

时间:2024-04-03 11:12:08浏览次数:24  
标签:库存 return 扣减 Redis goodKey 秒杀 三种 ID

Redis秒杀方案
  Redis性能很好,被大量使用于秒杀场景下,实现秒杀有以下几种方案:

方案一:使用商品ID作为分布式锁,加锁后扣减库存
该方案的实现流程为:

  • 用户发起秒杀请求到Redis,Redis先使用商品ID作为key尝试加锁,保证只有一个用户进入之后流程,保证原子性;
  • 如果加锁成功,则查询库存。如果库存充足,则扣减库存,代表秒杀成功;若库存不足,直接返回秒杀失败;

  实现代码如下:

 1 /**
 2  * Redis秒杀方法一:先加分布式锁,然后查询缓存,根据库存量数量进行后续操作:如果库存量大于零,则扣减库存,返回true;否则返回false;
 3  * @param goodId 商品ID
 4  * @return 成功返回true,失败返回false
 5  */
 6 @Override
 7 public Boolean secKillByRedisFun1(Integer goodId) {
 8     // 根据商品ID构造key
 9     String goodKey = "good-stock-" + goodId;
10     String userId = Thread.currentThread().getName() + "-" + System.currentTimeMillis();
11     // 使用商品作为锁,锁的粒度较大
12     String lockId = "sec-kill-lock-" + goodId;
13     return this.subStock(lockId, userId, goodKey);
14 }
15 
16 /**
17  * 使用分布式锁秒杀,加锁后再查询redis库存,最后扣减库存
18  * @param lockId 锁ID
19  * @param userId 用户ID
20  * @param goodKey 商品ID
21  * @return 秒杀成功返回 true,否则返回 false
22  */
23 private boolean subStock(String lockId, String userId, String goodKey) {
24     // 尝试先加锁,如果加锁成功再进行查询库存量,和扣减库存操作,此时只能有一个线程进入代码块
25     if (redisLock.lock(lockId, userId, 4000)) {
26         try {
27             // 查询库存
28             Integer stock = (Integer) redisTemplate.opsForValue().get(goodKey);
29             if (stock == null) {//商品不在缓存中
30                 return false;31             }
32             // 如果剩余库存量大于零,则扣减库存
33             if (stock > 0) {
34                 redisTemplate.opsForValue().decrement(goodKey);
35                 return true;
36             } else {
37                 return false;
38             }
39         } finally {
40             // 释放锁
41             redisLock.unlock(lockId, userId);
42         }
43     }
44     return false;
45 }

  该方案存在一些缺点:

  • 用户进来后都要抢锁,即便是库存量已经为零,仍然需要抢锁,这无疑带来了很多无用争抢;
  • 锁的是商品ID,锁粒度太大,并发性能可以进一步优化;

  解决方案:

  • 抢锁前先查询库存,如果库存已经为零,则直接返回false,不必参与抢锁过程;
  • 使用商品ID+库存量作为锁,降低锁粒度,进一步提升并发性能;

方案二:使用商品ID+库存量作为分布式锁,加锁后扣减库存
该方案的实现流程为:

  • 用户发起秒杀请求到Redis,Redis先查询库存量,然后根据商品ID+库存量作为key尝试加锁,保证只有一个用户进入之后流程,保证原子性;
  • 如果加锁成功,则查询库存。如果库存充足,则扣减库存,代表秒杀成功;若库存不足,直接返回秒杀失败;

  注意:第一步查询库存量后,可以添加判断库存是否为零的操作,这样就能过滤掉库存为零后的大量请求。

  实现代码如下:

 1 @Override
 2 public Boolean secKillByRedisFun2(Integer goodId) {
 3     // 根据商品ID构造key
 4     String goodKey = "good-stock-" + goodId;
 5     // 查询库存,使用库存量作为锁,细化锁粒度,提高并发量
 6     Integer curStock = (Integer) redisTemplate.opsForValue().get(goodKey);
 7     if (curStock <= 0) {
 8         return false;
 9     }
10     String userId = Thread.currentThread().getName() + "-" + System.currentTimeMillis();
11     String lockId = "sec-kill-lock-" + goodId + "-" + curStock;
12     return this.subStock(lockId, userId, goodKey);
13 }
14 
15 /**
16  * 使用分布式锁秒杀,加锁后再查询redis库存,最后扣减库存
17  * @param lockId 锁ID
18  * @param userId 用户ID
19  * @param goodKey 商品ID
20  * @return 秒杀成功返回 true,否则返回 false
21  */
22 private boolean subStock(String lockId, String userId, String goodKey) {
23     // 尝试先加锁,如果加锁成功再进行查询库存量,和扣减库存操作,此时只能有一个线程进入代码块
24     if (redisLock.lock(lockId, userId, 4000)) {
25         try { // 查询库存
27             Integer stock = (Integer) redisTemplate.opsForValue().get(goodKey);
28             if (stock == null) {//商品不在缓存中
29                 return false;
} 31 // 如果剩余库存量大于零,则扣减库存 32 if (stock > 0) { 33 redisTemplate.opsForValue().decrement(goodKey); 34 return true; 35 } else { 36 return false; 37 } 38 } finally { 39 // 释放锁 40 redisLock.unlock(lockId, userId); 41 } 42 } 43 return false; 44 }

以上两种先加锁再查询库存量扣减库存的方案,是为了保证查询库存扣减库存操作的原子性,也可以使用lua脚本实现这两个操作的原子性,这样就不需要额外维护分布式锁的开销。

方案三:使用INCRDECR原子操作扣减库存

该方案直接使用DECR操作扣减库存,不需要提前查询缓存,代码简洁:

  • 如果返回值大于零,说明库存充足,表示秒杀成功;
  • 如果返回值小于零,说明库存不足,需要使用INCR操作恢复库存,秒杀失败;

实现代码如下:

 1 /**
 2  * Redis 提前缓存数据库库存
 3  * @param goodkey 商品ID 
 4  * @param stockCount 商品库存量
 5 */
 6 public Boolean setRedisRepertory(String goodKey,Long stockCount){ 
 7  redisTemplate.opsForValue().set(goodKey,stockCount);   
 8   return Boolean.TRUE;
 9 }
10 /**
11  * Redis 秒杀方案三:使用原子操作DECR和INCR扣减库存
12  * @param goodId 商品ID
13  * @return
14  */
15 @Override
16 public Boolean secKillByRedisFun3(String goodId) {
17     // 根据商品ID构造key
18     String goodKey = "good-stock-" + goodId;
19     Long stockCount = redisTemplate.opsForValue().decrement(goodKey);
20     if (stockCount >= 0) {
21         return true;
22     } else {
23         // 如果库存不够,则恢复库存
24         redisTemplate.opsForValue().increment(goodKey);
25         return false;
26     }
27 }

 

不足:后期库存为零后,大量请求扣减库存后需要恢复库存,这是一个无用操作。

解决方案:可以提前查询库存,如果库存为零,直接返回false

原文地址:https://blog.csdn.net/qq_43705697/article/details/133685596

标签:库存,return,扣减,Redis,goodKey,秒杀,三种,ID
From: https://www.cnblogs.com/flameHe/p/18112104

相关文章

  • C# Redis简单封闭操作类
    usingStackExchange.Redis;usingSystem;usingSystem.Configuration;usingSystem.Linq;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceCommon.Utils{publicclassRedisUtils{#regionredis连接地址///<summary&g......
  • Redis事件
    前言我们有的时候会去想Redis是如何去处理客户端的请求的.服务端又是如何和客户端进行通信的,又是如何去处理一些定时操作的,这就是我们这篇文章想要了解的东西.而Redis将这些事件分为俩种:文件事件,时间事件文件事件:服务端和客户端之间通过套接字进行通讯,而在这些通讯的......
  • Redis高可用
     持久化:持久化是最简单的高可用方法,主要作用:数据备份,即将数据存储在硬盘保证数据不会因进程退出而丢失 主从模式:主从复制时高可用Redis的基础。主动复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。 哨兵:在主从复制的基础上实现了自动化的故障......
  • leetcode128. 最长连续序列【三种方法; 并查集; hashtable】
    文章目录1O(nlo......
  • Redis
    redis-cliSADDcities1北京上海广州深圳杭州苏州南京成都 redis-cliSADDcities2昆明哈尔滨济南厦门合肥佛山南昌兰州 redis-cliSADDcities3银川丽江保定三亚桂林襄阳redis-cliSMOVEcities2cities1昆明redis-cliSUNIONSTOREcitiescities1ci......
  • MySQL、Redis 和 Zookeeper 实现分布式锁方法及优缺点
    MySQL、Redis和Zookeeper都可以用来实现分布式锁,每种技术都有其特定的实现方法以及各自的优缺点。MySQL分布式锁实现方法在MySQL中实现分布式锁通常涉及到使用数据库表。可以创建一个专用的锁表,并利用行的唯一性(例如利用唯一索引)来实现锁机制。使用基于事务的 FORUP......
  • redis-BitMap(位图)使用方法
    一,BitMap介绍使用位存储,信息状态只有0和1Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。二,应用场景签到统计、状态统计三,命令命令 描述setbitkeyoffsetvalue 为指定key的offset位设置值getb......
  • 非关系型数据库——Redis基本操作
    目录一、Redis数据库常用命令1.Set——存放数据 2.Get——获取数据3.Keys——获取符合条件的键值4.Exists——判断键值是否存在5.Del——删除指定键值6.Type——获取键值对应的类型7.Rename——对已有键值重命名(覆盖)8.Renamenx——对已有键值重命名(不覆盖)9.Dbsize—......
  • redis特殊数据类型-Geospatial(地理位置)用法
    一 Geospatial(地理位置)介绍使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用有效的经度从-180度到180度。有效的纬度从-85.05112878度到85.05112878度。二 Geospatial应用场景        通过georadius就可以完成附近的人功能withcoo......
  • Redis 高可用之持久化
    一.高可用相关知识1.什么是高可用在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量......