GraphRedis 是一个纯PHP实现的轻量级图数据库,使用Redis作为存储后端。项目采用单文件架构设计,提供完整的图数据库功能,包括节点管理、边操作、图遍历算法等。
# 节点属性存储 (Hash)
node:1 -> {
"name": "Alice",
"age": "25",
"city": "北京",
"occupation": "程序员"
}
# 节点ID计数器 (String)
global:node_id -> "1000"
# 出边存储 (Sorted Set)
edge:1:out -> {
"2": 1.0, # 节点1 -> 节点2,权重1.0
"3": 2.5 # 节点1 -> 节点3,权重2.5
}
# 入边存储 (Sorted Set)
edge:2:in -> {
"1": 1.0 # 节点1 -> 节点2
}
# 边属性存储 (Hash)
edge_prop:1:2 -> {
"type": "friend",
"since": "2023-01-01",
"weight_desc": "好友关系"
}
核心设计思想:每条有向边 A -> B 在Redis中存储为两个条目:
edge:A:out 中添加成员 B(出边索引)edge:B:in 中添加成员 A(入边索引)addNode(array $prop): int源码分析:
public function addNode(array $prop): int
{
// 1. 生成全局唯一ID
$counterKey = $this->database === 0 ? 'global:node_id' : "global:node_id:db{$this->database}";
$id = $this->redis->incr($counterKey); // 原子递增
// 2. 存储节点属性
if (!empty($prop)) {
$this->redis->hMSet("node:$id", $prop); // Hash存储
}
return $id;
}
关键设计:
INCR 命令保证ID的原子性和唯一性getNode(int $id): ?array源码分析:
public function getNode(int $id): ?array
{
$raw = $this->redis->hGetAll("node:$id");
return $raw ?: null; // 空数组转换为null
}
性能特点:
addEdge(int $from, int $to, float $weight = 1.0, array $prop = []): void源码分析:
public function addEdge(int $from, int $to, float $weight = 1.0, array $prop = []): void
{
$pipe = $this->redis->multi(); // 开启事务
// 双向索引更新
$pipe->zAdd("edge:$from:out", $weight, $to); // 出边
$pipe->zAdd("edge:$to:in", $weight, $from); // 入边
// 边属性存储
if (!empty($prop)) {
$pipe->hMSet("edge_prop:$from:$to", $prop);
}
$pipe->exec(); // 原子提交
}
关键特性:
neighbors(int $id, string $dir = 'out', int $page = 1, ?int $size = null): array源码分析:
public function neighbors(int $id, string $dir = 'out', int $page = 1, ?int $size = null): array
{
$size = $size ?: $this->pageSize; // 默认分页大小
$key = "edge:$id:$dir"; // 动态键名
$start = ($page - 1) * $size; // 分页计算
$stop = $start + $size - 1;
return $this->redis->zRange($key, $start, $stop, true); // 返回 [id=>weight]
}
性能优化:
public function shortestPath(int $from, int $to, int $maxDepth = 6): ?array
{
if ($from === $to) {
return [0, [$from]]; // 自环处理
}
$q = new SplQueue(); // 队列实现BFS
$q->enqueue([$from, 0, [$from]]);
$seen = [$from => true]; // 访问标记
while (!$q->isEmpty()) {
[$id, $depth, $path] = $q->dequeue();
if ($depth >= $maxDepth) { // 深度限制
continue;
}
foreach ($this->neighbors($id, 'out', 1, 100) as $next => $weight) {
if ($next == $to) {
return [$depth + 1, array_merge($path, [$next])];
}
if (!isset($seen[$next])) {
$seen[$next] = true;
$q->enqueue([$next, $depth + 1, array_merge($path, [$next])]);
}
}
}
return null; // 无路径
}
算法特点:
多环境支持:
// 开发环境
$devGraph = new GraphRedis('127.0.0.1', 6379, 0, 0);
// 测试环境
$testGraph = new GraphRedis('127.0.0.1', 6379, 0, 1);
// 生产环境
$prodGraph = new GraphRedis('127.0.0.1', 6379, 0, 2);
隔离机制:
global:node_id:db{database}级联删除示例:
public function delNode(int $id): void
{
// 1. 预查询(事务外)
$out = $this->redis->zRange("edge:$id:out", 0, -1);
$in = $this->redis->zRange("edge:$id:in", 0, -1);
// 2. 原子删除(事务内)
$pipe = $this->redis->multi();
// 清理出边
if ($out) {
foreach ($out as $to) {
$pipe->del("edge_prop:$id:$to"); // 删除边属性
$pipe->zRem("edge:$to:in", $id); // 删除反向索引
}
}
$pipe->del("edge:$id:out");
// 清理入边
if ($in) {
foreach ($in as $from) {
$pipe->del("edge_prop:$from:$id");
$pipe->zRem("edge:$from:out", $id);
}
}
$pipe->del("edge:$id:in");
// 删除节点
$pipe->del("node:$id");
$pipe->exec();
}
图统计功能:
public function getStats(): array
{
$counterKey = $this->database === 0 ? 'global:node_id' : "global:node_id:db{$this->database}";
$nodeCount = $this->redis->get($counterKey) ?: 0;
$edgeCount = 0;
// 统计边数量
$pattern = 'edge:*:out';
$keys = $this->redis->keys($pattern);
foreach ($keys as $key) {
$edgeCount += $this->redis->zCard($key);
}
return [
'nodes' => (int) $nodeCount,
'edges' => $edgeCount,
'memory_usage' => $this->redis->info('memory')['used_memory_human'] ?? 'N/A'
];
}
// 创建社交网络
$alice = $graph->addNode(['name' => 'Alice', 'followers' => 1000]);
$bob = $graph->addNode(['name' => 'Bob', 'followers' => 500]);
// 建立关注关系
$graph->addEdge($alice, $bob, 1.0, ['type' => 'follow', 'since' => '2023-01-01']);
// 分析影响力
$outDegree = count($graph->neighbors($alice, 'out')); // 关注数
$inDegree = count($graph->neighbors($alice, 'in')); // 粉丝数
$influence = $outDegree + $inDegree;
// 推荐好友(二度关系)
$friends = $graph->neighbors($alice, 'out');
foreach ($friends as $friendId => $weight) {
$friendsOfFriend = $graph->neighbors($friendId, 'out');
// 推荐逻辑...
}
// 用户-商品关系图
$user = $graph->addNode(['name' => 'User1', 'age' => 25]);
$product = $graph->addNode(['name' => 'iPhone', 'category' => 'Electronics']);
// 行为记录
$graph->addEdge($user, $product, 8.5, [
'action' => 'purchase',
'rating' => 5,
'timestamp' => time()
]);
// 协同过滤推荐
$path = $graph->shortestPath($user1, $user2);
if ($path && $path[0] <= 3) {
// 相似用户,可以推荐其购买的商品
}
// 概念关系建模
$php = $graph->addNode(['name' => 'PHP', 'type' => 'language']);
$redis = $graph->addNode(['name' => 'Redis', 'type' => 'database']);
$graph->addEdge($php, $redis, 1.0, ['relation' => 'can_connect_to']);
// 知识推理
$related = $graph->dfs($php, 2); // 查找PHP相关的概念
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 添加节点 | O(1) | Redis INCR + HMSET |
| 查询节点 | O(1) | Redis HGETALL |
| 添加边 | O(log N) | Redis ZADD操作 |
| 查询邻居 | O(log N + M) | ZRANGE查询,M为返回数量 |
| BFS最短路径 | O(V + E) | 标准BFS算法 |
| DFS遍历 | O(V + E) | 标准DFS算法 |
存储效率:
class GraphRedisManager {
private static $instances = [];
public static function getInstance($database = 0) {
if (!isset(self::$instances[$database])) {
self::$instances[$database] = new GraphRedis(
'127.0.0.1', 6379, 0, $database
);
}
return self::$instances[$database];
}
}
// 批量插入节点
$pipe = $graph->getRedis()->multi();
for ($i = 0; $i < 1000; $i++) {
$pipe->hMSet("node:$i", ['batch' => 'insert']);
}
$pipe->exec();
try {
$graph = new GraphRedis('127.0.0.1', 6379, 0, 0);
$nodeId = $graph->addNode(['name' => 'Test']);
} catch (\RedisException $e) {
error_log("Redis连接失败: " . $e->getMessage());
} catch (\InvalidArgumentException $e) {
error_log("参数错误: " . $e->getMessage());
}
// 性能监控
$start = microtime(true);
$result = $graph->shortestPath($from, $to);
$duration = microtime(true) - $start;
echo "查询耗时: {$duration}ms\n";
// 内存使用
$stats = $graph->getStats();
echo "内存使用: {$stats['memory_usage']}\n";
GraphRedis 以其简洁的设计和强大的功能,为PHP开发者提供了一个优秀的图数据库解决方案。通过深度利用Redis的数据结构特性,实现了高性能的图数据存储与查询。
核心优势: