本文将讲解如何在Redis-cli和RedisTemplate中使用Lua脚本,以及Lua脚本的部分语法知识。
Lua具体语法请看:Lua 教程 | 菜鸟教程 (runoob.com)
介绍
Lua脚本可以确保一系列Redis命令在同一事务中执行,进而保证命令执行的原子性操作(虽然在redis集群中涉及多个槽位的操作可能无法保证原子性,但是只处理单槽位的key时是可以保证原子性的);有时,需要执行一系列Redis操作,这些操作必须按照特定的顺序和逻辑进行,就可以用Lua脚本将这些操作封装成一个单一的原子事务。
redis-cli
命令方式:EVAL "Lua脚本" numkeys key [key ...] arg [arg ...]
- "Lua脚本":替换为您要执行的Lua脚本。
- numkeys:指定Lua脚本中引用的键的数量。
- key [key ...]:指定Lua脚本引用的键。
- arg [arg ...]:指定Lua脚本引用的参数。
参数方式:redis-cli --eval 脚本地址 keys... , args...
示例
(分布式锁中为了保证一个线程只能解除自己的锁,使用setnx命令后的del命令必须判断该锁是否是它自身定下的,以免网络延迟导致del命令删除其他线程定下的锁)
先使用setnx命令,模拟一个线程获得锁:
127.0.0.1:6379> set lock lock-user1 NX EX 30
1
然后使用lua脚本进行释放锁:
127.0.0.1:6379> eval "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock lock-user1
1
该命令中使用redis.call调用get lock并判断返回值是否是ARGV[1],即lock-user1,如果是,说明该锁就是它定下的,就使用del命令解锁并返回值,否则直接返回0说明执行失败。
127.0.0.1:6379> eval "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock lock-user2 0
user2就无法解除该锁。
也可以使用参数方式:
lua.lua脚本文件:
if (redis.call('get',KEYS[1]) == ARGV[1]) then
return redis.call('del',KEYS[1])
else
return 0
end;
执行:
$ redis-cli -a 20030322 --eval /home/chilun/Redis/lua.lua lock , lock-user1
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
(integer) 1
RedisTemplate
常用方法:
public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {
return scriptExecutor.execute(script, keys, args);
}
List keys即key [key ...],Object... args即arg [arg ...]。
示例
同样是模拟分布式锁:
@RestController
public class LockController {
@Autowired
RedisTemplate redisTemplate;
private static final String KEY = "lock";
@GetMapping("/lock")
public boolean lock(@RequestParam String user) {
return redisTemplate.opsForValue().setIfAbsent(KEY, user, 30L, TimeUnit.SECONDS);
}
@GetMapping("/unlock")
public boolean unlock(@RequestParam String user) {
String LuaScript =
"if (redis.call('get',KEYS[1]) == ARGV[1]) then " +
"return redis.call('del',KEYS[1]) " +
"else " +
"return 0 " +
"end";
return (boolean) redisTemplate.execute(new DefaultRedisScript<>(LuaScript, Boolean.class), Arrays.asList(KEY), user);
}
}
user1上锁:
user2无法上锁:
30s后user2可上锁:
30s内user1无法解锁:
30s内user2可解锁: