最近因業務須要,研究了一下樹數據結果的存儲及查詢解決方案。 最初的想法是使用neo4j,但是在網上看了一下開源的不支持集羣,感受用的人很少。node
網上也查了一些 樹形結構數據存儲方案 但每種實現方案都有它的必定侷限性。redis
想了一短期後,想出了下面的方案:數據庫
1、 由於複雜的查詢都由Redis來處理,因此數據庫表的設計就變得很是簡單:tree 表json
字段名稱 | 數據類型 | 備註說明 |
---|---|---|
id | int | 主鍵 |
parent_id | int | 上級節點ID |
2、Redis的數據存儲方案:測試
把表的數據存儲到一個Hash表中,使用表中的id值作爲此hash表的key, value值爲:優化
{ id: 10, parentId: 9, childIds: [11] }
爲了簡化測試,這裏只演示Redis相關的操做lua
Tree 類定義spa
public class Tree { private Integer id; private String name; private Integer parentId; private List<Integer> childIds; }
往Redis中添加測試數據:.net
[@Test](https://my.oschina.net/azibug) public void addTestData() throws Exception { String key = "tree-test-key"; Tree tree = new Tree(); List<Integer> childIds = new ArrayList<>(); int max = 100000 tree.setChildIds(childIds); for (int i = 0; i < max; i++) { tree.setId(i); tree.setName("tree" + i); if (i > 0) { tree.setParentId(i - 1); } childIds.clear(); if(i < (max - 1)){ childIds.add(i + 1); } redis.setHash(key, "" + i, JsonUtil.toJson(tree)); } }
Lua 代碼的實現設計
在Lua中使用遞歸時,須要使用「尾調用」來優化代碼。關於尾調用的知識,你們能夠上網去搜索。
local treeKey = KEYS[1] local fnodeId = ARGV[1] local function getTreeChild(currentnode, t, res) if currentnode == nil or t == nil then return res end 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 if treeKey and fnodeId then return getTreeChild(fnodeId, "id", {}) end return {}
local treeKey = KEYS[1] local fnodeId = ARGV[1] local function getTreeChildCnt(currentnode, t, res) if currentnode == nil or t == nil then return res end 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) res = res + 1 if node and node.childIds then nextNode = node.childIds nextType = "childIds" end end elseif t == "childIds" then nextNode = {} nextType = "childIds" local treeNode = nil local cnt = 0 for _, val in ipairs(currentnode) do treeNode = redis.call("HGET", treeKey, tostring(val)) if treeNode then local node = cjson.decode(treeNode) res = res + 1 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 getTreeChildCnt(nextNode, nextType, res) end if treeKey and fnodeId then return getTreeChildCnt(fnodeId, "id", 0) end return 0
local treeKey = KEYS[1] local nodeId = ARGV[1] local function getTreeParent(treeKey, res, nodeId) if nodeId == nil or not (type(nodeId) == "number" or type(nodeId) == "string") then return res end local treeNode = redis.call("HGET", treeKey, nodeId) local nextNodeId = nil if treeNode then local node = cjson.decode(treeNode) table.insert(res, treeNode) if node then nextNodeId = node.parentId end end return getTreeParent(treeKey, res, nextNodeId) end if treeKey and nodeId then return getTreeParent(treeKey, {}, nodeId) end return {}
local treeKey = KEYS[1] local nodeId = ARGV[1] local function getTreeParentCnt(treeKey, nodeId, res) if nodeId == nil or not (type(nodeId) == "number" or type(nodeId) == "string") then return res end local treeNode = redis.call("HGET", treeKey, nodeId) local nextNodeId = nil if treeNode then local node = cjson.decode(treeNode) res = res + 1 if node then nextNodeId = node.parentId end end return getTreeParentCnt(treeKey, nextNodeId, res) end if treeKey and nodeId then return getTreeParentCnt(treeKey, nodeId, 0) end return 0
以上代碼由於使用了「尾調用」,因此變得相對比較複雜
此方案相對比較靈活,能支持相對比較大量的數據。
缺點:過於依賴Redis。數據同步會麻煩些,好在操做不是很複雜。