307 lines
16 KiB
Markdown
307 lines
16 KiB
Markdown
# dht-search
|
|
|
|
`dht-search` 是集 DHT Metadata 采集 RocksDB 持久化 Tantivy 搜索索引和 HTTP API 于一体的应用
|
|
|
|
## 构建准备
|
|
|
|
RocksDB 包含 C++ 代码并在构建时使用 bindgen 因此需要 libclang
|
|
|
|
Windows 可以把 libclang 安装到工作区本地目录
|
|
|
|
```powershell
|
|
python -m pip install --target .tools\libclang libclang
|
|
$env:LIBCLANG_PATH = "$PWD\.tools\libclang\clang\native"
|
|
cargo build -p dht-search --release
|
|
```
|
|
|
|
`.tools` 只用于本地构建不会部署到运行设备
|
|
|
|
## 配置
|
|
|
|
复制根目录的配置模板
|
|
|
|
```powershell
|
|
Copy-Item dht-search.example.toml dht-search.toml
|
|
```
|
|
|
|
相对 `data_dir` 以配置文件所在目录为基准解析
|
|
|
|
`content_filter_file` 指向独立的无效文件过滤配置 相对路径同样以主配置文件所在目录为基准解析 推荐直接使用根目录的 `content-filters.toml`
|
|
|
|
也可以通过命令行覆盖数据目录和本次运行时长
|
|
|
|
```powershell
|
|
cargo run -p dht-search -- --data-dir D:\data\dht-search --run-duration-secs 3600
|
|
```
|
|
|
|
不设置 `run-duration-secs` 时服务持续运行直到收到 Ctrl+C SIGINT 或 SIGTERM
|
|
|
|
生产 Web 页面需要先在 `web` 目录执行 `bun run build` Axum 会从 `http.web_dir` 提供构建结果
|
|
|
|
### 当前运行模板网络配置
|
|
|
|
`dht-search.example.toml` 默认使用经过本机一分钟资源测试的 Bitmagnet 等效激进配置 主动 DHT 查询仍共享 `max_outbound_queries_per_second` 总预算 所有队列保持有界 可以按设备和网络条件主动下调
|
|
|
|
| 配置项 | 运行模板值 | 作用 |
|
|
|---|---:|---|
|
|
| `max_outbound_queries_per_second` | `1000` | 三类主动 DHT UDP 查询的合计每秒速率硬上限 |
|
|
| `outbound_query_burst` | `200` | 空闲后允许立即消费的 UDP 查询数 |
|
|
| `find_node_queries_per_second` | `10` | `find_node` 自身速率上限 |
|
|
| `find_node_max_in_flight` | `100` | 同时等待响应的 `find_node` 数量 |
|
|
| `new_destinations_per_minute` | `12000` | 每分钟首次探测的新 UDP 目标数量 |
|
|
| `peer_lookups_per_second` | `200` | 每秒启动的 infohash Peer 查找数量 |
|
|
| `peer_lookup_max_active` | `200` | 同时运行的 Peer 查找数量 |
|
|
| `sample_queries_per_second` | `60` | BEP-51 采样查询速率 |
|
|
| `sample_max_in_flight` | `100` | 同时等待响应的 BEP-51 采样请求数量 |
|
|
| `sample_new_node_percent` | `50` | 按节点地址稳定分流到直接采样通道的比例 |
|
|
| `sample_candidate_queue_capacity` | `8192` | 新发现节点直接采样通道的有界容量 |
|
|
| `sample_fallback_to_iterative` | `false` | 采样节点没有返回 Peer 时快速结束以扩大覆盖面 |
|
|
| `metadata_workers` | `400` | 同时处理的 Metadata 任务数量 |
|
|
| `metadata_connects_per_second` | `400` | 每秒真正开始的 Peer TCP 连接硬上限 |
|
|
|
|
Metadata 下载和可用性握手共用 `metadata_connects_per_second` 预算不会各自叠加
|
|
|
|
### 按需可用性验证
|
|
|
|
搜索结果和详情访问只会把已过冷却期的种子异步加入持久化验证队列 HTTP 响应不会等待 DHT 或 Peer 网络
|
|
|
|
| 配置项 | 默认值 | 作用 |
|
|
|---|---:|---|
|
|
| `verification.enabled` | `true` | 是否启用按需可用性验证 |
|
|
| `verification.queue_capacity` | `10000` | 持久化验证队列容量 |
|
|
| `verification.max_active` | `2` | 同时验证的种子数量 |
|
|
| `verification.max_peer_attempts` | `3` | 每个种子最多握手的 Peer 数量 |
|
|
| `verification.lease_secs` | `60` | 异常退出后验证任务重新可领取的租约时间 |
|
|
| `verification.poll_interval_millis` | `250` | 持久化队列轮询间隔 |
|
|
|
|
详情访问使用高优先级 搜索结果使用普通优先级 队列满时高优先级可以替换最旧普通任务
|
|
|
|
可用性分为 `unknown` `active` 和 `possibly_stale` 一次或多次验证失败只表示当前可能没有可连接 Peer 不会删除种子
|
|
|
|
新抓取记录会把成功下载 Metadata 的来源 Peer 视为一次有效验证 旧记录按需复查时会同时使用新 DHT 结果和已保存的成功来源 Peer
|
|
|
|
热度是近期 DHT 发现强度最近出现时间和可连接 Peer 数的综合活跃度分数 不代表全球下载量
|
|
|
|
这组参数仍保留全局速率限制 如果同机代理或路由器再次出现不稳定应优先降低总 UDP 预算和 Metadata 建连速率
|
|
|
|
Windows 下索引每五秒批量提交 临时文件占用会自动指数退避重试且不会停止采集 HTTP 服务或丢失 RocksDB 待索引状态
|
|
|
|
### 磁盘空间保护
|
|
|
|
应用默认每十秒检查 `data_dir` 所在磁盘的剩余空间 低于保护阈值时先停止接收新 Metadata DHT 状态更新索引任务和可用性验证任务 已经进入有界持久化队列的记录会继续排空 随后进入只读保护
|
|
|
|
只读保护期间 RocksDB 和 Tantivy 不再产生业务写入 现有搜索详情健康检查和 Web 页面仍然可用 剩余空间达到独立恢复阈值后自动恢复采集 使用两个阈值可以避免临界空间附近反复暂停和恢复
|
|
|
|
| 配置项 | 默认值 | 作用 |
|
|
|---|---:|---|
|
|
| `disk_guard.enabled` | `true` | 是否启用磁盘空间保护 |
|
|
| `disk_guard.check_interval_secs` | `10` | 剩余空间检查间隔 |
|
|
| `disk_guard.minimum_free_bytes` | `5368709120` | 低于 5 GiB 时停止接收新任务 |
|
|
| `disk_guard.resume_free_bytes` | `6442450944` | 恢复到 6 GiB 时重新接受写入 |
|
|
|
|
磁盘空间探测失败时采用保守策略进入保护状态 `/stats` 返回 `disk_state` `disk_available_bytes` 阈值 活跃写入数 探测失败数 状态转换数和拒绝任务数 Web 运行状态使用绿色或黄色状态点展示正常与保护状态
|
|
|
|
### 日志轮转和保留
|
|
|
|
应用会在读取配置后初始化日志 默认只写入 `data/logs` 的滚动文件而不重复输出到终端 因此用脚本或后台进程启动时不需要再把标准错误重定向到长期增长的日志文件
|
|
|
|
| 配置项 | 默认值 | 作用 |
|
|
|---|---:|---|
|
|
| `logging.directory` | `data/logs` | 日志文件目录 相对主配置文件解析 |
|
|
| `logging.file_enabled` | `true` | 启用滚动文件日志 |
|
|
| `logging.console_enabled` | `false` | 同时输出到当前终端 |
|
|
| `logging.rotation` | `daily` | 轮转周期 支持 `minutely` `hourly` `daily` 和 `never` |
|
|
| `logging.retain_files` | `7` | 最多保留的匹配日志文件数量 |
|
|
| `logging.file_prefix` | `dht-search` | 日志文件名前缀 |
|
|
|
|
默认按天轮转时保留 7 个文件约等于保留最近 7 天 日志组件只清理同目录中同时匹配前缀和 `.log` 后缀的普通文件 不删除目录和符号链接 清理失败会输出错误但不会让服务退出
|
|
|
|
开发时需要直接观察终端日志可以设置 `console_enabled = true` 文件日志和终端日志不能同时关闭
|
|
|
|
### RocksDB 检查点备份和恢复
|
|
|
|
RocksDB 是唯一权威数据源 应用使用 RocksDB 原生 Checkpoint API 在线生成一致快照 备份期间采集可以继续运行 Tantivy 不进入备份因为它可以从 RocksDB 完整重建
|
|
|
|
| 配置项 | 默认值 | 作用 |
|
|
|---|---:|---|
|
|
| `backup.enabled` | `true` | 是否启用自动检查点 |
|
|
| `backup.directory` | `data/backups` | 检查点目录 相对主配置文件解析 |
|
|
| `backup.interval_secs` | `21600` | 每 6 小时创建一次检查点 |
|
|
| `backup.retain_checkpoints` | `3` | 保留最近 3 个自动检查点 |
|
|
| `backup.create_on_start` | `true` | 每次启动后立即创建一次检查点 |
|
|
|
|
备份目录与数据目录位于同一磁盘时 RocksDB 会尽量通过硬链接减少复制开销 放到其他磁盘时可能复制全部数据库文件 创建前会检查备份磁盘剩余空间 磁盘保护期间自动跳过而不会阻塞服务
|
|
|
|
`/stats` 返回检查点成功失败跳过清理数量 最近成功时间耗时和记录数量 自动清理只处理名称严格匹配 `checkpoint-` 加二十位时间戳的直接子目录 不会删除手工目录文件或符号链接
|
|
|
|
恢复必须在服务停止后执行 数据目录独占锁会阻止运行中的服务和恢复命令同时操作
|
|
|
|
```powershell
|
|
target\release\dht-search.exe `
|
|
--config dht-search.example.toml `
|
|
--restore-checkpoint data\backups\checkpoint-00000001775400000000
|
|
```
|
|
|
|
恢复命令会先以只读方式校验源检查点 再复制到数据目录内的暂存目录并二次校验 然后切换 `rocksdb` 目录 原数据库不会删除而是保留为 `rocksdb.pre-restore-*` 方便人工回滚 Tantivy 作为派生数据会被删除 下次正常启动自动从恢复后的 RocksDB 重建
|
|
|
|
确认恢复数据无误后可以人工删除 `rocksdb.pre-restore-*` 释放空间 不要在服务运行时移动或删除这些目录
|
|
|
|
### Metadata 安全限制
|
|
|
|
应用会在 Metadata 下载和进入 RocksDB 前执行两层资源与结构校验
|
|
|
|
| 配置项 | 默认值 | 作用 |
|
|
|---|---:|---|
|
|
| `metadata_limits.max_metadata_bytes` | `10485760` | 下载阶段允许的最大 info 字典字节数 |
|
|
| `metadata_limits.max_files` | `20000` | 单个种子允许的最大文件数量 |
|
|
| `metadata_limits.max_name_bytes` | `1024` | 种子名称最大 UTF-8 字节数 |
|
|
| `metadata_limits.max_path_bytes` | `4096` | 单个文件路径最大 UTF-8 字节数 |
|
|
| `metadata_limits.max_path_depth` | `64` | 单个文件路径最大目录层级 |
|
|
|
|
空名称 空文件列表 控制字符 空路径段 `.` `..` 大小溢出和声明总大小不一致会被分类拒绝
|
|
|
|
通过完整 Metadata 校验后被拒绝的 infohash 只在 RocksDB 保存原因规则指纹时间和次数 不保存名称或文件列表 相同规则下再次发现时不会重复下载 修改限制后规则指纹变化并允许重新判断
|
|
|
|
`/stats` 返回 `metadata_filtered` 总数以及 `metadata_filtered_*` 分类计数 Web 运行状态展示本次运行的过滤总数
|
|
|
|
可以使用独立数据目录和严格限制运行五分钟测试 不会污染正式数据目录
|
|
|
|
```powershell
|
|
cargo run -p dht-search -- --config dht-search.filter-test.toml
|
|
Invoke-RestMethod http://127.0.0.1:8080/stats | ConvertTo-Json -Depth 5
|
|
```
|
|
|
|
严格测试配置仅用于观察过滤效果 不应作为正式采集配置
|
|
|
|
### 无效文件隐藏规则
|
|
|
|
`content-filters.toml` 控制哪些文件不参与详情展示 搜索 文件数量 有效大小和内容聚合 默认规则会隐藏 BitComet `_____padding_file_` 文件以及 `.pad` 和 `.____padding_file` 填充目录
|
|
|
|
RocksDB 始终保存完整原始 Metadata 隐藏规则不会删除文件或种子 修改或回滚规则后应用会根据规则指纹重新计算内容组并从 RocksDB 重建 Tantivy
|
|
|
|
每条规则包含稳定 `id` 开关 匹配字段 匹配方式 值 大小写选项和可读原因 当前字段支持 `file-name` 与 `file-path` 匹配方式支持 `exact` `prefix` `suffix` `contains` `wildcard` 和 `regex` 动作只允许安全的 `hide`
|
|
|
|
通配符中 `*` 表示任意长度字符 `?` 表示一个字符并匹配完整字段 正则表达式使用 Rust `regex` 语法 文件路径在匹配前统一使用 `/` 分隔符
|
|
|
|
如果一个 Metadata 的全部文件都被隐藏 原始记录仍保留在 RocksDB 但不会进入搜索索引或公开详情
|
|
|
|
### 采样去重和 Peer 查找
|
|
|
|
BEP-51 返回的 infohash 会先进入有界批量准入队列并由 RocksDB 精确判断
|
|
|
|
新发现节点按地址稳定分流 同一地址只进入直接采样或 `find_node` 通道 直接采样队列满时会安全回退到抓取池 `/stats` 会分别报告候选队列直接采样请求响应 Hash 去重丢弃以及成功 Metadata 的网络来源
|
|
|
|
已有 infohash 只更新最后发现时间和发现次数不会再次执行 Peer Lookup
|
|
|
|
未知 infohash 首先只向返回样本的 DHT 节点查询一次 `get_peers` 只有单点查询没有返回 Peer 时才降级为有限迭代查找
|
|
|
|
`/stats` 中的 `sampled_hashes_filtered` `peer_lookup_preferred_succeeded` 和 `peer_lookup_fallbacks` 用于观察提前去重和单点优先效果
|
|
|
|
## Linux 资源限制
|
|
|
|
生产环境仍可能同时使用较多 TCP socket
|
|
|
|
Linux 生产运行必须把文件描述符上限提高到至少 65536
|
|
|
|
```bash
|
|
prlimit --nofile=65536:65536 -- \
|
|
/opt/dht-search/dht-search --config /opt/dht-search/dht-search.toml
|
|
```
|
|
|
|
systemd 服务需要设置
|
|
|
|
```ini
|
|
[Service]
|
|
LimitNOFILE=65536
|
|
```
|
|
|
|
文件描述符上限过低时 DHT 连接会挤占 Tantivy 和 RocksDB 打开文件所需的描述符并导致服务安全停止
|
|
|
|
## API
|
|
|
|
默认只监听 `127.0.0.1:8080`
|
|
|
|
```text
|
|
GET /health
|
|
GET /ready
|
|
GET /stats
|
|
GET /search?q=ubuntu&offset=0&limit=20
|
|
GET /search?q=%2A.iso
|
|
GET /search?q=&min_size=1048576&max_size=10737418240&extension=mkv
|
|
GET /search?q=流浪地球&min_files=1&availability=active&heat=hot&sort=heat
|
|
GET /search?q=%5ES%5Cd%7B2%7DE%5Cd%7B2%7D&mode=regex
|
|
GET /contents/{content_key}?offset=0&limit=20
|
|
GET /torrents/{infohash}?file_offset=0&file_limit=100
|
|
```
|
|
|
|
`limit` 被限制在 1 到 100 之间且 `offset` 最大为 10000
|
|
|
|
搜索支持中文英文数字和文件名片段匹配
|
|
|
|
默认搜索会自动识别不区分大小写的通配符并匹配名称 别名和文件路径 `*` 表示任意长度字符 `?` 表示一个字符 例如 `*.iso` 匹配所有以 `.iso` 结尾的已索引名称或文件路径 不含通配符时保持普通关键词和片段搜索
|
|
|
|
设置 `mode=regex` 后查询文本作为不区分大小写的正则表达式匹配名称 别名和文件路径 通配符和正则最长 256 字节并由 Tantivy 有限状态自动机执行
|
|
|
|
过滤参数还包括 `min_files` `max_files` `first_seen_after` `first_seen_before` `last_seen_after` `last_seen_before` `availability` 和 `heat`
|
|
|
|
排序支持 `relevance` `latest` `oldest` `heat` `size_desc` `size_asc` 和 `discoveries`
|
|
|
|
有关键词时默认按相关性排序 空查询默认按最近发现排序
|
|
|
|
搜索结果按 `content_key` 精确折叠并通过 `variant_count` 返回变体数量 `/contents/{content_key}` 用于分页查看全部 infohash 和磁力链接
|
|
|
|
搜索响应包含 `heat` 和 `availability` 摘要 详情响应包含完整验证时间 Peer 数和连续失败次数
|
|
|
|
详情文件列表默认返回 100 条且单次最多 200 条 使用 `file_offset` 翻页避免超大种子一次向浏览器返回全部文件
|
|
|
|
## 数据恢复
|
|
|
|
RocksDB 是权威数据源而 Tantivy 是可重建索引
|
|
|
|
当 Tantivy 目录不存在或结构不匹配时应用会直接创建新索引并从 RocksDB 的内容组状态完成全量重建
|
|
|
|
项目当前处于开发阶段 持久化结构变化时直接清理测试数据重新采集 不维护旧测试数据库兼容层
|
|
|
|
正常退出会先停止 DHT 再排空持久化队列提交剩余索引最后关闭 HTTP 服务
|
|
|
|
## 规模基准
|
|
|
|
`dht-benchmark` 使用确定性数据调用真实 RocksDB 写入内容聚合待索引状态 Tantivy 索引和搜索接口
|
|
|
|
默认每十条记录生成一个相同内容的不同 infohash 用于同时覆盖精确去重和内容折叠场景
|
|
|
|
性能测量必须使用 release 构建并从小规模逐步增加
|
|
|
|
```powershell
|
|
$env:LIBCLANG_PATH = "$PWD\.tools\libclang\clang\native"
|
|
|
|
cargo run --release -p dht-search --bin dht-benchmark -- `
|
|
--records 10000 `
|
|
--cleanup
|
|
|
|
cargo run --release -p dht-search --bin dht-benchmark -- `
|
|
--records 100000 `
|
|
--query-iterations 100 `
|
|
--cleanup
|
|
|
|
cargo run --release -p dht-search --bin dht-benchmark -- `
|
|
--records 1000000 `
|
|
--query-iterations 100
|
|
```
|
|
|
|
| 参数 | 默认值 | 作用 |
|
|
|---|---:|---|
|
|
| `--records` | `10000` | 生成记录数量 上限一千万 |
|
|
| `--generation-batch-size` | `1000` | 单批生成并暂存在内存的记录数量 |
|
|
| `--index-batch-size` | `1000` | 每次 Tantivy 提交的内容文档数量 |
|
|
| `--index-max-retries` | `20` | Windows 临时 IO 错误的最大连续重试次数 |
|
|
| `--query-iterations` | `50` | 每类查询正式采样次数 |
|
|
| `--query-warmup` | `5` | 每类查询预热次数 |
|
|
| `--duplicate-every` | `10` | 每多少条创建一个相同内容的不同 infohash 零表示禁用 |
|
|
| `--output-dir` | `benchmark-data` | 独立运行数据和 JSON 报告根目录 |
|
|
| `--cleanup` | 不启用 | 报告写入后删除本次 RocksDB 和 Tantivy 数据 |
|
|
|
|
终端和 JSON 报告包含写入吞吐索引吞吐查询平均值与 P50/P95/P99 RocksDB 与 Tantivy 字节占用每条平均占用和进程峰值内存
|
|
|
|
`benchmark-data/runs` 保存每次未清理的数据库和索引 `benchmark-data/reports` 始终保留 JSON 报告 两者均不进入 Git
|