Loading...
正在加载...
请稍候

本站Redis全文索引构建机制详解

✨步子哥 (steper) 2025年10月09日 13:22

🏗️ 架构概述

基于我对代码的深入分析,该项目使用RediSearch模块构建了一套完整的全文索引系统。整个索引机制采用分层异步架构,确保索引构建不影响主业务流程的响应性能。

📊 索引结构设计

1. 三套独立索引体系

系统设计了三套独立的RediSearch索引,分别处理不同类型的搜索需求:

Topic索引 (idx:topic)

// 索引键格式:zhichai:search:topic:{id}
$this->redis->rawCommand(
    'FT.CREATE', 'idx:topic',
    'ON', 'HASH',
    'PREFIX', '1', $redisPrefix . 'search:topic:',
    'SCHEMA',
    'title', 'TEXT', 'WEIGHT', '5',        // 标题权重最高
    'content', 'TEXT', 'WEIGHT', '1',      // 内容基础权重
    'author', 'TEXT', 'WEIGHT', '1.5',     // 作者权重中等
    'created_at', 'NUMERIC', 'SORTABLE',   // 支持时间排序
    'reply_count', 'NUMERIC', 'SORTABLE',  // 支持热度排序
    'is_hidden', 'TAG',                    // 隐藏状态过滤
    'is_closed', 'TAG'                     // 关闭状态过滤
);

Reply索引 (idx:reply)

// 索引键格式:zhichai:search:reply:{id}
'SCHEMA',
'content', 'TEXT', 'WEIGHT', '2',         // 回复内容权重高
'author', 'TEXT', 'WEIGHT', '1',          // 作者权重基础
'topic_title', 'TEXT', 'NOINDEX',         // 仅显示,不参与搜索
'topic_id', 'TAG',                        // 话题关联
'created_at', 'NUMERIC', 'SORTABLE',
'is_hidden', 'TAG'

User索引 (idx:user)

// 索引键格式:zhichai:search:user:{id}
'SCHEMA',
'nickname', 'TEXT', 'WEIGHT', '3',        // 昵称权重最高
'username', 'TEXT', 'WEIGHT', '2',        // 用户名权重中等
'created_at', 'NUMERIC', 'SORTABLE',
'status', 'TAG'                           // 用户状态过滤

🔄 索引构建流程

1. 自动索引确保机制

系统采用懒加载策略,在首次搜索时自动确保索引存在:

public function ensureIndexes(): void
{
    $indexes = ['idx:topic', 'idx:reply', 'idx:user'];
    
    foreach ($indexes as $idx) {
        try {
            // 尝试获取索引信息
            $this->redis->rawCommand('FT.INFO', $idx);
        } catch (\RedisException $e) {
            // 索引不存在,自动创建
            error_log("[SearchService] 索引 {$idx} 不存在,开始创建");
            $this->createIndex($idx);
        }
    }
}

2. 实时索引更新机制

每当有新数据创建或更新时,系统会立即更新对应的搜索索引:

Topic索引更新

public function indexTopic(array $topic): void
{
    $docKey = "search:topic:" . $topic['id'];
    
    // 内容长度限制,防止索引过大
    $content = $topic['content'] ?? '';
    if (strlen($content) > 10000) {
        $content = substr($content, 0, 10000);
    }
    
    $hash = [
        'title'       => $topic['title'] ?? '',
        'content'     => $content,
        'author'      => $topic['author_nickname'] ?? '',
        'created_at'  => (string)(strtotime($topic['created_at'] ?? 'now')),
        'reply_count' => (string)($topic['reply_count'] ?? 0),
        'is_hidden'   => $topic['is_hidden'] ? '1' : '0',
        'is_closed'   => $topic['is_closed'] ? '1' : '0',
    ];

    try {
        // 直接写入Redis Hash,RediSearch自动监听
        $this->redis->hMSet($docKey, $hash);
        error_log("[SearchService] 成功索引 Topic {$topic['id']}");
    } catch (\RedisException $e) {
        // 索引失败不影响主流程,仅记录日志
        error_log("[SearchService] 索引 Topic {$topic['id']} 失败: " . $e->getMessage());
    }
}

3. 异步队列处理

为了不影响主业务性能,索引更新通过异步队列进行处理:

// 在TopicService中的异步创建流程
public function createTopicAsync($userID, $title, $content, $creationTime = null)
{
    // ... 创建Topic逻辑 ...
    
    try {
        // 1. 立即写入Redis(同步)
        $this->redis->hMSet($topicKey, $topicData);
        $this->redis->zAdd($this->topicIndexKey, $timestamp, $topicID);
        
        // 2. 异步写入SQLite队列
        $this->asyncWriter->enqueue('INSERT', 'topics', $topicArray);
        
        // 3. 异步更新搜索索引
        $searchService = new SearchService();
        $searchService->indexTopic($topicArray);
        
    } catch (\Exception $e) {
        // 错误处理和回滚机制
    }
}

🔍 搜索查询机制

1. 智能搜索策略

系统使用通配符模式支持中文和部分匹配:

public function search(string $q, string $scope = 'all', int $page = 1, int $pageSize = 200): array
{
    // 转义特殊字符
    $escapedTerm = $this->escapeSearchTerm($clean);
    
    // 使用通配符包装,支持部分匹配
    $pattern = "*{$escapedTerm}*";
    
    // 根据权限过滤隐藏内容
    if (!$includeHidden) {
        $topicPattern = "({$pattern}) @is_hidden:{0}";
    }
}

2. 多维度排序算法

搜索结果采用智能排序策略:

// 1. 按相关性得分排序(TF-IDF算法)
// 2. 相同得分时按创建时间倒序
usort($allDocs, function($a, $b) {
    // 先比较相关性得分(命中次数越多得分越高)
    $scoreDiff = $b['_score'] <=> $a['_score'];
    if ($scoreDiff !== 0) {
        return $scoreDiff;
    }
    
    // 得分相同时,按创建时间降序(最新的在前)
    $timeA = (int)($a['created_at'] ?? 0);
    $timeB = (int)($b['created_at'] ?? 0);
    return $timeB <=> $timeA;
});

3. 权重分配策略

不同字段的权重设计体现了业务优先级:

  • Topic: title(5) > author(1.5) > content(1)
  • Reply: content(2) > author(1)
  • User: nickname(3) > username(2)

🚀 全量重建机制

系统提供了完整的索引重建脚本 rebuild_search_index.php

1. 重建流程

// Step 1: 清理旧文档
$searchPattern = 'search:*';
do {
    $keys = $redis->scan($it, $searchPattern, 200);
    foreach ($keys as $k) {
        $redis->del($k);
        $countDel++;
    }
} while ($it !== 0);

// Step 2: 删除并重建索引
foreach (['idx:topic', 'idx:reply', 'idx:user'] as $idx) {
    $redis->rawCommand('FT.DROPINDEX', $idx, 'DD');
}
$searchService->ensureIndexes();

// Step 3: 扫描并索引所有数据
$stmt = $pdo->query("SELECT * FROM topics ORDER BY created_at DESC");
foreach ($stmt->fetchAll() as $topicData) {
    $searchService->indexTopic($topicArray);
}

2. 性能优化

  • 批量处理: 每100条记录输出进度
  • 错误隔离: 单个文档失败不影响整体重建
  • 内存控制: 大内容自动截断防止内存溢出

💾 缓存策略

1. 搜索结果缓存

// 生成缓存键
$cacheKey = 'search:cache:' . md5($clean . $scope . $page . $pageSize . ($includeHidden ? '1' : '0'));

// 缓存搜索结果(TTL 60秒,平衡实时性与性能)
$this->redis->setex($cacheKey, 60, json_encode($results));

2. 前缀统一管理

所有Redis键都通过RedisManager统一管理前缀:

// config.php中的配置
'redis' => [
    'prefix' => 'zhichai:'
]

// RedisManager自动应用前缀
$redis->setOption(Redis::OPT_PREFIX, 'zhichai:');

🛡️ 容错处理

1. 超时保护

try {
    // Redis操作依赖连接的默认超时
    $this->redis->hMSet($docKey, $hash);
} catch (\RedisException $e) {
    // 索引失败不影响主流程,只记录日志
    error_log("[SearchService] 索引失败: " . $e->getMessage());
}

2. 降级策略

  • 核心功能优先: Topic/Reply创建必须成功
  • 辅助功能容错: 搜索索引失败仅记录日志
  • 异步重试: 通过异步队列支持重试机制

📈 性能特性

1. 查询性能

  • 索引查询: O(log N) 时间复杂度
  • 全文搜索: 毫秒级响应时间
  • 分页支持: 高效的LIMIT/OFFSET实现

2. 写入性能

  • 异步索引: 不阻塞主业务流程
  • 批量处理: 减少Redis往返次数
  • 内容截断: 防止大文档影响性能

🎯 总结

该Redis全文索引系统体现了以下设计亮点:

  1. 分层架构: 清晰的职责分离,便于维护和扩展
  2. 异步处理: 保证主业务性能不受索引构建影响
  3. 智能搜索: 支持中英文混合、部分匹配、权重排序
  4. 容错机制: 完善的错误处理和降级策略
  5. 运维友好: 提供完整的重建和监控工具

这套索引系统不仅满足了当前的全文搜索需求,还为未来的功能扩展预留了充分的灵活性。

讨论回复

加载中...
正在加载回复...

正在加载回复...

推荐
智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录