一起聊聊Redis实现秒杀的问题

 3248

本篇文章给大家带来了关于Redis的相关知识,其中主要介绍了关于实现秒杀的相关内容,包括了秒杀逻辑、存在的链接超时、超卖和库存遗留的问题,下面一起来看一下,希望对大家有帮助。


一起聊聊Redis实现秒杀的问题


1、秒杀逻辑

秒杀:解决计数器和人员记录的事务操作

1、uid和proid非空判断

2、连接redis

3、拼接key

库存key

秒杀成功用户key

4、获取库存,如果库存为null,秒杀还没开始

5、判断用户是否重复秒杀操作

6、判断商品数量,库存数量小于1,秒杀结束

7、秒杀过程

库存-1

把秒杀成功用户添加清单里面


2、存在问题

2.1、连接超时

原因:由于大量创建连接,十分消耗性能,并且有时获取连接不及时,出现连接超时的情况

2.2、超卖

在并发的情况下发生的,就是在输出没有库存(秒杀结束)后还有商品售出导致库存数量为负数。


一起聊聊Redis实现秒杀的问题


2.3、库存遗留

使用乐观锁解决问题2之后,出现问题3

如果库存数量相对并发更多,由于使用乐观锁,第一个用户秒杀成功后会修改库存键的版本号,其他抢到的用户会因为版本号不同导致无法继续购买,就会有库存遗留问题


3、解决

3.1、连接超时

使用连接池,工具类如下:

  1. public class JedisPoolUtil {
  2.   private static volatile JedisPool jedisPool = null;
  3.   private JedisPoolUtil() {
  4.   }
  5.   public static JedisPool getJedisPoolInstance() {
  6.       if (null == jedisPool) {
  7.           synchronized (JedisPoolUtil.class) {
  8.               if (null == jedisPool) {
  9.                   JedisPoolConfig poolConfig = new JedisPoolConfig();
  10.                   poolConfig.setMaxTotal(200);
  11.                   poolConfig.setMaxIdle(32);
  12.                   poolConfig.setMaxWaitMillis(100 * 1000);
  13.                   poolConfig.setBlockWhenExhausted(true);
  14.                   poolConfig.setTestOnBorrow(true);
  15.                   jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 60000);
  16.               }
  17.           }
  18.       }
  19.       return jedisPool;
  20.   }
  21. }
  22. //使用JedisPool 
  23. jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
  24. Jedis jedis = jedisPoolInstance.getResource();

springBoot版本(pom.xml引入,application.yml配置,然后注入对象即可)

  1. <dependency>
  2.   <groupId>org.springframework.boot</groupId>
  3.   <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6.   <groupId>redis.clients</groupId>
  7.   <artifactId>jedis</artifactId>
  8.   <version>3.2.0</version>
  9. </dependency>
  1. spring:
  2.   redis:
  3.     host: 127.0.0.1    port: 6379
  4.     database: 0
  5.     timeout: 1800000
  6.     lettuce:
  7.       pool:
  8.         max-active: 20
  9.         max-wait: -1
  10.         max-idle: 5
  11.         min-idle: 0
  1. @Autowired
  2. private RedisTemplate redisTemplate;


3.2、超卖问题

使用Redis事务,乐观锁 + watch

  1. //监视库存
  2. jedis.watch(kcKey);//中间代码忽略
  3.  
  4. //7 秒杀过程
  5. //使用事务
  6. Transaction multi = jedis.multi();//组队操作
  7. multi.decr(kcKey);multi.sadd(userKey,uid);//执行
  8. List<Object> results = multi.exec();if(results == null || results.size()==0) {
  9.     System.out.println("秒杀失败了....");
  10.     jedis.close();
  11.     return false;}


3.3、乐观锁导致的库存遗留问题

使用Lua嵌入式脚本语言

将复杂的或者多步的 Redis 操作,写为一个脚本,一次提交给Redis运行,减少反复连接 reids的次数。提升性能。

LUA脚本是类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成redis事务性的操作

LUA脚本功能,在Redis 2.6以上的版本才可以使用

利用 lua 脚本淘汰用户,解决超卖问题。

redis 2.6 版本以后,通过 lua 脚本解决争抢问题,实际上是 redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题

  1. local userid=KEYS[1];               //1、2行定义两个变量,                   
  2. local prodid=KEYS[2];
  3. local qtkey="sk:"..prodid..":qt";   //3,4行定义拼接key
  4. local usersKey="sk:"..prodid..":usr";
  5. local userExists=redis.call("sismember",usersKey,userid); //5-8,判断用户是否存在,不存在return 2
  6. if tonumber(userExists)==1 then
  7.     return2;
  8. end
  9. local num=redis.call("get",qtkey);  //9-11,判断商品是否存在
  10. if tonumber(num)<=0 then
  11.     return 0;
  12. else                                //12-15,用户和商品操作
  13.     redis.call("decr",qtkey);
  14.     redis.call("sadd",usersKey,userid);
  15. end
  16. return1;                            //最后一行return 1;  秒杀成功


完整代码如下:

  1. // 定义两段Lua脚本(使用Lua脚本可以解决乐观锁带来的库存遗留问题)
  2. static String secKillScript =
  3.       "local userid=KEYS[1];\r\n" +
  4.               "local prodid=KEYS[2];\r\n" +
  5.               "local qtkey='sk:'..prodid..\":qt\";\r\n" +
  6.               "local usersKey='sk:'..prodid..\":usr\";\r\n" +
  7.               "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
  8.               "if tonumber(userExists)==1 then \r\n" +
  9.               "   return 2;\r\n" +
  10.               "end\r\n" +
  11.               "local num= redis.call(\"get\" ,qtkey);\r\n" +
  12.               "if tonumber(num)<=0 then \r\n" +
  13.               "   return 0;\r\n" +
  14.               "else \r\n" +
  15.               "   redis.call(\"decr\",qtkey);\r\n" +
  16.               "   redis.call(\"sadd\",usersKey,userid);\r\n" +
  17.               "end\r\n" +
  18.               "return 1" ;
  19. public static boolean doSecKill(String uid,String prodid) throws IOException {
  20.   JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
  21.   Jedis jedis=jedispool.getResource();
  22.   jedis.select(2);
  23.   // 通过jedis的scriptLoad方法加载Lua脚本
  24.   String sha1=  jedis.scriptLoad(secKillScript);
  25.   //通过jedis的evalsha方法调用Lua脚本
  26.   Object result= jedis.evalsha(sha1, 2, uid,prodid);
  27.   String reString=String.valueOf(result);
  28.   if ("0".equals( reString )  ) {
  29.       System.err.println("已抢空!!");
  30.   }else if("1".equals( reString )  )  {
  31.       System.out.println("抢购成功!!!!");
  32.   }else if("2".equals( reString )  )  {
  33.       System.err.println("该用户已抢过!!");
  34.   }else{
  35.       System.err.println("抢购异常!!");
  36.   }
  37.   jedis.close();
  38.   return true;
  39. }


TAG标签:
本文网址:https://www.zztuku.com/detail-12130.html
站长图库 - 一起聊聊Redis实现秒杀的问题
申明:本文转载于《CSDN》,如有侵犯,请 联系我们 删除。

评论(0)条

您还没有登录,请 登录 后发表评论!

提示:请勿发布广告垃圾评论,否则封号处理!!

    编辑推荐