语法
#开启事务, 后续指令放入到事务队列中
multi
#提交事务, 执行事务队列中的指令, 结束事务
exec
#回滚事务, 清空事务队列中的指令,结束事务
discard
代码实现1: 成功
注意事项
exec指令
执行之后可能会有多个结果, 要使用集合
来封装.事务冲突问题
悲观锁
乐观锁
总结
需求
分析
准备
set seckill:0101:kc 100
开发步骤
①编写前端代码
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页title>
<script src="script/vue.js">script>
<script src="script/axios.js">script>
head>
<body>
<div id="app">
<button @click="handleSeckill">开始秒杀button>
div>
<script>
Object.assign(window, Vue);
const App = {
setup() {
const data = reactive({
cid: "1001"
})
const handleSeckill = () => {
axios({
method:"post",
url:"/doSeckill",
params:{
cid:data.cid
}
}).then((res) => {
const {msg}=res.data;
alert(msg);
console.log(res.data);
})
}
return {
data,
handleSeckill
}
}
}
Vue.createApp(App).mount('#app');
script>
body>
html>
②编写表现层代码
@Controller
public class SeckillController {
@PostMapping("/doSeckill/{cid}")
@ResponseBody
public ResultVO<Object> doSeckill(@PathVariable String cid){
//执行秒杀
String uid = UUID.randomUUID().toString().replace("-", "");
ResultVO<Object> result= SeckillUtils.doSeckill(uid,cid);
System.out.println("result = " + result);
return result;
}
}
编写jedis.properties配置问价
# redis主机地址
jedis.host=192.168.199.110
# redis端口
jedis.port=6379
# 最大连接数
jedis.maxTotal=1000
# 控制一个pool最多有多少个状态为空闲的jedis实例
jedis.maxIdle=100
# 最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException
jedis.maxWaitMillis=10000
③编写ResultVo.java工具类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ResultVO<T> {
private boolean flag;
private String msg;
private T data;
}
④编写JedisUtils工具类
public class JedisUtils {
private static JedisPool jedisPool;
static {
InputStream inputStream = JedisUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
int maxTotal = Integer.valueOf(properties.getProperty("jedis.maxTotal"));
int maxIdle = Integer.valueOf(properties.getProperty("jedis.maxIdle"));
long maxWaitMillis = Integer.valueOf(properties.getProperty("jedis.maxWaitMillis"));
String host = properties.getProperty("jedis.host");
int port = Integer.valueOf(properties.getProperty("jedis.port"));
//创建Jedis链接池对象
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(maxTotal);
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxWaitMillis(maxWaitMillis);
jedisPool = new JedisPool(
poolConfig,
host,
port
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
public static void close(Jedis jedis) {
if ( null != jedis) {
jedis.close();
jedis = null;
}
}
}
③编写SeckillUtils工具类
public class SeckillUtils {
/**
* 执行秒杀
* @param uid 用户id
* @param cid 商品id
* @return
*/
public static ResultVO<Object> doSeckill(String uid, String cid) {
//1.生成库存key [string:存储简单类型] [set seckill:1001:kc 100]
String kcKey="seckill:"+cid+":kc";
System.out.println("kcKey = " + kcKey);
//2.生成秒杀成功者清单key [set:存储多个结果且去重] [sadd seckill:1001:user user1 user2...]
String userKey="seckill:"+cid+":user";
//3.判断秒杀是否开始
Jedis jedis = JedisUtils.getJedis();
String kcStr = jedis.get(kcKey);
System.out.println("kcStr = " + kcStr);
if(kcStr==null){
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"秒杀未开始,敬请期待!",
null
);
}
//4.判断秒杀是否结束
int kc = Integer.parseInt(kcStr);
if(kc<=0){
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"秒杀已结束!",
null
);
}
//5.判断当前用户是否参加过秒杀
if (jedis.sismember(userKey,uid)) {
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"不能重复参加秒杀活动!",
null
);
}
//6.开始秒杀
//库存减一
jedis.decr(kcKey);
//记录秒杀成功的用户
jedis.sadd(userKey,uid);
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"秒杀成功!",
null
);
}
}
超卖问题
开发步骤
/usr/local/data.txt
①安装ab插件
②创建请求参数文件
cid=1001&
③开始模拟高并发
ab -n 1000 -c 200 -p /usr/local/data.txt -T "application/x-www-form-urlencoded" 192.168.13.54:8080/doSeckill
分析
代码实现
/**
* 解决超卖问题
*/
public class SeckillUtils1 {
/**
* 执行秒杀
* @param uid 用户id
* @param cid 商品id
* @return
*/
public static ResultVO<Object> doSeckill(String uid, String cid) {
//1.生成库存key [string:存储简单类型] [set seckill:1001:kc 100]
String kcKey="seckill:"+cid+":kc";
System.out.println("kcKey = " + kcKey);
//2.生成秒杀成功者清单key [set:存储多个结果且去重] [sadd seckill:1001:user user1 user2...]
String userKey="seckill:"+cid+":user";
//3.判断秒杀是否开始
Jedis jedis = JedisUtils.getJedis();
//4.给库存key加上乐观锁
jedis.watch(kcKey);
String kcStr = jedis.get(kcKey);
System.out.println("kcStr = " + kcStr);
if(kcStr==null){
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"秒杀未开始,敬请期待!",
null
);
}
//4.判断秒杀是否结束
int kc = Integer.parseInt(kcStr);
if(kc<=0){
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"秒杀已结束!",
null
);
}
//5.判断当前用户是否参加过秒杀
if (jedis.sismember(userKey,uid)) {
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"不能重复参加秒杀活动!",
null
);
}
//6.开始秒杀
//开启事务
Transaction multi = jedis.multi();
//库存减一
multi.decr(kcKey);
//记录秒杀成功的用户
multi.sadd(userKey,uid);
//提交事务
List<Object> result = multi.exec();
//判断事务是否执行失败
if ( result==null || result.size()==0) {
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"商品已被抢走!",
null
);
}
JedisUtils.close(jedis);
return new ResultVO<Object>(
false,
"秒杀成功!",
null
);
}
}
概述
概述
开发步骤
①修改SeckillUtils工具类
/**
* LUA脚本解决库存遗留问题
*/
public class SeckillUtils {
//将之前的多次请求,使用LUA脚本拼接成一次请求
static String secKillScript = "local uid=KEYS[1];\r\n" +
"local cid=KEYS[2];\r\n" +
"local qtkey='seckill:'..cid..\":kc\";\r\n" +
"local usersKey='seckill:'..cid..\":usr\";\r\n" +
"local userExists=redis.call(\"sismember\",usersKey,uid);\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,uid);\r\n" +
"end\r\n" +
"return 1";
public static ResultVO<Object> doSeckill(String uid, String prodid) throws IOException {
Jedis jedis = JedisUtils.getJedis();
String sha1 = jedis.scriptLoad(secKillScript);
Object result = jedis.evalsha(sha1, 2, uid, prodid);
String reString = String.valueOf(result);
if ("0".equals(reString)) {
System.out.println("失败!秒杀已经结束!");
JedisUtils.close(jedis);
return new ResultVO<Object>(false, "秒杀已经结束!", null);
} else if ("1".equals(reString)) {
System.out.println("抢购成功!!!!");
} else if ("2".equals(reString)) {
System.out.println("失败!您已经秒杀成功过了!把机会给别人吧!");
JedisUtils.close(jedis);
return new ResultVO<Object>(false, "不能重复秒杀!", null);
} else {
System.err.println("抢购异常!!");
JedisUtils.close(jedis);
return new ResultVO<Object>(false, "抢购异常!", null);
}
JedisUtils.close(jedis);
return new ResultVO<Object>(true, "秒杀成功!", null);
}
}