先写lua使用好处
- 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
- 原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
- 复用。客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。
基本使用
1、引入jar包配置序列化(这是随便在网上找的配置)
@Configuration
@EnableCaching //引入缓存
public class RedisConfig {
/**
* RedisTemplate配置
* @param redisConnectionFactory
* @return
*/
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 设置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
Object.class);
ObjectMapper om = new ObjectMapper()
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule()); // new module, NOT JSR310Module;;
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
// key序列化
redisTemplate.setKeySerializer(stringSerializer);
// value序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// Hash key序列化
redisTemplate.setHashKeySerializer(stringSerializer);
// Hash value序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
2、写lua脚本
常见命令,这里就不一一介绍
当 Lua 通过
call()
或pcall()
函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构。同样地,当 Lua 脚本在 Redis 内置的解释器里运行时,Lua 脚本的返回值也会被转换成 Redis 协议(protocol),然后由 EVAL将值返回给客户端。redis.call() 和 redis.pcall() 的唯一区别在于它们对错误处理的不同。
redis客户端使用lua案例,数字 2 指定了键名参数的数量, key1 和 key2 是键名参数,分别使用 KEYS[1] 和 KEYS[2] 访问,而最后的 first 和 second 则是附加参数,可以通过 ARGV[1] 和 ARGV[2] 访问它们。
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
给出一个RedisTemplate中lua的脚本案例。大致意思是:每次执行这个脚本,让本地变量count去自增1。如果count为1时设置KEYS[1]的过期时间为ARGV[1]。最后返回count的值
final String INCR_ID_LUA = "local count = redis.call('incr',KEYS[1])\n" +
"if count == 1 then\n" +
" redis.call('expire',KEYS[1],ARGV[1])\n" +
"end\n" +
"return count";
3、调用
好了,回归RedisTemplate中,同样的,我们可以用现有的api去调用lua脚本。RedisTemplate中有个execute的方法,事例使用这个重载方法。
@Override
public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {
return scriptExecutor.execute(script, keys, args);
}
全实例,解析:脚本中的key[*]对应的execute中的List<K> keys第*个参数,ARGV[*]对应的是Object... args可变参数的第*个参数
private static final String INCR_ID_LUA = "local count = redis.call('incr',KEYS[1])\n" +
"if count == 1 then\n" +
" redis.call('expire',KEYS[1],ARGV[1])\n" +
"end\n" +
"return count";
public Long test(String key, long time) {
DefaultRedisScript<Long> defaultRedisScript= new DefaultRedisScript<>();
defaultRedisScript.setResultType(Long.class);
defaultRedisScript.setScriptText(INCR_ID_LUA);
return redisTemplate.execute(defaultRedisScript,Collections.singletonList(key),time);
}
记录排错
前提:这个方法是我从别的redisUtil里边粘过来的
execute中有个List的参数,原本代码是这样写的
List<String> keys = new ArrayList<>(1);
keys.add(key);
然后就传入到execute中。
在原来redisUtil中是这样的,完全没问题
可是到我这,就变成了这样,有点迷惑啊???
提示泛型要用Object,然后我就把泛型去掉。
这下好了,也不报错了,然后重启运行代码,nice,不负众望
RedisCommandExecutionException: ERR Error running script (call to f_dd847a1fcf31c329aa47a8e785c63857901f477a): @user_script:3: ERR value is not an integer or out of range
这还是类型问题呗,然后就有了把keys变成Collections.singletonList(key)的操作。重启运行正确一气呵成。但是是哪出问题了呢?
抱着试一试的心态,我终于找向了redistemplate。终于!发现了问题所在。
我复制的redisUtil中
private final RedisTemplate<String, Object> redisTemplate;
而我的是
private final RedisTemplate<Object, Object> redisTemplate;
源码里:
对应的
真相了。。。。。。。
只能说码功尚浅,继续加油吧!!!慢慢积累呗。