一、引入相关依赖
可以新建Spring或Maven工程,在pom文件中引入Jedis依赖:
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
二、核心代码
SecKillDemo
package com.redis;
import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class SecKillDemo implements Runnable {
//这里需要更改成自己的Redis服务器地址
private Jedis jedis = new Jedis("10.128.*.*", 6379);
//顾客
private String customerName;
//商品
private String key;
public SecKillDemo1(String customerName, String key) {
this.customerName = customerName;
this.key = key;
}
@Override
public void run() {
boolean success = false;
String data;
int currentNum;
while (!success) {
//可重复抢购直到成功
//通过watch实现redis的incr(原子递增操作)
jedis.watch(key);
data = jedis.get(key);
//获取剩余商品数
currentNum = Integer.parseInt(data);
if (currentNum > 0) {
//开启事务
Transaction transaction = jedis.multi();
//设置新值,如果key的值被其它连接的客户端修改,那么当前连接的exec命令将执行失败
//方式一 ,在事务中设置data的值为原值减1,此时transaction.exec()的返回值的第一个元素是"OK"
currentNum--;
transaction.set(key, String.valueOf(currentNum));
//方式二,在事务中对键data对应的值做减1操作,此时transaction.exec()的返回值的第一个元素是data对应的当前值
//transaction.decrBy(key, Integer.valueOf(currentNum--));
List res = transaction.exec();
if (res.size() == 0) {
String failUserInfo = "fail---" + customerName;
String failMsg = failUserInfo + ",抢购失败,剩余商品数量:"+ currentNum ;
// 将秒杀失败的用户信息存入Redis。
jedis.setnx(failUserInfo, failMsg);
System.out.println(customerName + " 抢购失败");
} else {
success = true;
System.out.println(customerName + " 抢购成功,[" + key + "]剩余:" + currentNum);
String succUserInfo = customerName + " 抢购成功,[" + key + "]";
String succMsg = succUserInfo + ",抢购成功," + "剩余商品数量:"+currentNum;
System.out.println(res.get(0)+ " " + succMsg);
// 将秒杀成功的用户信息存入Redis。
jedis.setnx(succUserInfo, succMsg);
}
} else {
System.out.println("商品售空,活动结束!");
System.exit(0);
}
}
}
}
三、单元测试
SecKillDemoTest
package com.redis;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Before;
import org.junit.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import redis.clients.jedis.Jedis;
public class SecKillDemoTest {
//商品名称
private static String key = "华为P40";
//商品数量
private static String num = "50";
// 模拟用户抢购最大并发数
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
@Before
public void before() {
Jedis jedis = new Jedis("10.128.*.*");
//命令包括set、get、del等
//eval代表执行Lua语言的命令
//KEYS[1]代表传递给Lua脚本的第一个key参数,
//ARGV[1]代表第一个非key参数
String script = "redis.call('del',KEYS[1]);return redis.call('set',KEYS[1],ARGV[1])";
jedis.eval(script, Collections.singletonList(key), Collections.singletonList(num));
jedis.close();
}
@Test
public void test() {
}
public static void main(String[] args) {
try{
for (int i = 1; i <= 100; i++) {
executorService.submit(new SecKillDemo("顾客"+i,key));
}
}catch (Exception e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
执行步骤:
(1)执行junit单元测试方法,在redis中构建<key,value>数据。
(2)执行SecKillDemoTest 中的main方法。
执行结果如下:
从redis中查看结果如下:
代码下载地址
https://gitee.com/codefarmer001/spring-redis.git
参考文章:
使用Redis中间件解决商品秒杀活动中出现的超卖问题(使用Java多线程模拟高并发环境)
redis watch命令实现秒杀
Redis常用技术-----使用Lua语言