Redis Lua 总结
版本:version 2.6.0及以上
参考连接:http://redis.io/commands/eval
使用脚本的好处:
- 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延
- 原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
- 复用。客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑
使用脚本的限制:
1、不支持集群
All Redis commands must be analyzed before execution to determine which keys the command will operate on. In order for this to be true forEVAL, keys must be passed explicitly. This is useful in many ways, but especially to make sure Redis Cluster can forward your request to the appropriate cluster node.
at the cost of writing scripts not compatible with Redis Cluster.
原子操作。运行脚本的时候redis只会单独运行此脚本,不会运行其他脚本和执行其他redis命令。如果脚本执行很慢就会影响redis的高响应。
调用Lua脚本的语法:
$ redis-cli --eval path/to/redis.lua numberkeys KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...
- --eval,告诉redis-cli读取并运行后面的lua脚本
- path/to/redis.lua,是lua脚本的位置,也可以直接为脚本字符串。是一个Lua 5.1 script。
- numberkeys ,指定后续参数有几个key。
- KEYS[1] KEYS[2],是要操作的键,可以指定多个,在lua脚本中通过KEYS[1], KEYS[2]获取
- ARGV[1] ARGV[2],参数,在lua脚本中通过ARGV[1], ARGV[2]获取。
Lua脚本的简单实例:
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
Lua脚本的调用redis命令实例:
redis.call()
redis.pcall()
> eval "return redis.call('set','foo','bar')" 0
OK
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
call与pcall基本上一样。脚本报错时,call会直接报错,pcall不会报错,会把错误信息放到lua table 的err字段中。
Lua脚本与redis数据类型转换:
In other words there is a one-to-one conversion between Lua and Redis types. The following table shows you all the conversions rules:
Redis to Lua conversion table.
- Redis integer reply -> Lua number
- Redis bulk reply -> Lua string
- Redis multi bulk reply -> Lua table (may have other Redis data types nested)
- Redis status reply -> Lua table with a single
ok
field containing the status - Redis error reply -> Lua table with a single
err
field containing the error - Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type
Lua to Redis conversion table.
- Lua number -> Redis integer reply (the number is converted into an integer)
- Lua string -> Redis bulk reply
- Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
- Lua table with a single
ok
field -> Redis status reply - Lua table with a single
err
field -> Redis error reply - Lua boolean false -> Redis Nil bulk reply.
- Lua boolean true -> Redis integer reply with value of 1.
- lua中number数据类型,在integer和float之间没有区别,都会返回integer【会去掉小数】。如果想要float的话,请返回string,redis自己也是这样做的【如zscore命令】
存在nil和float的情况
> eval "return {1,2,3.3333,'foo',nil,'bar'}" 0
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) "foo"
Lua脚本redis命令:eval和evalSHA,其实客户端实现都是evalSHA,即使客户端显示的执行eval。
evla每次执行时需要不断的发送脚本到server端,而evalSHA是server记住一个SHA1【缓存】,就直接执行。如果没有记住则返回一个特殊错误,使客服端使用eval代替。
redis中的lua脚本名利:
1、SCRIPT FLUSH 唯一强制redis清除脚本缓存的方法。
2、SCRIPT EXISTS sha1 sha2 ... shaN 返回SHA1 是否存在redis脚本缓存中
3、SCRIPT LOAD script 加载脚本,在不执行脚本的情况下确保evalSHA不会失败
4、SCRIPT KILL 打断执行时间长的脚本【因为是原子操作,所以不会对其他的动作造成影响】
Lua脚本redis JAVA调用:
public class RedisLuaTest {
/**
* 数据: user_001 age 1 user_002 age 2
* 脚本功能:获取arg[1]字段大于arg[2]参数的key。
* 测试使用前先将类上面的注释放开【@RunWith和@ContextConfiguration】
* author liuzd
*/
// public static final String SCRIPT="local resultKeys={};for k,v in ipairs(KEYS) do local tmp = redis.call('hget', v, ARGV[1]);if tmp > ARGV[2] then table.insert(resultKeys,v);end;end;return resultKeys;";
public static final String SCRIPT="local resultKeys={}; local tmp = redis.call('hget', 'user_001', ARGV[1]); table.insert(resultKeys,'user_001');return resultKeys;";
@Autowired
private ICache cache;
@SuppressWarnings("unchecked")
@Test
public void test(){
Jedis jedis = cache.getResource().getJedis();
// jedis.auth(auth); //后续redis配置为需要密码访问时。做设置。
String[] allUserKeys={};
// String[] allUserKeys={"user_001","user_002"};
List<String> keys = Arrays.asList(allUserKeys);
List<String> params = new ArrayList<String>();
// params.add("age");
// params.add("1");
String shaFuncKey = jedis.scriptLoad(SCRIPT);//加载脚本,
List<String> resultKeys=new ArrayList<String>();
try {
resultKeys = (List<String>)jedis.evalsha(shaFuncKey, keys, params);
} catch (Exception e) {
e.printStackTrace();
System.out.println("lua 脚本执行报错:"+e);
}
for (String str : resultKeys) {
System.out.println(str);
}
}
}暂时redis脚本先研究到这。