【大数据】 Neo4J+Redis存储结构优化

303 阅读7分钟

image.png

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天,点击查看活动详情

前言

昨天文章中,考虑到图谱首页的查询速度等因素,直接从Neo4J查询并不是一个明智的选择,因为查询语句受限并不能展示想要的数据结构,并且节点分布不均,因此将图数据库中的部分数据通过查询语句写入到redis中,查询之后通过处理缓存数据,进行首页的初始化,这样能够满足首页的加载速度,但是发现一个问题,在数据处理阶段,也就是将数据处理为前端所需的形式时,由于多层嵌套,导致做了几个N方基本负责度的粑粑代码,因此感觉速度还是受影响,今天将对Redis的存储结构进行优化,并尝试模糊查询方法。

之前遗留的问题:

之前的数据处理为如下格式

[{"company_name":[{"product_name":["cvenumber1","cvenumber1","cvenumber1"]}]}}]

由于无论是公司还是产品还是漏洞编号都是通过list嵌套的方式存储,这样的好处是可以方便的使用limit参数返回想要的数据数量,如 product_name[list][:limit]就能获得如50条数据中的30条,但是在数据处理过程中,多层嵌套拿到内层参数需要大量的for循环,这样对于程序的运行时十分耗时的,随着数据量的增加,之前的数据处理代码如下:

def formatAllNodes(self, dataLine, level, limit):
    nodes = []
    edges = []
    if level == 2:
        for line in eval(dataLine)[:limit]:
            for k, v in line.items():
                if k != "product_number":
                    company_name = k
                    childrenNum = line["product_number"]
                    node = {
                        "id": company_name,
                        "name": company_name,
                        "level": 0,
                        "childrenNum": childrenNum,
                        "tag": company_name
                    }
                    nodes.append(node)
                    product_list = line[company_name][0]
                    for item in (product_list[:limit]):
                        for product_name, cve_list in (item.items()):
                            if product_name != "cve_number":
                                node = {
                                    "id": product_name,
                                    "name": product_name,
                                    "level": 2,
                                    "isLeaf": True,
                                    "tags": [
                                        company_name
                                    ]
                                }
                                nodes.append(node)
                                edge = {
                                    "source": company_name,
                                    "target": product_name
                                }
                                edges.append(edge)
        resDic = {"nodes":nodes,"edges":edges}
        return resDic
    elif level == 3:
        for line in eval(dataLine)[:limit]:
            for k, v in line.items():
                if k != "product_number":
                    company_name = k
                    childrenNum = line["product_number"]
                    node = {
                        "id": company_name,
                        "name": company_name,
                        "level": 0,
                        "childrenNum": childrenNum,
                        "tag": company_name
                    }
                    nodes.append(node)
                    product_list = line[company_name][0]
                    for item in (product_list[:limit]):
                        for product_name, cve_list in (item.items()):
                            if product_name != "cve_number":
                                node = {
                                    "id": product_name,
                                    "name": product_name,
                                    "level": 1,
                                    "childrenNum": item["cve_number"],
                                    "tag": product_name,
                                    "isLeaf": False,
                                    "tags": [
                                        company_name
                                    ]
                                }
                                nodes.append(node)
                                edge = {
                                    "source": company_name,
                                    "target": product_name
                                }
                                edges.append(edge)

                                for cvenumber in cve_list[:limit]:
                                    node = {
                                        "id": cvenumber,
                                        "name": cvenumber,
                                        "level": 2,
                                        "isLeaf": True,
                                        "tags": [
                                            company_name
                                        ]
                                    }
                                    nodes.append(node)
                                    edge = {
                                        "source": company_name,
                                        "target": cvenumber
                                    }
                                    edges.append(edge)
        resDic = {"nodes":nodes,"edges":edges}
        return resDic
    else:
        return {"nodes":[],"edges":[]}

4-5层的for循环明显不利于查询过程中的响应速度,因此首先从redis的存储结构入手。

def makeData():
    conn = connect2Neo4J().conn
    vulnerability = []
    conpany_list = conn.run("MATCH (n:company)-[r]->() RETURN n,count(r) order by count(r) DESC limit 50")
    for index, item in enumerate(conpany_list):
        conpany_name = (str(item.data().get("n")).split("name: '")[1].rstrip("'})"))
        print(index,conpany_name)
        product_number = int(item.data().get("count(r)"))
        # 漏洞列表存储公司名称以及公司所包含的产品数量
        vulnerability.append({"conpany_name": conpany_name, "product_number": product_number})
        product_list = conn.run(f"MATCH (n:company{{name:'{conpany_name}'}})-[r]->(m:product) -[t]-> () RETURN m,count(t) order by count(t) DESC limit 50")
        # 产品列表存储公司名称以及产品所包含的漏洞数量
        product_list_all = []
        for item in product_list:
            cvenode_number = int(item.data().get("count(t)"))
            product_name = (str(item.data().get("m")).split("name: '")[1].rstrip("'})"))
            product_list_all.append({"product_name":product_name, "cvenode_number":cvenode_number})
            cve_list = conn.run(f"MATCH (n:company{{name:'{conpany_name}'}})-[r]->(m:product{{name:'{product_name}'}}) -[t]-> (o:cvenumberNode)   RETURN o order by o.name DESC limit 50")
            # 漏洞数量列表
            cve_list_all = []
            for item in cve_list:
                cve_number = (str(item.data().get("o")).split("name: '")[1].rstrip("'})"))
                cve_list_all.append(cve_number)
            redisUtils().setKey(product_name, str(cve_list_all))
        redisUtils().setKey(conpany_name, str(product_list_all))
    redisUtils().setKey("vulnerability", str(vulnerability))

重新更新redis的存储结构,其中vulnerability保存了top50公司以及对应的产品数量,对于不同的公司,使用公司名称为键值,保存对应的产品和产品所包含的漏洞数量,第三部分则是产品名称作为键,而漏洞列表作为值,这样查询过程中,将for循环遍历的时间改为redis的查询时间,用于优化查询以及页面加载速度。

查询使用

redisUtils().getKey("vulnerability")

进行查询。

结果示例:

[{'conpany_name': 'intel', 'product_number': 6789}, {'conpany_name': 'hp', 'product_number': 5860}, {'conpany_name': 'cisco', 'product_number': 4020}, {'conpany_name': 'lenovo', 'product_number': 2800}, {'conpany_name': 'siemens', 'product_number': 2176}, {'conpany_name': 'dell', 'product_number': 2106}, {'conpany_name': 'qualcomm', 'product_number': 1881}, {'conpany_name': 'debian_9', 'product_number': 1356}, {'conpany_name': 'schneider-electric', 'product_number': 1291}, {'conpany_name': 'debian_10', 'product_number': 1227}, {'conpany_name': 'huawei', 'product_number': 1200}, {'conpany_name': 'debian_11', 'product_number': 979}, {'conpany_name': 'mitsubishielectric', 'product_number': 928}, {'conpany_name': 'netgear', 'product_number': 907}, {'conpany_name': 'debian_sid', 'product_number': 905}, {'conpany_name': 'supermicro', 'product_number': 888}, {'conpany_name': 'axis', 'product_number': 798}, {'conpany_name': 'lexmark', 'product_number': 766}, {'conpany_name': 'opensuse_Leap_15.1', 'product_number': 715}, {'conpany_name': 'amd', 'product_number': 680}, {'conpany_name': 'debian_12', 'product_number': 676}, {'conpany_name': 'ubuntu_18.04.5_lts', 'product_number': 667}, {'conpany_name': 'fedora_29', 'product_number': 636}, {'conpany_name': 'ibm', 'product_number': 629}, {'conpany_name': 'opensuse_Leap_15.2', 'product_number': 622}, {'conpany_name': 'fedora_27', 'product_number': 618}, {'conpany_name': 'samsung', 'product_number': 608}, {'conpany_name': 'oracle_8', 'product_number': 587}, {'conpany_name': 'phoenixcontact', 'product_number': 564}, {'conpany_name': 'fedora_28', 'product_number': 563}, {'conpany_name': 'fedora_31', 'product_number': 561}, {'conpany_name': 'ubuntu_18.04', 'product_number': 547}, {'conpany_name': 'opensuse_Leap_15.0', 'product_number': 542}, {'conpany_name': 'debian_8', 'product_number': 541}, {'conpany_name': 'fedora_EPEL_7', 'product_number': 540}, {'conpany_name': 'ubuntu_16.04.7_lts', 'product_number': 539}, {'conpany_name': 'hikvision', 'product_number': 532}, {'conpany_name': 'alpine_edge', 'product_number': 531}, {'conpany_name': 'opensuse_Leap_42.3', 'product_number': 531}, {'conpany_name': 'jenkins', 'product_number': 527}, {'conpany_name': 'fedora_32', 'product_number': 527}, {'conpany_name': 'asus', 'product_number': 519}, {'conpany_name': 'fedora_30', 'product_number': 507}, {'conpany_name': 'fedora_33', 'product_number': 492}, {'conpany_name': 'ubuntu_16.04', 'product_number': 482}, {'conpany_name': 'alpine_3.16', 'product_number': 467}, {'conpany_name': 'alpine_3.13', 'product_number': 459}, {'conpany_name': 'tp-link', 'product_number': 457}, {'conpany_name': 'ubuntu_18.10', 'product_number': 455}, {'conpany_name': 'alpine_3.15', 'product_number': 447}]

根据公司名称查询:

print(redisUtils().getKey("intel"))

结果如下:

[{'product_name': 'core_i7-8665u', 'cvenode_number': 59}, {'product_name': 'graphics_driver', 'cvenode_number': 58}, {'product_name': 'core_i7-8565u', 'cvenode_number': 58}, {'product_name': 'core_i7-8706g', 'cvenode_number': 58}, {'product_name': 'core_i7-8650u', 'cvenode_number': 58}, {'product_name': 'core_i7-8700', 'cvenode_number': 57}, {'product_name': 'core_i7-8700t', 'cvenode_number': 57}, {'product_name': 'core_i7-8700k', 'cvenode_number': 57}, {'product_name': 'core_i7-8500y', 'cvenode_number': 57}, {'product_name': 'core_i7-8750h', 'cvenode_number': 56}, {'product_name': 'core_i7-8709g', 'cvenode_number': 56}, {'product_name': 'core_i7-8550u', 'cvenode_number': 56}, {'product_name': 'core_i7-8809g', 'cvenode_number': 56}, {'product_name': 'core_i7-8850h', 'cvenode_number': 56}, {'product_name': 'core_i7-8705g', 'cvenode_number': 56}, {'product_name': 'core_i7-8700b', 'cvenode_number': 55}, {'product_name': 'xeon_gold_5220s', 'cvenode_number': 54}, {'product_name': 'xeon_gold_6230', 'cvenode_number': 54}, {'product_name': 'xeon_gold_6222v', 'cvenode_number': 54}, {'product_name': 'core_i7-8559u', 'cvenode_number': 54}, {'product_name': 'xeon_gold_5222', 'cvenode_number': 54}, {'product_name': 'xeon_gold_5218b', 'cvenode_number': 54}, {'product_name': 'xeon_bronze_3204', 'cvenode_number': 54}, {'product_name': 'xeon_gold_6226', 'cvenode_number': 54}, {'product_name': 'xeon_gold_6238l', 'cvenode_number': 54}, {'product_name': 'xeon_gold_5215', 'cvenode_number': 54}, {'product_name': 'xeon_silver_4208', 'cvenode_number': 54}, {'product_name': 'xeon_gold_5218n', 'cvenode_number': 54}, {'product_name': 'xeon_silver_4214', 'cvenode_number': 54}, {'product_name': 'xeon_gold_5218', 'cvenode_number': 54}, {'product_name': 'xeon_silver_4215', 'cvenode_number': 54}, {'product_name': 'xeon_silver_4214y', 'cvenode_number': 54}, {'product_name': 'xeon_gold_5217', 'cvenode_number': 54}, {'product_name': 'xeon_silver_4216', 'cvenode_number': 54}, {'product_name': 'xeon_gold_6238', 'cvenode_number': 54}, {'product_name': 'xeon_gold_5220', 'cvenode_number': 54}, {'product_name': 'xeon_silver_4210', 'cvenode_number': 54}, {'product_name': 'xeon_silver_4209t', 'cvenode_number': 54}, {'product_name': 'xeon_gold_5215l', 'cvenode_number': 54}, {'product_name': 'core_i7-7600u', 'cvenode_number': 53}, {'product_name': 'core_i7-7700t', 'cvenode_number': 53}, {'product_name': 'core_i7-7820hq', 'cvenode_number': 53}, {'product_name': 'core_i7-7920hq', 'cvenode_number': 53}, {'product_name': 'xeon_gold_5218t', 'cvenode_number': 53}, {'product_name': 'core_i7-7660u', 'cvenode_number': 53}, {'product_name': 'xeon_gold_5220t', 'cvenode_number': 53}, {'product_name': 'core_i7-10710u', 'cvenode_number': 52}, {'product_name': 'core_i7-10510y', 'cvenode_number': 52}, {'product_name': 'core_i7-7500u', 'cvenode_number': 52}, {'product_name': 'core_i7-7567u', 'cvenode_number': 52}]

根据产品查询,结果如下:

['CVE-2022-29901', 'CVE-2022-21180', 'CVE-2022-0005', 'CVE-2022-0002', 'CVE-2022-0001', 'CVE-2021-33150', 'CVE-2021-33124', 'CVE-2021-33107', 'CVE-2021-0158', 'CVE-2021-0157', 'CVE-2021-0156', 'CVE-2021-0127', 'CVE-2021-0125', 'CVE-2021-0124', 'CVE-2021-0119', 'CVE-2021-0118', 'CVE-2021-0117', 'CVE-2021-0116', 'CVE-2021-0115', 'CVE-2021-0111', 'CVE-2021-0107', 'CVE-2021-0103', 'CVE-2021-0099', 'CVE-2021-0095', 'CVE-2021-0093', 'CVE-2021-0092', 'CVE-2021-0091', 'CVE-2020-8703', 'CVE-2020-8700', 'CVE-2020-8695', 'CVE-2020-8694', 'CVE-2020-8670', 'CVE-2020-24507', 'CVE-2020-24506', 'CVE-2020-24486', 'CVE-2020-24457', 'CVE-2020-12360', 'CVE-2020-12359', 'CVE-2020-12358', 'CVE-2020-12357', 'CVE-2020-0593', 'CVE-2020-0551', 'CVE-2020-0549', 'CVE-2020-0548', 'CVE-2020-0543', 'CVE-2020-0529', 'CVE-2020-0528', 'CVE-2019-14615', 'CVE-2019-14607', 'CVE-2019-11157']

数据处理程序优化:

def formatAllNodes(self, dataLine, level, limit):
    nodes = []
    edges = []
    if level == 2:
        for line in eval(dataLine)[:limit]:
            company_name = line.get("conpany_name")
            product_number = line.get("product_number")
            node = {
                "id": company_name,
                "name": company_name,
                "level": 0,
                "childrenNum": product_number,
                "tag": company_name
            }
            nodes.append(node)
            for line in eval(self.r.getKey(company_name))[:limit]:
                product_name = line.get("product_name")
                node = {
                    "id": product_name,
                    "name": product_name,
                    "level": 1,
                    "isLeaf": True,
                    "tags": [
                        company_name
                    ]
                }
                nodes.append(node)
                edge = {
                    "source": company_name,
                    "target": product_name
                }
                edges.append(edge)
                nodes.append(node)
        resDic = {"nodes":nodes,"edges":edges}
        return resDic
    elif level == 3:
        for line in eval(dataLine)[:limit]:
            company_name = line.get("conpany_name")
            product_number = line.get("product_number")
            node = {
                "id": company_name,
                "name": company_name,
                "level": 0,
                "childrenNum": product_number,
                "tag": company_name
            }
            nodes.append(node)
            for line in eval(self.r.getKey(company_name))[:limit]:
                product_name = line.get("product_name")
                node = {
                    "id": product_name,
                    "name": product_name,
                    "level": 1,
                    "childrenNum": line.get("cvenode_number"),
                    "tag": product_name,
                    "isLeaf": False,
                    "tags": [
                        company_name
                    ]
                }
                nodes.append(node)
                edge = {
                    "source": company_name,
                    "target": product_number
                }
                edges.append(edge)
                nodes.append(node)
                for line in eval(self.r.getKey(product_name))[:limit]:
                    cvenumber = line
                    node = {
                        "id": cvenumber,
                        "name": cvenumber,
                        "level": 2,
                        "isLeaf": True,
                        "tags": [
                            product_name
                        ]
                    }
                    nodes.append(node)
                    edge = {
                        "source": product_name,
                        "target": cvenumber
                    }
                    edges.append(edge)
        resDic = {"nodes":nodes,"edges":edges}
        return resDic

查询速度比之前有所优化

查询1000节点时间能控制在100-300ms,暂时可以接受

image.png