Java使用Redis实现秒杀功能
作者:楚瑞涛 发布时间:2023-04-11 11:26:54
标签: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;
}
}
结果
这才是我们最希望看到的结果!
来源:https://blog.csdn.net/cong____cong/article/details/105566983


猜你喜欢
- 在spring Boot中,有些代码是WEB功能,例如API等,但是有些逻辑是非WEB,启动时就要调用并持续运行的,该如何加载自己的非WEB
- B/S 系统中对http 请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验是不可缺
- 本文实例讲述了C# winform实现右下角弹出窗口结果的方法。分享给大家供大家参考,具体如下:using System.Runtime.I
- 这几天恰好和朋友谈起了递归,忽然发现不少朋友对于“尾递归”的概念比较模糊,网上搜索一番也没有发现讲解地完整详细的资料,于是写了这么一篇文章,
- 设置变量nRowNum = 8; % 画布行数nColNum = 9; % 画布列数offset_x = 0;% 红车坐标起点offset_
- 1.选择排序(冒泡排序)升序用第一个元素跟其他元素比较,如果该元素比其他元素,则交换,保证该元素是最小的。然后再用第二个元素跟后面其他的比较
- 一、利用Web服务中的JavaScriptSerializer 类System.Web.Script.Serialization空间,位于S
- 1.背景最近项目中有一个需求需要从用户输入的值找到该值随对应的名字,由于其它模块已经定义了一份名字到值的一组常量,所以想借用该定义。2.实现
- 开发设计搞了一个带圆形进度的进度条,在GitHub上逛了一圈,发现没有,自己撸吧。先看界面效果:主要思路是写一个继承ProgressBar的
- 本文实例为大家分享了android中NFC读写功能的具体代码,供大家参考,具体内容如下首先检查一下设备是否支持NFC功能private vo
- 一、点名器需求:我有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器实现步骤:创建字符缓冲输入流对象创建Arr
- 本文实例为大家分享了C#生成Word文件的具体代码,供大家参考,具体内容如下通过Microsoft.Office.Interop.Word生
- 前言开篇一个例子,我看看都有谁会?如果不会的,或者不知道原理的,还是老老实实看完这篇文章吧。@Slf4j(topic = "c.V
- 导语关于<resultMap>标签映射,<association>&<collection>的使用什么时候用<resultMap>标签映射1
- C#在程序中定义和使用自定义事件可以分为以下几个步骤:步骤1:在类中定义事件using System;public class TestCl
- 昨天下午快下班的时候,无意中听到公司两位同事在探讨批量向数据库插入数据的性能优化问题,顿时来了兴趣,把自己的想法向两位同事说了一下,于是有了
- 本文通过C#程序代码展示如何给PDF文档添加可视化数字签名和不可见数字签名。可视化数字签名,即在PDF文档中的指定页面位置添加签名,包含相关
- 本文实例为大家分享了Android实现蒙板效果的相关代码,供大家参考,具体内容如下1、不保留标题栏蒙板的实现效果:原理:1、弹窗时,设置背景
- 目前为止我们已经了解了如何通过编程创建 CompletableFuture 对象以及如何获取返回值,虽然看起来这些操作已经比较方便,但还有进
- 委托(delegate)是一种可以把引用存储为函数的类型,这类似于c++中的函数指针。回调函数c++中的回调函数,就是用函数指针来实现的。类