在处理高并发业务数据的时候,经常把使用REDIS作为数据载体,以提高接口响应速度。在复杂业务场景下,一次请求可能会涉及大量的redis操作,为了保证数据一致性,再使用MULTI/EXEC命令就不太合适了。2.6版本后,REDIS开始支持LUA脚本(将客户端的LUA脚本存入REDIS服务端,然后再执行)在REDIS内部保证脚本操作数据时的一致性。
redis执行 lua命令
redis主要通过eval和evalsha两个命令执行lua脚本:
eval命令格式如下
EVAL script numkeys key [key ...] arg [arg ...]
script是一段LUA脚本的字符串;
evalsha命令要配合script load命令先将脚本上报到REDIS服务端后,才能正常使用,其参数格式类似eval命令
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
其中sha1是script load命令返回的脚本sha1值,可以惟一指向服务端的一段脚本;执行evalsha命令前最好通过SCRIPT EXISTS先判断对应的脚本是否存在
譬如springboot中redistemplate如下
DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(List.class);
redisScript.setScriptText("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}");
List<String> keys = Arrays.asList("hello", "world");
List result = (List) redisTemplate.execute(redisScript, keys, "li lei", "han mei mei");
return result;
redistemplate会重复利用脚本的sha1值,避免每次执行时重新提交脚本到服务器,参见DefaultScriptExecutor
protected <T> T eval(RedisConnection connection, RedisScript<T> script, ReturnType returnType, int numKeys, byte[][] keysAndArgs, RedisSerializer<T> resultSerializer) {
Object result;
try {
result = connection.evalSha(script.getSha1(), returnType, numKeys, keysAndArgs);
} catch (Exception var9) {
if (!ScriptUtils.exceptionContainsNoScriptError(var9)) {
throw var9 instanceof RuntimeException ? (RuntimeException)var9 : new RedisSystemException(var9.getMessage(), var9);
}
result = connection.eval(this.scriptBytes(script), returnType, numKeys, keysAndArgs);
}
return script.getResultType() == null ? null : this.deserializeResult(resultSerializer, result);
}
redis lua脚本说明
1. 使用KEYS[] ARGV[]代表传入的keys和args,下标从1开始;脚本中使用redis.call关键字执行redis命令,譬如redis.call('set', KEY[1], ARGV[1])
1. lua脚本将多个reids操作作为一个整体,并能执行一些简单的计算比较逻辑,跟单个redis命令一样,都是原子操作
2.对已经开始执行的lua脚本无法被中断
3. redis分片时,lua脚本会在某些分片上执行,所以执行前要确认所有的key值对应的数据是否都在一个分片上
4. lua脚本会存在redis服务端,可以通过SCRIPT FLUSH命令清除
参考 redis 脚本