你好,我是猿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问题解决