把人和人之间的关注关系、商品和订单之间的购买关系塞进MySQL,做多层级关联查询时往往要写好几层JOIN,数据量一大速度就顶不住了。图数据库换了一种思路:把关系本身当作一等公民存储起来,查询时沿着边遍历即可。RedisGraph是Redis生态里的图数据库模块,它把这种能力直接带进了Redis进程,用Cypher这种声明式语言描述图查询。这篇文章从安装部署开始,带你一步步掌握RedisGraph的建模方式和Cypher的核心语法。

RedisGraph是什么,它凭什么值得学
RedisGraph是遵循Redis Modules规范开发的一个模块,编译成动态链接库之后,通过--loadmodule参数加载进Redis服务进程。加载成功后,Redis就多出一组以GRAPH开头的命令,例如GRAPH.QUERY、GRAPH.DELETE、GRAPH.RO_QUERY等。客户端执行图查询和执行普通的GET、SET没有本质区别,请求和响应都走同一套RESP协议,这意味着几乎所有支持自定义命令的Redis客户端都能直接使用它,不需要额外的驱动。
性能层面,RedisGraph没有采用传统图数据库那种邻接表加指针跳转的实现,而是把整个图编码成稀疏邻接矩阵,底层调用GraphBLAS这个线性代数运算库来完成矩阵运算。一次模式匹配本质上被翻译成若干次稀疏矩阵乘法,这种实现在多跳查询场景下扩展性很好,再配合Redis本身基于内存的存储结构,常见查询的延迟能压到毫秒甚至微秒级别。
和Neo4j这类需要独立部署的图数据库相比,RedisGraph的定位明显更轻量:不用单独搭建一套服务,和缓存共用一个Redis实例就行;数据模型也相对简单,适合中小规模的图场景,例如社交关系分析、推荐召回、轻量知识图谱等。如果你的业务已经重度使用Redis,引入RedisGraph的额外学习成本基本只剩Cypher语法这一块,上手门槛相当低。
图数据模型:节点、标签、关系与属性
RedisGraph里的图由四类元素构成。节点代表实体,在Cypher里用一对圆括号表示;标签是节点的类型,作用类似关系库里的表名,一个节点可以同时挂多个标签;关系连接两个节点,用方括号加箭头表示,方向是固定的,从起点指向终点;属性以键值对的形式挂在节点或关系上,支持字符串、数值和布尔值三种类型,暂不支持嵌套结构。把这四个概念吃透之后你会发现,Cypher的所有语法本质上都是在描述这些元素的组合模式。
建图用CREATE语句。在redis-cli里执行时,外层是GRAPH.QUERY命令,第一个参数是图的名字,第二个参数才是Cypher语句本身。图名对应Redis里的一个key,第一次执行CREATE时会自动建图,完全不需要提前声明schema,这种无模式设计让业务迭代非常灵活。下面这条语句创建了两个人物节点,以及一条从张三指向李四的认识关系,属性直接写在花括号里。
GRAPH.QUERY 社交图 "CREATE (:人物 {姓名: '张三', 年龄: 30})-[:认识 {年份: 2019}]->(:人物 {姓名: '李四', 年龄: 28})"
给已经存在的节点补建关系时,需要先用MATCH把节点定位出来并绑定到变量上,然后在CREATE里引用这些变量。变量只在单条查询的范围内有效,这是Cypher和SQL一个显著的区别:Cypher的查询过程更像画图,你先圈出要操作的元素,再描述对这些元素执行的动作。为了排版简洁,后文的示例只展示纯Cypher语句,实际执行时套上GRAPH.QUERY和图名即可。
MATCH (a:人物 {姓名: '张三'}), (b:人物 {姓名: '王五'})
CREATE (a)-[:认识 {年份: 2021}]->(b)
MATCH模式匹配:Cypher查询的核心
Cypher的灵魂是模式匹配。MATCH子句里画出你想找的图形结构,引擎负责在数据中找出所有满足该结构的组合,RETURN子句决定返回哪些字段。比如查询张三认识的所有人,只需要把建图时的CREATE语句改成MATCH,把CREATE关键字换成RETURN,写法和思维模型完全一致,这也是Cypher被称为图形化语言的原因。
MATCH (a:人物 {姓名: '张三'})-[:认识]->(b:人物)
RETURN a.姓名, b.姓名
过滤条件用WHERE子句表达,支持比较运算、逻辑组合、字符串模糊匹配等常见操作。WHERE既能作用于节点属性,也能作用于关系属性,比如找出2020年及以后建立的认识关系。如果查询时不关心方向,比如好友这种天然双向的关系,把箭头去掉写成一条普通连线就行,引擎会做双向匹配。
MATCH (a:人物)-[r:认识]->(b:人物) WHERE r.年份 >= 2020 RETURN a.姓名, b.姓名, r.年份
多跳查询才是图数据库真正的杀手锏。想找张三朋友的朋友,只需要在模式里连续画两条边;想查某个范围内的所有可达节点,用变长路径语法指定跳数区间即可。这类查询放到SQL里意味着多层自连接加去重,写起来痛苦执行起来更痛苦,而Cypher里只是把箭头多写几次,语义清晰,执行计划也由引擎自动优化。
MATCH (a:人物 {姓名: '张三'})-[:认识*1..3]->(b:人物)
RETURN DISTINCT b.姓名
聚合统计与最短路径查询
查出来的数据经常需要进一步加工处理。Cypher提供了ORDER BY排序、LIMIT截取、SKIP跳过等子句,用法和SQL基本一致,组合起来就能实现分页。聚合函数方面,COUNT、SUM、AVG、COLLECT都比较常用,其中COLLECT会把匹配到的值聚合成一个列表,做分组汇总时特别顺手。下面这条语句统计每个人认识的人数,按好友数从多到少排列并取前十条。
MATCH (a:人物)-[:认识]->(b:人物) RETURN a.姓名, COUNT(b) AS 好友数 ORDER BY 好友数 DESC LIMIT 10
路径查询方面,RedisGraph实现了最短路径函数shortestPath。给定起点和终点,它会返回两者之间边数最少的那条路径,路径上的每个节点和关系都可以展开查看。社交场景里的共同好友分析、推荐系统里的关系链路追溯、风控里的担保圈识别,都能借助这个能力落地。需要留意的是,最短路径的两端必须绑定到具体的节点,不能是泛化的模式。
MATCH p = shortestPath((a:人物 {姓名: '张三'})-[:认识*]->(b:人物 {姓名: '赵六'}))
RETURN p
在应用代码里调用RedisGraph
服务端部署最省事的方式是用官方提供的Docker镜像,一条命令就能跑起一个自带图模块的Redis实例,适合本地开发和快速验证。生产环境建议从源码编译,把编译产物redisgraph.so放到固定目录,在redis.conf里配置好loadmodule路径,这样每次重启模块都会自动加载。验证模块是否就绪很简单,执行MODULE LIST命令,看返回列表里有没有graph相关的条目。
docker run -p 6379:6379 -it --rm redislabs/redisgraph
客户端层面,Python的redis-py通过execute_command就能发送GRAPH.QUERY命令。返回结果是一个嵌套结构,第一层是表头信息,从第二层开始是数据行,需要自己写一点解析逻辑把它转成字典。下面是一个完整示例,查询好友数排名前五的人物并把结果整理成字典列表打印出来。
import redis
r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
query = "MATCH (p:人物)-[:认识]->(friend:人物) RETURN p.姓名, COUNT(friend) AS 好友数 ORDER BY 好友数 DESC LIMIT 5"
raw = r.execute_command('GRAPH.QUERY', '社交图', query)
# raw[0] 是表头列表,raw[1] 开始是数据行
header = [col[1] for col in raw[0]]
rows = [dict(zip(header, row)) for row in raw[1]]
for item in rows:
print(item)
实际使用中有几条经验值得记下。图名就是Redis的key,会占用键空间,命名时最好加统一前缀方便管理;在大图上做无条件的全量MATCH开销不小,查询尽量带上标签和属性条件,让引擎缩小搜索范围;图的持久化依赖Redis自身的RDB和AOF机制,重启前务必确认持久化策略已开启,否则图数据会全部丢失;读多写少的场景可以用GRAPH.RO_QUERY执行只读查询,避免读流量干扰写入。另外,RedisGraph的官方开发已经停止,现有版本仍然可以正常使用,但新项目选型时不妨顺带评估FalkorDB这类兼容Cypher语法的后续项目,学到的查询语法几乎可以无缝迁移。
整体来看,RedisGraph把图查询的门槛降到了Redis用户触手可及的位置:会Redis就能部署,学一门Cypher就能查询。从单机关系分析到轻量推荐场景,它都能胜任。把本文的示例跑通之后,建议拿自己业务里的真实关系数据建一张小图练手,多跳查询和路径分析这些能力,只有亲手写过才能真正体会到它和SQL多层JOIN之间的差距。

