redis lua 性能优化

写在前面的话


本文不是对redis-lua运行环境的优化,而是针对日常开发过程中,具体的业务而进行的优化。

redis lua 脚本优化场景

  • 优点:redis-lua 是对redis 指令的扩充,同时保证脚本执行的原子性。
  • 缺点:在集群环境下需要hashtag的方式,将访问的数据放在同一个节点上,
    至于hashtag的设置简单来说就是在原始key上使用{}
结合一个具体的案例,我们来分析下:

案例

首先,将业务抽象,要保存一棵目录树到redis,比如说人员组织架构(我们不是搞用户中心的),
一种做法是将数据打平,放到redis中 eg:
XX集团/YY分公司/ZZ子公司,一种是使用hash结构 eg:XX集团:{YY分公司:ZZ子公司}

其次,我们现在要获取每个层级的所有子节点,表面上看使用hash结构更加快捷,但如果在集群环境下,这个结构就保存到了一个实例上了,必然会引起
hotkey和bigkey 问题。反观使用打平的方式,因为数据分散到多个节点上,性能反而会提高很多。

接着,今天主要跟大家聊下lua来查询子节点的方案,我们的要求是如何能随便拿出来某一层,当然我们最后选择使用hash结构存储(历史原因吧,客户端每次发送的请求都是某一层级的值,而不是完整路径,所以用模糊匹配查找起来难度较大)
那lua查询获得所有子节点的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
local treeKey = KEYS[1]
local fnodeId = ARGV[1]

-- 获取某个层级下的子节点
local function getTreeChild(currentnode, t, res)
if currentnode == nil or t == nil then
return res
end

l local nextNode = nil
local nextType = nil
if t == "id" and (type(currentnode) == "number" or type(currentnode) == "string") then
local treeNode = redis.call("HGET", treeKey, currentnode)
if treeNode then
local node = cjson.decode(treeNode)
table.insert(res, treeNode)
if node and node.childIds then
nextNode = node.childIds
nextType = "childIds"
end
end
elseif t == "childIds" then
nextNode = {}
nextType = "childIds"
local treeNode = nil
local node = nil
local cnt = 0
for _, val in ipairs(currentnode) do
treeNode = redis.call("HGET", treeKey, tostring(val))
if treeNode then
node = cjson.decode(treeNode)
table.insert(res, treeNode)
if node and node.childIds then
for _, val2 in ipairs(node.childIds) do
table.insert(nextNode, val2)
cnt = cnt + 1
end
end
end
end
if cnt == 0 then
nextNode = nil
nextType = nil
end
end
-- 使用递归的方式调用
return getTreeChild(nextNode, nextType, res)
end

以上代码出自 https://my.oschina.net/u/1469495/blog/1504818#comments
做了些调整,但是性能存在问题,未在生产上使用。

但是,我们发现业务中查询节点是一个高频的操作,追踪代码发现一个for循环4万次调用,里面又有4次hget操作,合下来就是16w,于是又开始尝试使用lua,将这4次hget合并,当然有同学会问
为什么不使用mget,或者pipeline,因为我们for 循环里的get操作还有业务逻辑,key之间有依赖关系,需要拿到上一个key对应的value,做截取然后在加工,作为下一次的key,所以单纯使用mget解决不了我们的问题
尝试过后,性能提升了好几倍

调试

redis-lua 的调试,推荐使用参数 --ldb-sync-mode
因为该参数,再调试的过程不会被打断,直接使用 –ldb 会被打断
,或者hang住到那,这都不是我们期待的,所以条件允许,请使用 --ldb-sync-mode 参数

调试过程中,常用命令如下:

  1. n(ext) 单步下一步
  2. b(reakpoint) 设置断点 b 13
  3. p(rint) 打印当前context的所有变量
坚持原创技术分享,您的支持将鼓励我继续创作!