前言
想象一个场景,如果CMDB使用关系型数据库(MySQL/Oracle)存储整个机房的网络拓扑信息
- 服务器
- 交换机
- 路由器
- 防火墙
- 端口
- 链路
- 机架
- 机房
这个关系型数据库的表字段得建多少外键?查询一条链路要join多少张表?怎么实时更新维护?
图数据库
Neo4j是一个原生图数据库(Native Graph Database),常被用于构建知识图谱(Knowledge Graph)应用。
图数据库是一种以图结构(节点、关系、属性)为核心存储模型的新型数据库。
它不再使用传统的表、行、列结构,而是直接存储实体与实体之间的关系,非常适合表达网络、拓扑、组织、社交、依赖等天然具有复杂关系连接特性的数据。
图数据库最大的特点是:关系是第一公民,查询时无需JOIN、无需外键,速度极快。
使用图数据库
Cypher是Neo4j图数据库专用的查询语言,专门用来查询和操作图数据,是图数据库版的SQL,但比SQL更简单、更直观、更擅长处理关系网。
from neo4j import GraphDatabase # 连接你的 Neo4j 数据库 URI = "neo4j://127.0.0.1:7687" USER = "neo4j" PASSWORD = "" # 这里改成你的密码! #空白图纸 def trunk_graph(driver): with driver.session() as session: session.run("MATCH (n) DETACH DELETE n") print("✅图谱数据已清空") #构建图 def build_graph(driver): with driver.session() as session: session.run(""" CREATE (router:Router {name:'核心路由器', ip:'10.0.0.1'}), (fw:Firewall {name:'防火墙', ip:'10.0.0.2'}), (switch:Switch {name:'汇聚交换机', ip:'10.0.0.3'}), (server1:Server {name:'应用服务器-1', ip:'192.168.1.10'}), (server2:Server {name:'应用服务器-2', ip:'192.168.1.11'}), (rack:Rack {name:'机柜A'}), (room:Room {name:'机房一区'}) CREATE (router)-[:CONNECT_TO]->(fw), (fw)-[:CONNECT_TO]->(switch), (switch)-[:CONNECT_TO]->(server1), (switch)-[:CONNECT_TO]->(server2), (server1)-[:MOUNT_IN]->(rack), (rack)-[:LOCATED_IN]->(room) """) print("✅机房拓扑创建完成!") #图查询 def query_server_path(driver): with driver.session() as session: result = session.run(""" MATCH path = (server1:Server {name:'应用服务器-1'})<-[:CONNECT_TO*]-() RETURN path """) for record in result: print("服务器1的上游链路:", record["path"]) if __name__ == '__main__': driver = GraphDatabase.driver(URI, auth=(USER, PASSWORD)) trunk_graph(driver) build_graph(driver) query_server_path(driver) driver.close()
所以未来的趋势不是用Neo4j取代外键,而是关系型数据库继续当主数据源,Neo4j 做关系加速层,复杂查询走图,事务和报表还留在SQL里。
关系性数据库管数据,图数据库管关系,关系性数据库写,图库查,CDC 同步,各司其职。
旁路小模型根据自然语言生成Cypher查询
import os,ollama,json from llama_index.core import ( SimpleDirectoryReader, VectorStoreIndex, StorageContext, load_index_from_storage, Settings ) from llama_index.core.node_parser import SentenceSplitter from llama_index.embeddings.huggingface import HuggingFaceEmbedding from neo4j import GraphDatabase #长期记忆-Rage class LocalRAG(object): def __init__( self, knowledge_dir="knowledge", index_dir="vector_index", chunk_size=30, chunk_overlap=4 ): self.knowledge_dir = knowledge_dir self.index_dir = index_dir # 配置分段 & 向量模型 Settings.node_parser = SentenceSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap) Settings.embed_model = HuggingFaceEmbedding(model_name="all-MiniLM-L6-v2") # 自动加载已有索引,没有则重建 if os.path.exists(index_dir): self.index = self._load_index() else: self.index = self._build_index() self.retriever = self.index.as_retriever(similarity_top_k=3) def _build_index(self): documents = SimpleDirectoryReader(self.knowledge_dir).load_data() index = VectorStoreIndex.from_documents(documents) # 保存到本地 index.storage_context.persist(persist_dir=self.index_dir) return index def _load_index(self): storage_context = StorageContext.from_defaults(persist_dir=self.index_dir) return load_index_from_storage(storage_context) def search(self, query): nodes = self.retriever.retrieve(query) return [node.text for node in nodes] #长期记忆关系图谱 class LocalGraph(object): #初始化知识图谱连接 def __init__(self,url,username,password): self.url = url self.username = username self.password = password self.driver=GraphDatabase.driver(uri=self.url, auth=(self.username, self.password)) def loacal_side_llm(self,user_prompt): prompt = f""" 你是一个只会输出JSON的Cypher生成器。 任务: 从用户问题中,提取【应用名称】。 生成固定格式的JSON,不要解释,不要多余内容。 节点:App(oamname) 关系:call 输出格式(严格遵守): {{ "match": "(a:App)-[r:call*1..20]->(b:App)", "where": "a.oamname = '这里填提取到的应用名称'", "return": "a.oamname AS 入口应用, collect(b.oamname) AS 全链路依赖" }} 用户问题:{user_prompt} """ response = ollama.generate( model="llama3.1:8b", prompt=prompt, format="json", options={"temperature": 0.01} ) return response.get("response") def search(self, query,parameters=None): with self.driver.session() as session: result = session.run(query, parameters) return result.data() # 返回Python能看懂的格式 def init_data(self): with self.driver.session() as s: s.run(""" CREATE (a1:App {oamname:'nginx-gateway'}), (a2:App {oamname:'ops-mgmt'}), (a3:App {oamname:'rds-mysql-5678abc9-singapore-prod'}) CREATE (a1)-[:call]->(a2), (a2)-[:call]->(a3) """) print("✅ 测试数据插入完成!") def trunk_graph(self): with self.driver.session() as session: session.run("MATCH (n) DETACH DELETE n") print("✅图谱数据已清空") if __name__ == '__main__': graph = LocalGraph(url="neo4j://localhost:7687",username="neo4j",password="Dell2026") # graph.trunk_graph() # graph.init_data() # graph.loacal_side_llm(user_prompt="111") # query = """ # MATCH (a:App {oamname: 'Nginx'})-[:call*1..20]->(b:App) # RETURN a.oamname AS 入口应用, collect(b.oamname) AS 全链路依赖 # """ # result_json= graph.loacal_side_llm("nginx-gateway应用的链路信息") result = json.loads(result_json) cypher = f"MATCH {result['match']} WHERE {result['where']} RETURN {result['return']}" print(cypher) print(graph.search(query=cypher))
浙公网安备 33010602011771号