最近在做某项目时,遇到了一种场景:从Redis中读取数据,进行数据处理后,删除该数据。乍一看好像没什么问题。但是把该场景扩展到多线程,多应用同时执行时,可能会导致重复读取相同数据,并进行处理的场景。因此,为了实现Redis原子性操作数据,决定引入Redis中的Lua脚本来执行数据读取,数据处理和数据删除操作。
1、Redis Lua脚本介绍
Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放。其设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。因为广泛的应用于:游戏开发、独立应用脚本、Web 应用脚本、扩展和数据库插件等。
使用Lua脚本的好处如下:
(1)、减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
(2)、原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
(3)、复用。客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。
Lua命令不多,主要是下面这几个:
- EVAL
- EVALSHA
- SCRIPT LOAD – SCRIPT EXISTS
- SCRIPT FLUSH
- SCRIPT KILL
相关命令使用可参考:Lua 脚本,Lua教程
本文不详细说明。
2、SpringBoot实现Lua脚本
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
复制代码
- 代码示例
@Autowired
private RedisTemplate<String,Object> redisTemplate;
public List<Object> getDataAndDel(String key, String value) {
//Lua脚本
String redisLua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//Redis key值
String redisKey = "REDIS_KEY_LUA_TEST";
long nowTimeMs = System.currentTimesMillis();
List<String> keys = Collections.singletonList(redisKey);
List<String> argv = Collections.singletonList(String.valueOf(nowTimeMs));
List<Object> datas = redisTemplate.execute(new RedisCallback<Long>() {
@Override
public List<Object> doInRedis(RedisConnection connection) throws DataAccessException {
Object obj = connection.getNativeConnection();
if (obj instanceof JedisCluster) {
// 集群模式
return (List<Object>) ((JedisCluster) obj).eval(redisLua, keys, argv);
} else (obj instanceof Jedis) {
/单机模式,若Redis为集群,则可去掉
return (List<Object>) ((Jedis) obj).eval(redisLua, keys, argv);
}
}
});
return datas;
}
复制代码
说明:Redis Lua脚本最好在Redis命令行里测试正确后,在用java编写执行。
3、参考文档:
RedisTemplate执行lua脚本在Redis集群模式下报错EvalSha is not supported in cluster environment.
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END