1.Lua脚本,基础语法,去库存脚本编写;
2.SpringBoot整合Lua脚本,配置类RedisScript
3.调用lua脚本,execute(redisScript, keys, wanted);
4.加锁和未加锁的测试,Redission锁的应用;
https://www.lua.org/
https://www.dba.cn/book/lua/
Lua简化语法
local ——>定义变量(var let)
KEYS[1] ——>数组 从一开始 (key, field)
ARGV[1] —->Redis值
tonumber—->将字符串转换为数值
redis.call()
if then
end
以商品库存为例
设置图书的redis存储方式: hash
将书去(减少)库存放入lua脚本。
- 检查数量
- 判断数量是否大于0
- 做-1操作
-- 图书去库存放到lua脚本中
-- 传的参数:物品,数量,购买数量
-- 采用redis的hash数据结构
-- 1.首先定义变量
local book = KEYS[1] -- 获取第一个key,物品
local amount = KEYS[2] -- 获取第二个key,当前物品的数量
local wanted = tonumber(ARGV[1]) -- 获取要减少的库存,即要订购的数量
-- 2.判断产品是否存在,book amount 函数:HEXISTS key field
-- 如果存在返回integer 1,不存在返回0
local isExists = tonumber(redis.call("HEXISTS",book,amount))
-- 判断key,field是否存在
if isExists == 1 then
-- key 和 field field 存在
-- 获取图书数量,判断是否大于0,且减库存后也大于0,hget book amount
local currVal = tonumber(redis.call("hget",book,amount))
-- 判断是否大于0,且减库存后也大于0
if currVal>0 and currVal-wanted >= 0 then
-- 进行去库存,将数量减少wanted,hincrby book amount -1
redis.call("hincrby",book,amount,0-wanted)
return 8 -- 去库存成功
else
return 4 -- 因为库存不足,导致失败
end
else
return 9 -- key 和 field是否存在
end
将书去(减少)库存放入lua脚本。
- 检查数量
- 判断数量是否大于0
- 做-1操作
-- 图书去库存放到lua脚本中
-- 传的参数:物品,数量,购买数量
-- 采用redis的hash数据结构
-- 1.首先定义变量
local book = KEYS[1] -- 获取第一个key,物品
local amount = KEYS[2] -- 获取第二个key,当前物品的数量
local wanted = tonumber(ARGV[1]) -- 获取要减少的库存,即要订购的数量
-- 2.判断产品是否存在,book amount 函数:HEXISTS key field
-- 如果存在返回integer 1,不存在返回0
local isExists = tonumber(redis.call("HEXISTS",book,amount))
-- 判断key,field是否存在
if isExists == 1 then
-- key 和 field field 存在
-- 获取图书数量,判断是否大于0,且减库存后也大于0,hget book amount
local currVal = tonumber(redis.call("hget",book,amount))
-- 判断是否大于0,且减库存后也大于0
if currVal>0 and currVal-wanted >= 0 then
-- 进行去库存,将数量减少wanted,hincrby book amount -1
redis.call("hincrby",book,amount,0-wanted)
return 8 -- 去库存成功
else
return 4 -- 因为库存不足,导致失败
end
else
return 9 -- key 和 field是否存在
end
package com.tianju.redisDemo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
/**
* 运行lua脚本的配置类
*/
@Configuration
public class RedisConfig {
@Bean // 别人写的类方容器中
public RedisScript<Long> redisScript(){
DefaultRedisScript redisScript = new DefaultRedisScript<>();
redisScript.setResultType(Long.class);
// lua脚本的位置
redisScript.setLocation(
new ClassPathResource("/lua/book-unstock.lua") // 关联lua脚本
);
return redisScript;
}
}
package com.tianju.redisDemo.controller;
import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/api/book")
@Slf4j
public class BookUnStockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// @Autowired
@Resource
private RedisScript<Long> redisScript;
@RequestMapping("/unstock")
public HttpResp unStock(
String book, // hash的key,物品
String amount, // field,字段,当前数量
String wanted // 购买数量
){
List<String> keys = new ArrayList<>();
keys.add(book);
keys.add(amount);
Long v = stringRedisTemplate
.opsForHash()
.getOperations()
.execute(redisScript, keys, wanted);// wanted表示想要的数量
if (v.intValue()==4){
return HttpResp.results(ResultCode.BOOK_RUSH_ERROR,new Date(),"库存不足");
}
if (v.intValue()==9){
return HttpResp.results(ResultCode.BOOK_RUSH_ERROR,new Date(),"key不存在");
}
if (v.intValue()==8){
Object o = stringRedisTemplate.opsForHash().get(book, amount);
log.debug(">>>当前库存:"+o);
return HttpResp.results(ResultCode.BOOK_RUSH_SUCCESS,new Date(),"图书抢购成功");
}
return HttpResp.results(ResultCode.LUA_SCRIPT_ERROR,new Date(),null);
}
}
(1)client方法测试;
(2)JMeter进行高并发测试
HttpResp.java
package com.tianju.redisDemo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HttpResp<T> implements Serializable {
private ResultCode resultCode;
private Date time;
private T result;
public static <T> HttpResp <T> results(
ResultCode resultCode,
Date time,
T results){
HttpResp httpResp = new HttpResp();
httpResp.setResultCode(resultCode);
httpResp.setTime(time);
httpResp.setResult(results);
return httpResp;
}
}
ResultCode.java
package com.tianju.redisDemo.dto;
import sun.dc.pr.PRError;
/**
* 枚举类型,http请求的返回值
*/
public enum ResultCode {
BOOK_RUSH_SUCCESS(20010,"图书抢购成功"),
BOOK_RUSH_ERROR(3001,"图书抢购失败"),
LUA_SCRIPT_ERROR(3002,"Lua脚本操作失败")
;
private Integer code;
private String msg;
private ResultCode(Integer code,String msg){
this.code =code;
this.msg = msg;
}
}
package com.tianju.redisDemo.controller;
import com.tianju.redisDemo.dto.HttpResp;
import com.tianju.redisDemo.dto.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/api/book")
@Slf4j
public class BookUnStockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// @Autowired
@Resource
private RedisScript<Long> redisScript;
@Autowired
private RedissonClient redissonClient;
@RequestMapping("/unstock")
public HttpResp unStock(
String book, // hash的key,物品
String amount, // field,字段,当前数量
String wanted // 购买数量
){
List<String> keys = new ArrayList<>();
keys.add(book);
keys.add(amount);
// 1.获取锁对象
RLock myLock = redissonClient.getLock("myLock");
try {
// 2.准备锁代码,并进行加锁
myLock.lock();
Long v = stringRedisTemplate
.opsForHash()
.getOperations()
.execute(redisScript, keys, wanted);// wanted表示想要的数量
if (v.intValue()==4){
return HttpResp.results(ResultCode.BOOK_RUSH_ERROR,new Date(),"库存不足");
}
if (v.intValue()==9){
return HttpResp.results(ResultCode.BOOK_RUSH_ERROR,new Date(),"key不存在");
}
if (v.intValue()==8){
Object o = stringRedisTemplate.opsForHash().get(book, amount);
log.debug(">>>当前库存:"+o);
return HttpResp.results(ResultCode.BOOK_RUSH_SUCCESS,new Date(),"图书抢购成功");
}
return HttpResp.results(ResultCode.LUA_SCRIPT_ERROR,new Date(),null);
} finally {
// 3.解除锁
myLock.unlock(); // 把锁释放
}
}
}
1.Lua脚本,基础语法,去库存脚本编写;
2.SpringBoot整合Lua脚本,配置类RedisScript
3.调用lua脚本,execute(redisScript, keys, wanted);
4.加锁和未加锁的测试,Redission锁的应用;