该栏目会系统的介绍 Redis 的知识体系,共分为相关概念、操作指令、主从复制等模块
文章目录
事务简介
1、概述
- :redis 事务是一个单独的隔离操作,就是用来串联多个命令防止别的命令插队
2、特性
- 单独的隔离操作
- 没有隔离级别
- 不保证原子性
3、相关命令
功能 | 指令 |
---|---|
组队 | multi |
执行 | exec |
取消组队 | discard |
监视 | watch |
取消监视 | unwatch |
秒杀案例
- 连接超时问题:使用连接池解决
- 超卖问题:使用事务和乐观锁
- 遗留库存问题:使用Lua脚本
/**
* 1、使用连接池解决超时问题
* 2、使用事务与乐观锁解决超卖问题,但引入遗留库存问题,用Lua脚本解决
*
* @param productId 商品Id
* @param userId 用户Id
* @return 操作结果
*/
@Test
public boolean secKill(String productId, String userId) {
if (productId == null || userId == null) {
return false;
}
// 定义key
String stockKey = "sk:" + productId + ":stock";
String userKey = "sk:" + productId + ":user";
// 判断秒杀是否开始
final String stock = (String) stringOps.get(stockKey);
if (stock == null) {
System.out.println("秒杀活动还没开始,请等待");
return false;
}
// 判断用户是否已经秒杀过
final Boolean result = Optional.ofNullable(setOps.isMember(userKey, userId)).get();
if (result) {
System.out.println("用户已经参加过秒杀了");
return false;
}
// 判断秒杀是否结束
if (Integer.parseInt(stock) <= 0) {
System.out.println("秒杀结束...");
return false;
}
// 监视库存,使用乐观锁机制
redisTemplate.watch(stockKey);
// 开启事务
redisTemplate.multi();
stringOps.decrement(stockKey);
setOps.add(userKey, userId);
final List<Object> results = redisTemplate.exec();
if (results.size() == 0) {
System.out.println("秒杀失败");
return false;
}
System.out.println("秒杀成功");
return true;
}
/**
* LUA脚本概述:将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接
* redis的次数。提升性能。LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完
* 成一些redis事务性的操作
*
* 使用Lua脚本解决遗留库存问题
*
* @param productId 商品Id
* @param userId 用户Id
* @return 操作结果
*/
@Test
public boolean secKillScript(String productId, String userId) {
String secKillScript = "local userid=KEYS[1];\r\n" +
"local prodid=KEYS[2];\r\n" +
"local qtkey='sk:'..prodid..\":qt\";\r\n" +
"local usersKey='sk:'..prodid..\":usr\";\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";
// 定义key
String stockKey = "sk:" + productId + ":stock";
String userKey = "sk:" + userId + ":user";
// 执行Lua脚本
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(secKillScript);
redisScript.setResultType(Long.class);
final long result = Optional.ofNullable(redisTemplate.execute(
redisScript, Arrays.asList(userId, productId))).get();
if (0 == result) {
System.out.println("秒杀活动结束");
return false;
} else if (1 == result) {
System.out.println("秒杀成功");
return true;
} else if (2 == result) {
System.out.println("用户已经参加过秒杀了");
return false;
} else {
System.out.println("秒杀异常!");
return false;
}
}