目录
引出
1.Lua脚本,基础语法,去库存脚本编写;
2.SpringBoot整合Lua脚本,配置类RedisScript<Long>;
3.调用lua脚本,execute(redisScript, keys, wanted);
4.加锁和未加锁的测试,Redission锁的应用;
Lua脚本及其应用
Lua语法
Lua简化语法
local ——>定义变量(var let)
KEYS[1] ——>数组 从一开始 (key, field)
ARGV[1] —->Redis值
tonumber—->将字符串转换为数值
redis.call()
if then
end
Lua脚本
以商品库存为例
定义商品库存信息
设置图书的redis存储方式: hash
编写Lua脚本(去库存-1)
将书去(减少)库存放入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
Redis+Lua脚本项目应用(三):抢购图书
3.0版本:redis+Lua,数据不安全
1.在resources下新建lua脚本
将书去(减少)库存放入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
2.写lua脚本的配置类
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;
}
}
3.controller层调用lua脚本
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);
}
}
4.进行测试
(1)client方法测试;
(2)JMeter进行高并发测试
dto层的响应
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;
}
}
4.0版本:Redis+Lua+Redission
1.采用redission加锁
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(); // 把锁释放
}
}
}
2.进行测试
总结
1.Lua脚本,基础语法,去库存脚本编写;
2.SpringBoot整合Lua脚本,配置类RedisScript<Long>;
3.调用lua脚本,execute(redisScript, keys, wanted);
4.加锁和未加锁的测试,Redission锁的应用;
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/165097.html