Java使用Redis实现秒杀功能

 更新时间:2020年09月29日 14:25:53   作者:楚瑞涛  
这篇文章主要为大家详细介绍了Java使用Redis实现秒杀功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

秒杀功能

秒杀场景现在已经非常常见了,各种电商平台都有秒杀的产品,接下来我们模拟一个秒杀的项目,最终能够确保高并发下的线程安全。界面比较简单,但是功能基本实现。

界面

点击“秒杀点我”按钮后台就会输出秒杀结果。

第一版

使用Redis缓存数据库,使用一个key-value存储秒杀商品数量,使用set集合存储秒杀成功的用户。我们以商品0101为示例,设置商品的初始数量为200件。不考虑并发问题,实现功能。

html、jsp、servlet文件不重要省略。

package com.redis.secondskill;
 
import java.util.List;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
 
public class SS0 {
 public static boolean doSecKill(String uid,String prodid) {
 JedisPool jedisPool = JedisPollTool.getInstance();
 Jedis jedis = jedisPool.getResource();
 String productCountStr = "sec:"+prodid+":count";
 String productUserStr = "sec:"+prodid+":user";
 String productCount = jedis.get(productCountStr);
 if(null == productCount) {
 System.out.println("秒杀还没有开始");
 JedisPollTool.distroy(jedisPool, jedis);
 return false;
 }
 if(jedis.sismember(productUserStr, uid)) {
 System.out.println(uid + "用户已经秒杀成功");
 JedisPollTool.distroy(jedisPool, jedis);
 return false;
 }
 int prodCount = Integer.parseInt(productCount);
 if(prodCount <= 0) {
 System.out.println("秒杀结束");
 JedisPollTool.distroy(jedisPool, jedis);
 return false;
 }
 jedis.decr(productCountStr);
 jedis.sadd(productUserStr, uid);
 
 JedisPollTool.distroy(jedisPool, jedis);
 System.out.println(uid + "秒杀成功");
 return true;
 }
}

使用linux httpd-tools工具进行并发测试。

ab -n 1000 -c 200 -p /test/file.txt -T "application/x-www-form-urlencoded" 192.168.0.101:8080/redis-demo/ss

结果

从结果大致来看,没有什么问题,来查看一个后台Redis的数据

秒杀的结果里面居然有负数,证明卖超了。

第二版

使用Redis的事务,保证没有超卖的情况发生。

package com.redis.secondskill;
 
import java.util.List;
 
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
 
public class SS1 {
 public static boolean doSecKill(String uid,String prodid) {
 JedisPool jedisPool = JedisPollTool.getInstance();
 Jedis jedis = jedisPool.getResource();
 String productCountStr = "sec:"+prodid+":count";
 String productUserStr = "sec:"+prodid+":user";
 jedis.watch(productCountStr); //开始监视
 String productCount = jedis.get(productCountStr);
 if(null == productCount) {
 System.out.println("秒杀还没有开始");
 JedisPollTool.distroy(jedisPool, jedis);
 return false;
 }
 if(jedis.sismember(productUserStr, uid)) {
 System.out.println(uid + "用户已经秒杀成功");
 JedisPollTool.distroy(jedisPool, jedis);
 return false;
 }
 int prodCount = Integer.parseInt(productCount);
 if(prodCount <= 0) {
 System.out.println("秒杀结束");
 JedisPollTool.distroy(jedisPool, jedis);
 return false;
 }
 Transaction transaction = jedis.multi();
 transaction.decr(productCountStr);
 transaction.sadd(productUserStr, uid);
 List<Object> exec = transaction.exec();
 
 
 if(exec == null || exec.size() == 0) {
 System.out.println("秒杀失败,稍后重试");
 JedisPollTool.distroy(jedisPool, jedis);
 return false;
 }
 JedisPollTool.distroy(jedisPool, jedis);
 System.out.println(uid + "秒杀成功");
 return true;
 }
}

结果

由于使用了watch和事务,每次的并发线程访问中只有一个线程能够提交成功,可以保证不出现超卖的现象,但是对于一些用户来说是极其不公平的。

第三版

使用Lua脚本来实现,因为Redis是单线程的,又是C语言编写的,可以使用Lua调用Redis的命令,Lua会具有排他性,所以能够保证安全。

package com.redis.secondskill;
 
import java.util.HashSet;
import java.util.Set;
 
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
 
public class SS2 {
 
 static String luaScript ="local userid=KEYS[1];\r\n" + 
 "local prodid=KEYS[2];\r\n" + 
 "local qtkey='sec:'..prodid..\":count\";\r\n" + 
 "local usersKey='sec:'..prodid..\":user\";\r\n" + 
 "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + 
 "if tonumber(userExists)==1 then \r\n" + 
 " return 2;\r\n" + 
 "end\r\n" + 
 "local num = redis.call(\"get\" ,qtkey);\r\n" + 
 "if tonumber(num)<=0 then \r\n" + 
 " return 0;\r\n" + 
 "else \r\n" + 
 " redis.call(\"decr\",qtkey);\r\n" + 
 " redis.call(\"sadd\",usersKey,userid);\r\n" + 
 "end\r\n" + 
 "return 1" ;
 
 public static boolean doSecKill(String uid,String prodid) {
 JedisPool jedisPool = JedisPollTool.getInstance();
 Jedis jedis = jedisPool.getResource();
 String sha1 = jedis.scriptLoad(luaScript);
 
 Object result= jedis.evalsha(sha1, 2, uid,prodid);
 
 String reString=String.valueOf(result);
 if ("0".equals( reString ) ) {
 System.err.println("已抢空!!");
 }else if("1".equals( reString ) ) {
 System.out.println(uid + "抢购成功!!!!");
 }else if("2".equals( reString ) ) {
 System.err.println("该用户已抢过!!");
 }else{
 System.err.println("抢购异常!!");
 }
 JedisPollTool.distroy(jedisPool, jedis);
 return true;
 
 }
}

结果

这才是我们最希望看到的结果!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Scala方法与函数使用和定义详解

    Scala方法与函数使用和定义详解

    这个章节会很烧脑,需要认真研读,我会尽量写的详细一些。 方法和函数,看似是两个概念,其实他严格来说也是两个概念,但我们大可以理解成是同一个概念,在使用时只有语法上的细微差别,是很类似的,都理解为function即可
    2022-12-12
  • 关于druid连接池的使用详解

    关于druid连接池的使用详解

    文章介绍了Druid连接池的两种使用方式:直接在代码中配置和通过配置文件配置,同时,文章详细讲解了如何在Web项目中启用Druid的监控功能,包括配置web.xml文件、设置过滤器等步骤,最后,文章还提到了Druid提供的加密解密工具,使数据库密码更安全
    2025-02-02
  • 详解java代码中init method和destroy method的三种使用方式

    详解java代码中init method和destroy method的三种使用方式

    这篇文章主要介绍了详解java代码中init method和destroy method的三种使用方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • springboot项目docker分层构建的配置方式

    springboot项目docker分层构建的配置方式

    在使用dockerfile构建springboot项目时,速度较慢,用时比较长,为了加快构建docker镜像的速度,采用分层构建的方式,这篇文章主要介绍了springboot项目docker分层构建,需要的朋友可以参考下
    2024-03-03
  • MyBatis-Plus之逻辑删除的实现

    MyBatis-Plus之逻辑删除的实现

    这篇文章主要介绍了MyBatis-Plus之逻辑删除的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • springboot连接sqllite遇到的坑及解决

    springboot连接sqllite遇到的坑及解决

    这篇文章主要介绍了springboot连接sqllite遇到的坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • 浅析java中String类型中“==”与“equal”的区别

    浅析java中String类型中“==”与“equal”的区别

    这篇文章主要介绍了浅析java中String类型中“==”与“equal”的区别,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • java微信企业号开发之开发模式的开启

    java微信企业号开发之开发模式的开启

    这篇文章主要为大家详细介绍了java微信企业号开发之开发模式的开启方法,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • java查找文件夹下最新生成的文件的方法

    java查找文件夹下最新生成的文件的方法

    在本篇文章中我们给大家分享了关于java怎么查找文件夹下最新生成的文件的相关方法和知识点,有需要的朋友们参考下。
    2019-07-07
  • Java Swing JProgressBar进度条的实现示例

    Java Swing JProgressBar进度条的实现示例

    这篇文章主要介绍了Java Swing JProgressBar进度条的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12

最新评论