记坑-Redis集群模式使用Lua脚本的限制

有一种无奈叫测试环境好好的,一到线上就热烈的马,各种问题原形毕露

问题复现:

ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, and KEYS should not be in expression
堆栈信息:
org.springframework.dao.InvalidDataAccessApiUsageException: ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, and KEYS should not be in expression; nested exception is redis.clients.jedis.exceptions.JedisDataException: ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, and KEYS should not be in expression
at org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:69)
at org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:42)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
at org.springframework.data.redis.connection.jedis.JedisConnection.convertJedisAccessException(JedisConnection.java:187)

由于前段时间自己写的redis定长队列使用了lua脚本,测试好好的,线上就报错了,还好不是什么大问题。因为目前测试环境单个实例,线上环境使用的是redis集群模式,所以已经踩了不少坑
起初写的脚本如下:

private static final String LIMIT_OFFER_LUA =
            "local key = KEYS[1]" +
            "local num = tonumber(ARGV[1])" +
            "local val = ARGV[2]" +
            "if (redis.call('llen', key) >= num) then redis.call('rpop', key) end " +
            "redis.call('lpush', key, val)";

其中key和arg都用lua变量,因为好几处使用了,正常来讲申明变量没什么问题,可是阿里集群对Lua脚本进行了限制,再通过错误信息,意思为,key的位置必须为数组,不能使用脚本变量

解决办法

修改lua脚本,去掉key的local变量,改为KEYS[i]数组的形式,将数组直接传入进去
修改脚本如下:

private static final String LIMIT_OFFER_LUA =
            "local num = tonumber(ARGV[1])" +
            "local val = ARGV[2]" +
            "if (redis.call('llen', KEYS[1]) >= num) then redis.call('rpop', KEYS[1]) end " +
            "redis.call('lpush', KEYS[1], val)";

集群中Lua脚本的限制

Redis Cluster对使用Lua脚本增加了一些限制,在此基础上,Redis集群版对使用Lua脚本存在如下额外限制:

  • 小版本限制,若无法执行EVAL的相关命令,并报错ERR command eval not support for normal user时,请升级小版本后重试,具体操作请参见升级小版本。
  • 所有Key必须在一个slot上,否则报错-ERR eval/evalsha command keys must be in same slot\r\n。
    您可以通过CLUSTER KEYSLOT命令获取目标Key的哈希槽(Hash Slot)进行确认。
  • 对单个节点执行SCRIPT LOAD命令时,不保证将该Lua脚本存入至其他节点中。
  • 不支持发布订阅命令,包括PSUBSCRIBE、PUBSUB、PUBLISH、PUNSUBSCRIBE、SUBSCRIBE和UNSUBSCRIBE。
  • 不支持UNPACK函数。

代理模式(Proxy)执行Lua的额外限制

  • 所有key都应该由KEYS数组来传递,redis.call/pcall中调用的Redis命令,key的位置必须是KEYS array,且不能使用Lua变量替换KEYS,否则直接返回错误信息:
  • 调用必须要带有key,否则直接返回错误信息:
  • 不支持在MULTI、EXEC事务中使用EVAL、EVALSHA、SCRIPT系列命令。
  • 不支持在Lua中执行跨Redis节点的命令,例如KEYS、SCAN等。为了保证Lua执行的原子性,Proxy会根据KEYS参数将Lua发送到一个Redis节点执行并获取结果,从而导致该结果与全局结果不一致。

参考链接
https://help.aliyun.com/document_detail/92942.html?spm=a2c4g.92942.0.0.4b7e34c4V35sy0

posted @ 2023-05-26 15:38  木马不是马  阅读(3078)  评论(0编辑  收藏  举报