lua脚本在redis集群中执行报错--Lua script attempted to access a non local key in a cluster node...
EVAL、EVALSHA命令
Redis從2.6.0版本開(kāi)始提供了eval命令,通過(guò)內(nèi)置的Lua解釋器,可以讓用戶(hù)執(zhí)行一段Lua腳本并返回?cái)?shù)據(jù)。因?yàn)镽edis單線(xiàn)程模型的特點(diǎn),可以保證多個(gè)命令的原子性(因?yàn)樽罱捻?xiàng)目需要用到簡(jiǎn)單的分布式鎖,所以會(huì)用到lua來(lái)釋放鎖)
腳本性能
帶寬優(yōu)化
Redis Cluster 或 阿里云Redis集群版使用注意事項(xiàng)
Redis從3.0開(kāi)始支持了Cluster功能,之前使用eval的時(shí)候可能沒(méi)什么問(wèn)題,但當(dāng)切換成Cluster模式的時(shí)候,可能會(huì)出現(xiàn)一些問(wèn)題:
上面的錯(cuò)誤是因?yàn)镽edis要求單個(gè)Lua腳本操作的key必須在同一個(gè)節(jié)點(diǎn)上,但是Cluster會(huì)將數(shù)據(jù)自動(dòng)分布到不同的節(jié)點(diǎn)(虛擬的16384個(gè)slot,具體看官方文檔),阿里云集群版的官網(wǎng)其實(shí)也有對(duì)應(yīng)說(shuō)明:在Redis集群版實(shí)例中,事務(wù)、腳本等命令要求所有的key必須在同一個(gè)slot中,如果不在同一個(gè)slot中將返回以下錯(cuò)誤信息(:command keys must in same slot)
如何解決?
CLUSTER KEYSLOT key的文檔中提供了解決方法,你需要將把key中的一部分使用{}包起來(lái),redis將通過(guò){}中間的內(nèi)容作為計(jì)算slot的key,類(lèi)似key1{mykey}、key2{mykey}(如果你的key是“REDIS_LOCK_FORPR”,可以講該key的一部分用{}括起來(lái),例如“REDIS_LOCK_{FORPR}”)這樣的都會(huì)存放到同一個(gè)slot中(缺點(diǎn)是不能平滑的過(guò)度老業(yè)務(wù),需要修改原來(lái)使用的key,如果之前的key是統(tǒng)一管理的,也沒(méi)那么麻煩)
官方地址:https://redis.io/commands/cluster-keyslot
// 部分代碼private static final String DISTRIBUTE_LOCK_SCRIPT_UNLOCK_VAL = "if" +" redis.call('get', KEYS[1]) == ARGV[1]" +" then" +" return redis.call('del', KEYS[1])" +" else" +" return 0" +" end";Object eval = 0; List<String> keys = new ArrayList<>(); keys.add(REDIS_LOCK_PREFIX + lockKey); List<String> argv = new ArrayList<>(); argv.add(lockValue); try {// 這里不用指名有幾個(gè)key,jedis內(nèi)部會(huì)根據(jù)keys集合大小來(lái)獲取eval = jedis.eval(DISTRIBUTE_LOCK_SCRIPT_UNLOCK_VAL, keys, argv); } catch (Exception e) {logger.error("解鎖失敗:" + e.getMessage()); } finally {if (jedis != null) {jedis.close();} }集群環(huán)境中 lua 處理
redis 集群中,會(huì)將鍵分配的不同的槽位上,然后分配到對(duì)應(yīng)的機(jī)器上,當(dāng)操作的鍵為一個(gè)的時(shí)候,自然沒(méi)問(wèn)題,但如果操作的鍵為多個(gè)的時(shí)候,集群如何知道這個(gè)操作落到那個(gè)機(jī)器呢?比如簡(jiǎn)單的mget命令,mget test1 test2 test3,還有我們上面執(zhí)行腳本時(shí)候傳入多個(gè)參數(shù),帶著這個(gè)問(wèn)題我們繼續(xù)。
首先用 docker 啟動(dòng)一個(gè) redis 集群,docker pull grokzen/redis-cluster,拉取這個(gè)鏡像,然后執(zhí)行docker run -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 --name redis-cluster-script -e "IP=0.0.0.0" grokzen/redis-cluster啟動(dòng)這個(gè)容器,這個(gè)容器啟動(dòng)了一個(gè) redis 集群,3 主 3 從。
我們從任意一個(gè)節(jié)點(diǎn)進(jìn)入集群,比如redis-cli -c -p 7003,進(jìn)入后執(zhí)行cluster nodes可以看到集群的信息,我們鏈接的是從庫(kù),執(zhí)行set lua fun,有同學(xué)可能會(huì)問(wèn)了,從庫(kù)也可以執(zhí)行寫(xiě)嗎,沒(méi)問(wèn)題的,集群會(huì)計(jì)算出 lua 這個(gè)鍵屬于哪個(gè)槽位,然后定向到對(duì)應(yīng)的主庫(kù)。
執(zhí)行mset lua fascinating redis powerful,可以看到集群反回了錯(cuò)誤信息,告訴我們本次請(qǐng)求的鍵沒(méi)有落到同一個(gè)槽位上
(error) CROSSSLOT Keys in request don't hash to the same slot同樣,還是上面的 lua 腳本,我們加上集群端口號(hào),執(zhí)行redis-cli -p 7000 --eval /tmp/limit_fun.lua limit_vgroup 192.168.1.19 , 10 3 1548660999,一樣返回上面的錯(cuò)誤。
針對(duì)這個(gè)問(wèn)題,redis官方為我們提供了hash tag這個(gè)方法來(lái)解決,什么意思呢,我們?nèi)℃I中的一段來(lái)計(jì)算 hash,計(jì)算落入那個(gè)槽中,這樣同一個(gè)功能不同的 key 就可以落入同一個(gè)槽位了,hash tag 是通過(guò){}這對(duì)括號(hào)括起來(lái)的字符串,比如上面的,我們改為mset lua{yes} fascinating redis{yes} powerful,就可以執(zhí)行成功了,我這里 mset 這個(gè)操作落到了 7002 端口的機(jī)器。
同理,我們對(duì)傳入腳本的鍵名做 hash tag 處理就可以了,這里要注意不僅傳入鍵名要有相同的 hash tag,里面實(shí)際操作的 key 也要有相同的 hash tag,不然會(huì)報(bào)錯(cuò)Lua script attempted to access a non local key in a cluster node,什么意思呢,就拿我們上面的例子來(lái)說(shuō),執(zhí)行的時(shí)候如下所示,可以看到,前面的兩個(gè)鍵都加了 hash tag —— yes,這樣沒(méi)問(wèn)題,因?yàn)槟_本里面只是用了一個(gè)拼接的 key —— limit_vgroup{yes}_192.168.1.19{yes}。
redis-cli -c -p 7000 --eval /tmp/limit_fun.lua limit_vgroup{yes} 192.168.1.19{yes} , 10 3 1548660999如果我們?cè)谀_本里面加上redis.call("GET", "yesyes")(別讓這個(gè)鍵跟我們拼接的鍵落在一個(gè)solt),可以看到就報(bào)了上面的錯(cuò)誤,所以在執(zhí)行腳本的時(shí)候,只要傳入?yún)?shù)鍵、腳本里面執(zhí)行 redis 命令時(shí)候的鍵有相同的 hash tag 即可。
另外,這里有個(gè) hash tag 規(guī)則:
鍵中包含{字符;建中包含{字符,并在{字符右邊;并且{,}之間有至少一個(gè)字符,之間的字符就用來(lái)做鍵的 hash tag。
所以,鍵limit_vgroup{yes}_192.168.1.19{yes}的 hash tag 是 yes。foo{}{bar}鍵的 hash tag就是它本身。foo{{bar}}鍵的 hash tag 是 {bar。
總結(jié)
- redis集群版的lua腳本,可以通過(guò)key的部分字符串hash來(lái)解決
- redis集群版的分布式是會(huì)根據(jù)KEY進(jìn)行hash取模然后打到不同的slot,這種思想是典型的分而治之。分治,分流,降級(jí)。
思考
如果某個(gè)業(yè)務(wù)都通過(guò)key{mykey}去儲(chǔ)存獲取內(nèi)容,所有的操作都會(huì)hash到同一個(gè)slot,這個(gè)slot所在的節(jié)點(diǎn)壓力就會(huì)變大(不均衡),如果解決?
總結(jié)
以上是生活随笔為你收集整理的lua脚本在redis集群中执行报错--Lua script attempted to access a non local key in a cluster node...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: transient-java 关键字
- 下一篇: 如何动态添加修改删除定时任务