强调:当你打算在本地测试redis的某个功能的时候,你必须保证你本地的springboot版本、redis版本(两个:pom中引入的依赖版本,本地redis-ser的版本)和你公司项目中的版本保持一致。因为不同版本,差别挺大的,往往可能因为版本问题导致两种不同的结果。
我们在使用redis的lua脚本功能来实现库存扣减,可以先参考下面的博客:
基于redis实现的扣减库存_JackieZhengChina的博客-CSDN博客_redis减库存
首先,该博客代码是完全可以实现扣减库存的。其次有个坑需要留意一下:
1、博客只字未提springboot的版本。我是下载了源码,然后看了一下springboot的版本发现1.5.13.RELEASE的版本,该版本默认的redis客户端连接是jedis;所以使用这个版本来测试功能是没有问题的; 但是,现在springboot最新版本是2.7的了,而springboot2.x以上版本,默认的redis客户端是lettuce, 导致的结果是,你上面的代码总是执行失败,原因是代码都是对jedis进行判断而没有对lettuce进行判断。
解决方式:(代码不需要动,只需要动pom)
a:代码不做修改,把springboot2.x以上的版本降低到2.x以下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cms</groupId>
<artifactId>stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-stock</name>
<description>redis-stock</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
// springboot2.x以下,默认已经导入该依赖
//</dependency>
//<dependency>
// <groupId>redis.clients</groupId>
// <artifactId>jedis</artifactId>
//</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
--不推荐,因为大部分公司使用的是2.x以上的版本了,不能因为你这个需求降低版本。
b:代码不做修改,springboot2.x不动,修改默认的redis客户端(将lettuce依赖排除,那么就会使用jedis依赖)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cms</groupId>
<artifactId>stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-stock</name>
<description>redis-stock</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
<version>2.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
--不推荐。因为有的公司就是使用lettuce作为默认redis的客户端。
总结:
虽然上面博客的代码可行,但是不建议使用。
建议使用StringRedisTemplate来执行lua脚本,好处是我不需要考虑我的redis客户端到底是使用jedis还是lettuce(换言之,我不需要考虑springboot的版本)。
---注意:此处说使用的是StringRedisTemplate,不要用RedisTemplate。因为由于他俩序列化的方式不同,导致一模一样的lua脚本,StringRedisTemplate可能执行成功而RedisTemplate执行不成功。比如:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cms</groupId>
<artifactId>stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-stock</name>
<description>redis-stock</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
<version>2.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private StringRedisTemplate stringRedisTemplate;
private String subStock="local key=KEYS[1];\n" +
"local subNum = tonumber(ARGV[1]) ;\n" +
"local surplusStock = tonumber(redis.call('get',key));\n" +
"if (surplusStock<=0) then return 0\n" +
"elseif (subNum > surplusStock) then return 1\n" +
"else\n" +
" redis.call('incrby', KEYS[1], -subNum)\n" +
" return 2 \n" +
"end";
public Long test(String ipAmountKey, Long initAmountValue, Long reduceAmountValue){
//构建redisScript对象,构造方法参数1 执行的lua脚本 参数2 结果返回类型
DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>(subStock,Long.class);
//参数1 redisScript对象 参数2 keys,可以是多个,取决于你lua里的业务, 参数3 args 需要给lua传入的参数 也是多个
Long result = (Long) stringRedisTemplate.execute(defaultRedisScript, Arrays.asList("seckillStock:1594778100813"), "10");
// 执行失败
//Long result = (Long) redisTemplate.execute(defaultRedisScript, //Arrays.asList("seckillStock:1594778100813"), "10");
return result;
}
究其根本原因:大概率是RedisTemplate默认是jdk的序列化方式,而StringRedisTemplate默认是string的序列化方式:RedisTemplate和StringRedisTemplate的区别_大海会笑的博客-CSDN博客_stringredistemplate
再来参考这篇文章(我并未自己尝试):使用RedisTemplate执行lua脚本_Avogrado的博客-CSDN博客_redistemplate执行lua脚本
这篇文章可以使用RedisTemplate执行lua成功,是因为他代码里面配置了RedisTemplate的string序列化,然后才成功的。
再来看这篇文章(我并未自己尝试):
RedisTemplate使用lua脚本_字节抖动的博客-CSDN博客_redistemplate执行lua脚本
这篇文章可以使用RedisTemplate执行lua成功,是因为以文件的形式读取lua脚本。
可参考的lua-初始化库存、操作库存: