在模拟秒杀第二篇中,我们使用了redis事务,并用watch进行监控,虽然将超卖问题解决了,但是引出了一个新的问题——库存剩余。其中原因就是watch的乐观锁导致的,版本不一致,不执行事务。现在使用另外一个方案(即LUA脚本)进行解决。
LUA脚本是一个嵌入式的脚本,不能独立运行,需要其他的程序进行调用,本身是用C语言进行编写的,调用redis执行具有原子性。
在秒杀过程中,将redis的库存判断,秒杀过程,全部交给脚本实现,java代码中,只做调用
实现代码如下:
import com.lixl.redis.utils.RandomUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.StaticScriptSource;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @author lixl
* @description 秒杀相关controller
* @date 2022/2/10
*/
@RestController
public class SecKillController {
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping("doSecKill")
public String doSeckill(String goodId){
String userId = RandomUtils.getUserId()+"";
if (!StringUtils.hasText(goodId)){
System.out.println("没有该类商品的秒杀");
return "没有该类商品的秒杀";
}
List<String> keys = new ArrayList<>();
keys.add(userId);
keys.add(goodId);
//调用lua脚本并执行
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(Long.class);//返回类型是Long
//lua文件存放在resources目录下的redis文件夹内
redisScript.setScriptSource(new StaticScriptSource(getLuaText()));
Object execute = redisTemplate.execute(redisScript, keys);
System.out.println(execute.toString());
return execute.toString();
}
/**
* 组合lua脚本,执行redis操作
* lua脚本执行redis操作时候,具有原子性,整体操作结束后,才会允许其他线程访问
* @return
*/
private String getLuaText(){
// 定义变量userid设置值为key列表中第一个
String text = "local userid=KEYS[1];\n" +
// 定义变量goodid设置值为key列表中第二个
"local goodid=KEYS[2];\n" +
// 定义变量 kckey 为字符串
"local kckey='good:'..goodid..':kc';\n" +
// 定义变量 userkey 为字符串
"local userkey='good:'..goodid..':user';\n" +
// 利用redis调用查看set集合值方法 sismember
"local userExists=redis.call('sismember',userkey,userid);\n" +
// 判断如果值存在,则返回 2 不存在向下执行
"if tonumber(userExists)==1 then \n" +
" return 2;\n" +
"end \n" +
// 利用redis调用get方法,获取库存数量
"local num=redis.call('get',kckey);\n" +
// 判断库存数量小于 0 则返回 0 标识无法秒杀
"if tonumber(num)<=0 then \n" +
" return 0;\n" +
"else \n" +
// 调用减一方法,对库存量减一操作
" redis.call('decr',kckey);\n" +
// 设置秒杀成功用户id
" redis.call('sadd',userkey,userid);\n" +
"end \n" +
// 秒杀成功 返回1
"return 1";
return text;
};
}
至此完成秒杀问题