Lua script attempted to access a non local key in a cluster node

你好,我是猿java。

使用

业务场景是电商库存计算,公司使用的是redis cluster集群,因为涉及到一些简单的库存计算,为了保证原子性,特使用了lua脚本,lua的函数脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- type都是java代码中传入的String值,sku为Long型
local function availableRealSaleCal(type,sku)
local key = formatKey(type, sku)
-- 销售库存 = (if 可售卖量 then 销售库存 = min(可售库存,可售卖量)else 销售库存 = 可售库存 end)
local availableRealSale = 0;
local availableSale = redis.call('INCRBY', key..":AVAILABLE_SALE", 0);
local saleLimit = redis.call('HGET', key, 'sale_limit');
redis.call('SET', stocksKey .. ":AVAILABLE_REAL_SALE", availableRealSale);
return availableRealSale
end

--库存key
local function formatKey(type, sku)
return "stock:"..type..":"..":{"..sku.."}"
end;

说明

在上面的lua脚本中,有{sku}这样的用法,{}是在redis cluster里面的特有的hash tags,具体使用可以参考redis的官方文档:Redis hash tags文档,
Redis hash slot计算

疑问

代码上线半年没有出现过问题,怎么突然会出现运营无法增加库存,特查看了一下服务器的错误日志,日志详情如下:

1
2
3
4

Caused by: redis.clients.jedis.exceptions.JedisDataException: ERR Error running script (call to f_1fbde7f097d74a7d77c854c93b308d36d164dbf9): @user_script:371: @user_script: 371: Lua script attempted to access a non local key in a cluster node
at redis.clients.jedis.Protocol.processError(Protocol.java:115)

看到这个错误,一脸懵,第一次遇到,于是google了一下,找到几个类似的问题,大致意思也差不多,下面给出一个stackover上面的例子,链接如下:stackoverflow相同的错误
lua代码摘取如下:

1
2
3
local f3=redis.call('HGET',KEYS[1],'1');
local f4=redis.call('HGET',f3,'1') ;
return f4;

错误解释是在lua中,执行多条语句,要保证key hash的slot是同一个,否则就会出现上面的错误,比如:KEYS[1]和 f3 hash后不在同一个slot就会出现上述错误。

解决思路

顺着 google的那个例子,反观我们的lua脚本,整个lua都是根据{sku}来做hash,然后{sku}的结果不稳定,导致几条语句执行的时候hash到不同的slot中? 顺着这个思路,最后发现:线上跑的代码,sku都是传的14位的Long,没有出现问题,现在传入的sku是15位的Long出现问题,是sku的长度导致{sku}结果值不稳定,最后在中间部门同时的配合下,找到了中间件的执行log:stockskey:stock:40-248-000008:{1.112422310001e+14},太奇怪了,sku传入的明明是一个Long的类型,怎么变成{1.112422310001e+14}这个奇怪的内容,
原来在中间件有个cjson的操作,当传入的Long类型位数大于14时,会把Long会转成科学计数法,真实要hash的key变了,导致{sku}计算不稳定, 最后把Long转String问题解决

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。

drawing