当前位置:   article > 正文

Python 图算法系列5-使用Py2neo操作neo4j_neo4j中的15种不同图表算法python实现

neo4j中的15种不同图表算法python实现

说明

Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做图)上而不是表中。Neo4j也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。程序员工作在一个面向对象的、灵活的网络结构下而不是严格、静态的表中——但是他们可以享受到具备完全的事务特性、企业级的数据库的所有好处。
Neo4j因其嵌入式、高性能、轻量级等优势,越来越受到关注.

关于Neo4j的作用在我别的文章会体现,总结起来可以做:

  • 1 储存业务关系的数据库
  • 2 图算法的存储空间

这本书挺有趣,将如何使用neo4j, py2neo以及加上Flask构建一个网站,很期待有空读一读。Packt这个出版社的书看着挺漂亮的,github地址
在这里插入图片描述

我从这本书摘取了一些我认为比较必要&有用的补充内容。

1 内容

1.1 关于Neo4j的基本信息

现有关系型数据更适用于关系是静态的场景,更多的关注实体而非关系。
RDBMS is good for use cases where the relationship between entities is more or less static and does not change over a period of time. Moreover, the focus of RDBMS is more on the entities and less on the relationships between them.
Neo4j 更加关注于关系而非实体本身。
Neo4j, as an open source graph database, is part of the NoSQL family, and provides
a lexible data structure, where the focus is on the relationships between the entities
rather than the entities themselves.

SQL和Cypher的比较(后续我们主要使用Py2neo执行Cypher的功能,但有时也需要直接执行Cypher。)
在这里插入图片描述
Python对应的工具

  • pymysql ~ SQL
  • py2neo ~ Cypher

我们后续使用的是Community版本,分别用单机和容器方式实现。有两点可以关注:

  • Community Purpose(GPL许可)

    • 1 用于开发和测试
    • 2 中小规模的应用
    • 3 研发,不关注运行监控和性能
  • Enterprise Purpose(个人许可,Startup Program, Enterprise subscription)

    • 1 备份
    • 2 恢复
    • 3 复制(这点应该可以通过scale多个容器达到相同的效果)

1.2 Cypher介绍

Cypher作为图数据库的查询语言,是声明式(Declarative, 关注要什么),具备

  • 1 子图匹配(Subgraph Matching)
  • 2 路径匹配(Comparing and returning paths)
  • 3 聚合(Aggregations)
  • 4 近似匹配和排序(Approximate Matching and ranking)

Cypher工作的四阶段:

  • 1 解析和验证命令语句,并生成查询计划
  • 2 锁定初始节点(Initial Node)
  • 3 选择及遍历关系
  • 4 修改/返回值

1 Neo4j 可以在数秒内搜索数十亿个节点,但是搜索全图不是一个好主意。
2 搜索从一个初始点开始(Initial Node)

Cypher语法结构模仿了SQL,语句的组成部分有四部分:

  • 1 节点(类似于记录行 row)
  • 2 属性(类似于字段)
  • 3 标签(类似于table)
  • 4 关系(类似于join)

数据库的CRUD: Create, Read, Update ,Delete

Cypher 关键命令

  • 1 Match
    • Match (x:Male) where 【条件】 return x
    • 从Male表中选择了符合某些条件的x
  • 2 Optional Match
    • 和match一样,不过会返回空
  • 3 Start
    • Start n=node:nodeindex(Name=“Jordan”) return n;
  • 4 聚合操作
    • count
      • Match (x:Male) where 【条件】 return count(x)
      • Match (x:Male) where 【条件】 return count(distinct x.name)
  • 5 Create
    • Create (n:Male {Name:“Jordan”, Age:“24”});
      • 给节点集合(相当于数据表)Male增加了一个节点。
  • 6 Set (在Update操作中)
    • match (n:Male {name:“Jordan”}) set n.age = 25 return n
  • 7 Merge(类似create new if not exists else update)
    • Merge (n:Male {name:“Jordan”}) return n
Match (n:Male {name:"Jordan", age:24})  # 匹配Male中名为Jordan, 年龄为24的节点
on match set n.age = 25 , n.last_update_timestamp = timestamp() # 如果存在了,修改年龄属性,并修改/增加 最后一次更新时间戳
on create set n.create_timestamp = timestamp() # 如果不存在那么创建并更新创建时间戳
  • 1
  • 2
  • 3
  • 8 Delete
    • Match (n:Male {name:“Jordan”, age:24}) delete n;
    • 删除掉某个属性,很NoSql
      • Match (n:Male {name:“Andrew”, age:24}) remove n.age return n

1.3 模式匹配(Pattern Matching)

模式是Cypyer的核心,以下建立一个简单的例子

  1. 清库
# 1 先删除关系
MATCH (n)-[r]-(n1) delete n,r,n1;
# 2 删除节点
MATCH (n) delete n;
  • 1
  • 2
  • 3
  • 4
  1. 建立数据(4男2女)
    每个分号是一条执行命令。
CREATE (bradley:MALE:TEACHER {name:'Bradley', surname:'Green',
 age:24, country:'US'});
 
 CREATE (matthew:MALE:STUDENT {name:'Matthew', surname:'Cooper',
       age:36, country:'US'});
       
CREATE (lisa:FEMALE {name:'Lisa', surname:'Adams', age:15,
       country:'Canada'});
       
 CREATE (john:MALE {name:'John', surname:'Goodman', age:24,
       country:'Mexico'});
       
 CREATE (annie:FEMALE {name:'Annie', surname:'Behr', age:25,
       country:'Canada'});
       
CREATE (ripley:MALE {name:'Ripley', surname:'Aniston',
       country:'US'});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  1. 建立关系。从两个不同的集合选择节点,会提示产生笛卡尔积(有点像SQL的Join有木有)
MATCH(bradley:MALE{name:"Bradley"}),(matthew:MALE{name:"Matthew"})
       WITH bradley, matthew
       CREATE (bradley)-[:FRIEND]->(matthew) , (bradley)-[:TEACHES]-
       >(matthew);
       
 MATCH (bradley:MALE{name:"Bradley"}),(matthew:MALE{name:"Matthew"})
       WITH bradley,matthew
       CREATE (matthew)-[:FRIEND]->(bradley);
       
MATCH (bradley:MALE{name:"Bradley"}),(lisa:FEMALE{name:"Lisa"})
       WITH bradley,lisa
       CREATE (bradley)-[:FRIEND]->(lisa);

MATCH (lisa:FEMALE{name:"Lisa"}),(john:MALE{name:"John"})
       WITH lisa,john
       CREATE (lisa)-[:FRIEND]->(john);

MATCH (annie:FEMALE{name:"Annie"}),(ripley:MALE{name:"Ripley"})
       WITH annie,ripley
       CREATE (annie)-[:FRIEND]->(ripley);

MATCH (ripley:MALE{name:"Ripley"}),(lisa:FEMALE{name:"Lisa"})
       WITH ripley,lisa
       CREATE (ripley)-[:FRIEND]->(lisa);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

1.3.1 节点模式(Node Pattern)

MATCH (a) return a
  • 1

在这里插入图片描述

1.3.2 标签模式(Label Pattern)

返回 MALE & TEACHER的节点

MATCH (n:MALE:TEACHER) return n;
  • 1

在这里插入图片描述

1.3.3 关系模式(Relationship Pattern)

关系查询分为有向和无向(有向关系加个 < 或 > ), 查询返回的应该是三元组(start, relation, end)

MATCH (x:TEACHER)-[r:TEACHES]->(y:STUDENT) return x,r,y;
  • 1

在这里插入图片描述

1.3.4 属性匹配(Property Pattern)

Match (a:MALE { name: "John", age : 24 }) return a;
  • 1

1.3.5 在where语句中表达 Pattern

# 1 = , >
   MATCH (n)
   where n.name = "John" and n.age< 25
   return n;
# 2 In, Null
 MATCH (n)
   where n.name IN["John","Andrew"] and n.age is Not Null
   return n;
# 3 正则匹配
MATCH (n)
   where n.name =~"J.*"
   return n;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

1.3.6 排序语句

# 1 按两个属性(字段)排序
MATCH (n)return n ORDER by n.name, n.age;
# 2 只返回前三个
MATCH (n)return n ORDER by n.name, n.age LIMIT 3;
# 3 除了前三个
MATCH (n)return n ORDER by n.name, n.age SKIP 3;
# 4 先排除前三个再选中剩下的两个
MATCH (n)return n ORDER by n.name, n.age SKIP 3 LIMIT 2;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

1.3.7 With 连接多个模式

有点类似having, 可以把之前模式的结果作为输入条件。

# 1 基于上一步的聚合挑选(相当于having)
MATCH (x{ name: "Bradley" })--(y)-->()
   WITH y, count(*) AS cnt
   WHERE cnt> 1
   RETURN y;
# 2 和create配合
MATCH (x { name: "Bradley" })--(y)-->()
   WITH x
   CREATE (n:Male {name:"Smith", Age:"24"})-[r:FRIEND]->(x)
   return n,r,x;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

每次为x产生了3个smith, 估计是x被匹配到了三次,应该不太对,删掉所有的Smith

match (x {name:'Smith'})-[r:FRIEND]->(y {name:'Bradley'}) delete x, r
  • 1

查看一下之前查询生成的内容(with继承的结果)

MATCH (x { name: "Bradley" })--(y)-->() return x,y
  • 1

不知道是不是Bradley有三条向外的边导致的重复
在这里插入图片描述
修改一下

MATCH (x { name: "Bradley" })--(y)-->()
   WITH x
   order by x.name limit 1
   CREATE (n:Male {name:"Smith", Age:"24"})-[r:FRIEND]->(x)
   return n,r,x;
  • 1
  • 2
  • 3
  • 4
  • 5

这样似乎就对了,正常逻辑下返回的x应该是一个实体。只能说图数据库以关系为主吧(因为有三条边,所以x也被虚空fork了3次)。
在这里插入图片描述
另一个尝试,通过汇聚把x给“groupby”了

MATCH (x { name: "Bradley" })--(y)-->()
   WITH x,count(*) as xxx
   CREATE (n:Male {name:"Smith", Age:"24"})-[r:FRIEND]->(x)
   return n,r,x;
  • 1
  • 2
  • 3
  • 4

1.3.8 Union和Union all

union 返回去重后的记录

MATCH (x:MALE)-[:FRIEND]->() return x.name, labels(x)
   UNION
   MATCH (x:FEMALE)-[:FRIEND]->()return x.name, labels(x);
  • 1
  • 2
  • 3

在这里插入图片描述
union all 返回有重复的记录

MATCH (x:MALE)-[:FRIEND]->() return x.name, labels(x)
   UNION all
   MATCH (x:FEMALE)-[:FRIEND]->()return x.name, labels(x);
  • 1
  • 2
  • 3

在这里插入图片描述

1.4 增加一些数据

  • 1 增加三步电影
CREATE (firstBlood:MOVIE {name:"First Blood"});
      CREATE (avengers:MOVIE {name:"Avengers"});
      CREATE (matrix:MOVIE {name:"Matrix"});

  • 1
  • 2
  • 3
  • 4
  • 2 用户给电影增加评级
    因为使用的是name(这里可以视为唯一id),因此不会像之前一样返回多条记录,我猜会增加8条边。
MATCH
       (bradley:MALE{name:"Bradley"}),(matthew:MALE{name:"Matthew"}),(lisa:FEMALE{name:"Lisa"}), (john:MALE{name:"John"}), (annie:FEMALE{name:"Annie"}),(ripley:MALE{name:"Ripley"}),
       (firstBlood:MOVIE {name:"First Blood"}), (avengers:MOVIE
       {name:"Avengers"}), (matrix:MOVIE {name:"Matrix"})
       WITH bradley, matthew, lisa, john, annie, ripley, firstBlood,
       avengers, matrix
       CREATE (bradley)-[:HAS_RATED{ratings:5}]->(firstBlood),
        (matthew)-[:HAS_RATED{ratings:4}]->(firstBlood),
        (john)-[:HAS_RATED{ratings:4}]->(firstBlood),
        (annie)-[:HAS_RATED{ratings:4}]->(firstBlood),
        (ripley)-[:HAS_RATED{ratings:4}]->(firstBlood),
        (lisa)-[:HAS_RATED{ratings:5}]->(avengers),
        (matthew)-[:HAS_RATED{ratings:4}]->(avengers),
       (annie)-[:HAS_RATED{ratings:3}]->(avengers);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述

现在的情况:
1. 有一些男性和女性
2. 这些人一些是朋友或者师生
3. 他们看了电影并且评级

问:
Q1. Bradley生活在美国,哪些人和他一样?
Q2: 多少男性有女性朋友?
Q3: 我是Bradley,我想知道谁和我有共同好友?
Q4: 我是Bradley,我想知道谁是我好友的好友(但还不是我的)?
Q5: 找到所有电影以及被评级的次数(这个很mysql)
Q6:找到被Bradley以及他的朋友Rating的电影(这个在mysql里应该要连两层)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

A1

match (x {country:"US"}) return x
  • 1

在这里插入图片描述
A2

match (x:MALE)-[r:FRIEND]->(y:FEMALE) return
x.name as MaleName,type(r) as Relation,y.name as
FemaleName;
  • 1
  • 2
  • 3

在这里插入图片描述
A3

match (x{name:"Bradley"})-[:FRIEND]-
   >(friend)<-[:FRIEND]-(otherFriend) return distinct
   friend.name as CommonFriend;
  • 1
  • 2
  • 3

在这里插入图片描述
在这里插入图片描述
FRIEND本该是双向的(无向图),这个场景只能理解为谁是Bradley的情敌了(Ripley)。
A4

Match (me{name:"Bradley"})-[r:FRIEND]-
       (myFriend),(myFriend)-[:FRIEND]-(otherFriend)
       where NOT (me)-[:FRIEND]-(otherFriend)
       return otherFriend.name as NotMyFriends;
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
结果和书里的又不太一样,这次是自己出现了两次。

目前的关系如图:
在这里插入图片描述
第一部分语句:Bradley的盆友

Match (me{name:"Bradley"})-[r:FRIEND]- (myFriend) return me,r,myFriend
  • 1

在这里插入图片描述
第二部分语句:几乎就是整张图

Match (myFriend)-[r1:FRIEND]-(otherFriend)
      return myFriend,r1, otherFriend
  • 1
  • 2

在这里插入图片描述

我的neo4j服务器版本是3.x的,书里是2.x的,估计有些调整,这个差异先跳过。而且好像版本4(2020年)也发布了,先记着这里可能有坑把。

A5

MATCH (movie:MOVIE)<-[r:HAS_RATED*0..]-
   (person)
   return  movie.name as Movie, count(person)-1 as
   countOfRatings order by countOfRatings;
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
虽然得到结果,但是被warning了,看来版本之间的确有很大差异。提示说把关系绑定在一个变量长度模式下是不建议的,以后会被移除掉。

在这里插入图片描述
A6

match (x{name:"Bradley"})-[:FRIEND]-
       >(friend)-[r:HAS_RATED]->(movie) return friend.name as
       Person, r.ratings as Ratings,movie.name as Movie;
  • 1
  • 2
  • 3

在这里插入图片描述

1.7 数据库操作

ACID。
A (All all Nothing) 。保持事务原子性。
C(一致性)。任何时刻任何用户访问的结果是一致的。
I(隔离性)。每个操作都不会影响到其他操作。
D(持久化)。完成的操作被持久化。

再把库删了

# 1 先删关系
MATCH (n)-[r]-(n1) delete r,n.n1;
# 2 再删边
MATCH (n) delete n;
  • 1
  • 2
  • 3
  • 4

1.7.1 创建单节点

# 1 创建
create (n) return n;
# 2 查询
MATCH (n) return ID(n);
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
节点ID:
在这里插入图片描述

1.7.2 创建多节点

# 可以不写return 
CREATE (n),(n1);
  • 1
  • 2

在这里插入图片描述

使用Unwind创建多节点

据说这样比较快

UNWIND [{name:"李四"},{name:"张三"}] AS mynodes
CREATE (n) SET n = mynodes
  • 1
  • 2

1.7.3 带标签创建节点(这样节点就是有「组织」的了)

CREATE (:MALE)
  • 1

在这里插入图片描述
在这里插入图片描述
带标签带属性的创建

CREATE (x:MALE{name:"John"});
  • 1

在这里插入图片描述
neo4j默认会把name属性显示在球上
在这里插入图片描述
Neo4j支持的数据类型。
在这里插入图片描述

1.8 创建关系

CREATE (n:MALE{name:"John"})-[r:FRIEND]->(n1:FEMALE {name: "Kate"});
  • 1

在这里插入图片描述
Neo4j处理该语句的方式如下:
在这里插入图片描述

1.8.1 创建多条关系

一种是关系比较深的

CREATE (m1:MALE{name:"Pat"})-[r1:FRIEND]->
       (m2:MALE{name:"Smith"})-[r2:FRIEND]->(f1:FEMALE{name:"Kate"})-
       [r3:FRIEND]->(f2:FEMALE{name:"Kim"})
       return m1.name, type(r1), m2.name, type(r2), f1.name, type(r3),
       f2.name;
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述
一种是并行的

create (n1 {name:'x1'})-[r1:Hello {time:'now'}]->(n11 {name:'y1'}),(n2 {name:'x2'})-[r2:Hello {time:'later'}]->(n22 {name:'y2'})

  • 1
  • 2

在这里插入图片描述
create不支持创建无向(双向)连接,挺有意思的。
在这里插入图片描述
因为有些关系是不能重复的,所以cypher也提供了唯一关系的创建方法

match (andres { name:'Andres' })
CREATE UNIQUE (andres)-[:WORKS_WITH]->(michael {name:'Michael' });
  • 1
  • 2

直接运行这个会进行空操作(因为没有找到Andres)
在这里插入图片描述

#1 创建Andres节点
create (andres { name:'Andres' });
# 2 匹配并创建
match (andres { name:'Andres' })
CREATE UNIQUE (andres)-[:WORKS_WITH]->(michael {name:'Michael' });
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

1.9 创建约束

# 1 创建约束
CREATE CONSTRAINT ON (n:MALE) ASSERT n.name IS UNIQUE;
# 2 取消约束
DROP CONSTRAINT ON (n:Hello) ASSERT n.name IS UNIQUE;
  • 1
  • 2
  • 3
  • 4

创建约束成果:
在这里插入图片描述
取消约束结果:
在这里插入图片描述

虽然neo4j会为每个节点创建一个id,但那个我们不太好控制,不如自己创建一个主键。因为有时我们容易不小心创建新节点(虽然我们不打算),所以自己设定好主键,加一个约束,确保节点的一致性。

有了唯一约束之后,Merge更新节点就比较容易了, 参考

# 1 匹配某个节点,如果不存在就创建,存在就返回
merge (n:Hello {name:'x3'})
return n
# 2 检查节点是否存在,如果不存在则创建它并设置属性(如果存在就没反应)
## opr 2.1 x3已存在,无反应
merge (n:Hello {name:'x3'})
on create set n.createTime = timestamp()
return n
## opr 2.2 x4不存在,创建节点,并创建createTime属性
merge (n:Hello {name:'x4'})
on create set n.createTime = timestamp()
return n

##opr 2.3 匹配节点,并在找到的节点上设置属性
merge (n:Hello {name:'x4'})
on match set n.age=111
return n

## opr 2.4 create 与 match 同时使用
merge (n:Hello {name:'x4'})
on create set n.style='rich'
on match set n.style = 'very rich'
return n

merge (n:Hello {name:'x5'})
on create set n.style='rich'
on match set n.style = 'very rich'
return n

## opr 2.5 删除属性
merge (n:Hello {name:'x4'})
remove  n.style
return n
  • 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

在这里插入图片描述

opr 2.3 匹配节点,并在找到的节点上设置属性
在这里插入图片描述
类似地,可以操作节点的标签(增加set或删除remove);
关系也可以,匹配(match)然后删除(delete)

cypher语句总结,注意Unwind批量创建

1.10 Cypher Schema Index

Cypher创建的索引是Schema Index, (还有Legacy Index, 暂时不去管他)。
闭着眼大概能知道索引可以增加查询速度,但是会有一定开销,不要乱建等等。

CREATE INDEX ON :MALE(name);
DROP INDEX ON :MALE(name);
  • 1
  • 2

如果有了索引,那么查询时

MATCH (n:MALE)
USING INDEX n:MALE(name)
where n.name="Matthew"
return n;

# 这种写法据说能更快一点
MATCH (n:MALE)
   USING SCAN n:MALE
   where n.name="Matthew"
   return n;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Neo4j使用一种基于开销的优化算法(Cost based optimizer)来给每个查询生成执行计划。

两种分析执行计划(Execution Plan)的方法,在命令语句前加关键词:

  • 1 Explain。解释不执行。
  • 2 Profile。解释并执行。

从使用层面上暂时不去关注这些,但是当出现严重性能瓶颈的时候,可以查看并优化(我并不希望做这个)

2 Python与neo4j的连接

首先要安装py2neo,这个就相当于是mysql的pymysql。py2neo的操作风格和sqlalchemy有点像,算是数据库的ORM吧。

什么是ORM
即Object-Relationl Mapping,它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了 。

2.1 Graph - 连接

from py2neo import Graph, Node, Relationship, Subgraph, NodeMatcher, RelationshipMatcher
import time
graph = Graph(
    "http://localhost:7474",
    username="neo4j",
    password="YOURPASSWD"
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

原生的查询语句Cypher还是有点麻烦的,回到Python感觉就好了很多。整体上,我采用子图作为单位,批量的从Neo4j中取、存数据。

2.2 Node, NodeMatcher

节点的模板

# 节点ID,每个node有labels和attrs
node_template_dict = {
    'labels': [],
    'attrs': {}
}

# 制定节点,无论是什么节点,都可以叫实体。实体要求具备eid和name
def make_a_node(**kw):
    eid = kw.get('attrs').get('eid')
    name = kw.get('attrs').get('name')
    assert all([eid, ename]), '需要eid和name'
    labels = kw.get('labels')
    attrs = kw.get('attrs')
    return Node(*labels, **attrs)
# 在一个图中,先查找节点是否存在,不存在则创建
def match_a_node(graph=graph, node_template_dict=node_template_dict, eid_name='eid', new_node_dict=None):
    assert all([node_template_dict, new_node_dict]), '节点模板和新节点模板不能为空'
    eid = new_node_dict.get(eid_name)
    assert all([eid]), 'eid must be %s' % eid_name
    nmatcher = NodeMatcher(graph)
    some_node = nmatcher.match(eid=new_node_dict[eid_name]).first()
    if some_node is None:
        exist_tag = False
        some_node_dict = node_template_dict.copy()
        some_node_dict['attrs'] = new_node_dict
        some_node = make_a_node(**some_node_dict)
    else:
        exist_tag = True

    res_dict = {}
    res_dict['exist_tag'] = exist_tag
    res_dict['node'] = some_node
    return res_dict
  • 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

2.3 Relationship, RelationshipMatcher

节点的更新更麻烦些,需要增加三元组 A-Rel-B

rel_template_dict = {
    'from_node_id' :'',
    'to_node_id' : '',
    'from_node_dict' : {},
    'to_node_dict' : '',
    'rel_id' :'',
    'rel_dict' : {},
}

'''
- from_node_id
    + from_node_dict
        + lables
        + attrs
- to_node_id
    + to_node_dict
        + lables
        + attrs
- rel_id
- rel_dict(因为rel_attrs不用再加列表,所以为单层字典)

# 定义一种关系
class PayMoney(Relationship):
    pass
# 增加一条关系
def match_a_rel(relclass=PayMoney, graph=graph, rel_template_dict=rel_template_dict, rid_name='rel_id', new_rel_dict=None):
    assert all([rel_template_dict, new_rel_dict]), '关系模板和新关系字典不能为空'
    rmatcher = RelationshipMatcher(graph)
    rid = new_rel_dict.get(rid_name)
    
    from_node = match_a_node(new_node_dict=new_rel_dict.get('from_node_dict'))['node']
    to_node = match_a_node(new_node_dict=new_rel_dict.get('to_node_dict'))['node']

    assert all([rid, isinstance(from_node, Node), isinstance(to_node, Node)]), 'rid musth be %s, from and to node must be Node Class' % rid_name
    para_dict = {rid_name: rid}

    some_rel = rmatcher.match(**para_dict).first()
    
    rel_attrs = new_rel_dict.get('rel_dict')

    if some_rel is None:
        exist_tag = False
        some_rel = relclass(from_node, to_node, **rel_attrs)
    else:
        exist_tag = True
    res_dict = {}
    res_dict['exist_tag'] = exist_tag
    res_dict['rel'] = some_rel
    return res_dict
  • 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
  • 49

一个例子

standard_rel_data_dict = {}

from_node_dict = {}
from_node_dict['labels'] = []
from_node_dict['attrs'] = {}
from_node_dict['attrs']['eid'] = '123'
from_node_dict['attrs']['name'] = 'abc'

to_node_dict = {}
to_node_dict['labels'] = []
to_node_dict['attrs'] = {}
to_node_dict['attrs']['eid'] = '456'
to_node_dict['attrs']['name'] = 'def'

rel_dict = {}
rel_dict['rel_id'] = '789'


standard_rel_data_dict['from_node_id'] = from_node_dict.get('attrs').get('eid')
standard_rel_data_dict['to_node_id'] = to_node_dict.get('attrs').get('eid')
standard_rel_data_dict['rel_id'] = rel_dict.get('rel_id')
standard_rel_data_dict['from_node_dict'] = from_node_dict
standard_rel_data_dict['to_node_dict'] = to_node_dict
standard_rel_data_dict['rel_dict'] = rel_dict

match_a_rel(relclass=Call, new_rel_dict= standard_rel_data_dict)
  • 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

2.4 Subgraph

在数据量变动很小的时候才会单个节点,单个节点的更新,通常情况下,使用Subgraph批量的读写会比较快。(大约5000节点/秒)

ogm类似sqlalchemy

Some Notes:

1. 关于节点和关系的建立
+ 建立节点和关系之前最好先查找一下是否已经存在这个节点了。如果已经存在的话,则建立关系的时候使用自己查找到的这个节点,而不要新建,否则会出现一个新的节点。
+ 如果一条条建立关系和节点的话,速度并不快。如果条件合适的话,最好还是用Neo4j的批量导入功能。不过因为在本项目里面并不适合使用批量导入功能,所以只能一条条的插入

2. 索引
+ 在Neo4j 2.0版本以后,尽量使用schema index,而不要使用旧版本的索引。
+ 最好在插入数据之前就建立好索引,否则索引的建立会很消耗时间。
+ 索引建立是非常有必要的,一个索引可以很大程度上降低大规模数据的查询速度。
+ 以我自己为例,这次做项目的时候因为第一次没有加入索引,当数据规模达到4w节点,7w+关系的时候,查询一个节点以及相关联的节点关系所消耗的时间已经达到了1s。
+ 在对节点按照name属性建立了索引之后,截止我写这篇blog的时候,数据量为节点600w+,关系1100w+,查询一个节点以及相关联的节点关系消耗的时间和数据量很小的时候几乎没有什么差别,基本上都稳定在16~20ms左右。

3. 图数据库应该存什么数据(关系和权值.统计量)
+ 我们在使用图数据库的时候必须要明确一点,图数据库能够帮助我们的是以尽量快的速度找出来不同的节点之间的关系。因此向一个节点或者关系里面插入很多其余无关的数据是完全没有必要的,会很大程度浪费硬盘资源,在检索的时候也会消耗更多的时间。
+ 所以务必牢记,图数据库的本质是为了方便查找不同的人或者事物之间的关系的,而不是为了存储数据的。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3 使用容器部署Neo4j

3.1 编辑docker-compose文件

文件目录如下(conf文件夹不一定要,plugins可能以后要,data用于存放数据库文件,log是日志)
project
├── docker-compose.yaml
├── neo4j
│ ├── conf
│ │ └── neo4j.conf
│ ├── data
│ └── log

version: '2'
services:

  neo4j:
    image: neo4j:3.5.8
    container_name: neo4j
    restart: always
    privileged: true
    volumes:
      - ./neo4j/data:/data:rw
      - ./neo4j/logs:/var/lib/neo4j/logs:rw
      - ./neo4j/conf:/var/lib/neo4j/conf:rw
    
    ports:
      - "7474:7474"
      - "7687:7687"
    environment:
      - NEO4J_AUTH=neo4j/xxxx
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

将文件夹挪到 /opt/project 下

docker-compose up --build -d
  • 1

在这里插入图片描述

3.2 开启外网防火墙

在这里插入图片描述

3.3 访问测试(使用python)

import pandas as pd
import numpy as np


from py2neo import Graph, Node, Relationship, Subgraph, NodeMatcher, RelationshipMatcher
import time
graph = Graph(
    "http://111.111.111.111:7474",
    username="neo4j",
    password="12345678"
)

def make_a_node(**kw):
    eid = kw.get('attrs').get('eid')
    ename = kw.get('attrs').get('name')
    assert all([eid, ename]), '需要eid和name'

    # 这里才是创建节点,所以kw要有attr键值
    labels = kw.get('labels')
    attrs = kw.get('attrs')
    return Node(*labels, **attrs)


some_node_dict = {}
some_node_dict['attrs'] = {'eid': 'e001',
                           'name': 'andy'
                           }
some_node_dict['labels'] =['T1']
    
A = make_a_node(**some_node_dict)
graph.create(A)
graph.push(A)
  • 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

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/161178
推荐阅读
相关标签
  

闽ICP备14008679号