Files

20 KiB

dht-search

dht-search 是集 DHT Metadata 采集 RocksDB 持久化 Tantivy 搜索索引和 HTTP API 于一体的应用

构建准备

RocksDB 包含 C++ 代码并在构建时使用 bindgen 因此需要 libclang

Windows 可以把 libclang 安装到工作区本地目录

python -m pip install --target .tools\libclang libclang
$env:LIBCLANG_PATH = "$PWD\.tools\libclang\clang\native"
cargo build -p dht-search --release

.tools 只用于本地构建不会部署到运行设备

配置

根目录 config.toml 包含全部运行配置和内容过滤规则

cargo run -p dht-search -- --config config.toml

相对目录以 config.toml 所在目录为基准解析

也可以通过命令行覆盖数据目录

cargo run -p dht-search -- --data-dir D:\data\dht-search

服务持续运行直到收到 Ctrl+C SIGINT 或 SIGTERM

生产 Web 页面需要先在 src/web 目录执行 bun run build Axum 会从 http.web_dir 提供构建结果

当前运行模板网络配置

config.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 8 同时验证的种子数量
verification.max_peer_attempts 3 每个种子最多握手的 Peer 数量
verification.lease_secs 60 异常退出后验证任务重新可领取的租约时间
verification.poll_interval_millis 250 持久化队列轮询间隔

详情访问使用高优先级 搜索结果使用普通优先级 队列满时高优先级可以替换最旧普通任务

可用性分为 unknown activepossibly_stale 一次或多次验证失败只表示当前可能没有可连接 Peer 不会删除种子

新抓取记录会把成功下载 Metadata 的来源 Peer 视为一次有效验证 旧记录按需复查时会同时使用新 DHT 结果和已保存的成功来源 Peer

热度是近期 DHT 发现强度最近出现时间和可连接 Peer 数的综合活跃度分数 不代表全球下载量

这组参数仍保留全局速率限制 如果同机代理或路由器再次出现不稳定应优先降低总 UDP 预算和 Metadata 建连速率

Windows 下索引每五秒批量提交 临时文件占用会自动指数退避重试且不会停止采集 HTTP 服务或丢失 RocksDB 待索引状态

磁盘空间保护

应用固定每分钟检查 data_dir 所在文件系统的总容量和剩余空间 低于保护阈值时先停止接收新 Metadata DHT 状态更新索引任务和可用性验证任务 已经进入有界持久化队列的记录会继续排空 随后进入只读保护

只读保护始终启用且不需要用户配置 保护阈值取文件系统总容量的 5% 并限制在 512 MiB 到 20 GiB 之间 恢复缓冲取总容量的 1% 并限制在 256 MiB 到 5 GiB 之间 文件系统扩容后会自动采用新阈值

只读保护期间 RocksDB 和 Tantivy 不再产生业务写入 现有搜索详情健康检查和 Web 页面仍然可用 剩余空间达到自动恢复阈值后重新接受写入 使用两个阈值可以避免临界空间附近反复暂停和恢复

磁盘空间探测失败时采用保守策略进入保护状态 /stats 返回 disk_state disk_available_bytes 阈值 活跃写入数 探测失败数 状态转换数和拒绝任务数 Web 运行状态使用绿色或黄色状态点展示正常与保护状态

日志轮转和保留

应用会在读取配置后初始化日志并始终输出到终端 因此 Docker 可以直接通过 docker logs 读取运行日志 文件日志默认同时写入 data/logs 并允许从 Web 高级设置关闭

配置项 默认值 作用
logging.directory data/logs 日志文件目录 相对主配置文件解析
logging.file_enabled true 启用滚动文件日志
logging.rotation daily 轮转周期 支持 minutely hourly dailynever
logging.retain_files 7 最多保留的匹配日志文件数量
logging.file_prefix dht-search 日志文件名前缀

默认按天轮转时保留 7 个文件约等于保留最近 7 天 日志组件只清理同目录中同时匹配前缀和 .log 后缀的普通文件 不删除目录和符号链接 清理失败会输出错误但不会让服务退出

关闭文件日志不会影响终端输出

RocksDB 检查点备份和恢复

RocksDB 是唯一权威数据源 应用使用 RocksDB 原生 Checkpoint API 在线生成一致快照 备份期间采集可以继续运行 Tantivy 不进入备份因为它可以从 RocksDB 完整重建

配置项 默认值 作用
backup.enabled false 是否启用自动检查点 默认关闭且不在 Web 展示
backup.directory data/backups 检查点目录 相对主配置文件解析
backup.interval_secs 21600 每 6 小时创建一次检查点
backup.retain_checkpoints 3 保留最近 3 个自动检查点
backup.create_on_start true 每次启动后立即创建一次检查点

需要自动检查点时可以直接修改 config.toml 备份目录与数据目录位于同一磁盘时 RocksDB 会尽量通过硬链接减少复制开销 放到其他磁盘时可能复制全部数据库文件 创建前会检查备份磁盘剩余空间 磁盘保护期间自动跳过而不会阻塞服务

/stats 返回检查点成功失败跳过清理数量 最近成功时间耗时和记录数量 自动清理只处理名称严格匹配 checkpoint- 加二十位时间戳的直接子目录 不会删除手工目录文件或符号链接

恢复必须在服务停止后执行 数据目录独占锁会阻止运行中的服务和恢复命令同时操作

target\release\dht-search.exe `
  --config config.toml `
  --restore-checkpoint data\backups\checkpoint-00000001775400000000

恢复命令会先以只读方式校验源检查点 再复制到数据目录内的暂存目录并二次校验 然后切换 rocksdb 目录 原数据库不会删除而是保留为 rocksdb.pre-restore-* 方便人工回滚 Tantivy 作为派生数据会被删除 下次正常启动自动从恢复后的 RocksDB 重建

确认恢复数据无误后可以人工删除 rocksdb.pre-restore-* 释放空间 不要在服务运行时移动或删除这些目录

运行诊断历史

应用把可删除的运行资源采样写入独立的 data/diagnostics.sqlite3 RocksDB 仍然是唯一业务权威数据源 删除诊断数据库不会影响种子数据搜索索引或恢复

配置项 默认值 作用
diagnostics.enabled true 是否采集并保存运行诊断历史
diagnostics.database data/diagnostics.sqlite3 独立 SQLite 数据库路径
diagnostics.sample_interval_secs 10 实时资源采样间隔
diagnostics.raw_retention_hours 24 原始采样保留时间
diagnostics.minute_retention_days 30 每分钟快照保留时间
diagnostics.queue_capacity 128 SQLite writer 有界队列容量

SQLite 使用 WAL 和单独 writer 线程 原始采样超过 24 小时自动删除 同一分钟只保留最新快照且超过 30 天自动删除 写入失败不会停止采集和搜索核心服务

诊断快照包含进程内存 CPU 时间线程句柄 DHT 流量队列深度 RocksDB Block Cache MemTable Compaction 和 SST 状态 Tantivy Writer 预算和提交耗时以及 HTTP 并发错误分类平均 P95 和最大延迟

配置管理

配置文件只是强类型配置 DTO 的 TOML 持久化形式 服务通过 GET /config 返回当前文件配置和稳定修订号 通过 PUT /config 接受完整 DTO

更新前会执行与启动时相同的完整校验 保存时先在同目录写入并同步临时文件再原子替换目标文件 旧修订号返回 409 Conflict 防止多个页面互相覆盖

dht.enabled 外当前版本不在线修改正在运行的 DHT 存储索引和监听器 保存其他配置后返回 restart_required = true 并在重启服务后统一生效 命令行覆盖字段也会单独返回并继续优先于文件配置

诊断页通过 /collectors 选择集群总览或单个采集器并展示对应 DHT Peer 和进程指标 独立采集器页面负责按节点启停与修改配置 每个节点拥有独立修订号并由协调器集中持久化

通过 Web 保存会按 DTO 重新生成 TOML 原有手写注释不会保留 管理接口默认随 HTTP 服务提供 因此生产部署不应把 /config 暴露到不受信任的公网入口

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 运行状态展示本次运行的过滤总数

可以通过命令行覆盖独立数据目录进行测试 不会污染正式数据目录

cargo run -p dht-search -- --config config.toml --data-dir data-filter-test
Invoke-RestMethod http://127.0.0.1:8080/stats | ConvertTo-Json -Depth 5

无效文件隐藏规则

config.tomlcontent_filter 区域控制哪些种子标题和内部文件不参与详情与搜索 默认内部规则会隐藏 BitComet _____padding_file_ 文件以及 .pad.____padding_file 填充目录

RocksDB 始终保存完整原始 Metadata 用户隐藏规则不会删除文件或种子也不会改变 content_key 和内容组成员关系 标题命中或全部内部文件被隐藏时只隐藏对应 infohash 版本 同组其他版本仍可展示

配置只包含 torrent_name_patternsfile_patterns 两组规则 内部文件规则同时检查 basename 和规范化完整路径 Web 使用两个多行文本框编辑并按行切分 空行和重复规则自动忽略

每行默认是不区分大小写的通配符 * 表示任意长度字符 ? 表示一个字符并匹配完整字段 使用 regex: 前缀可以编写 Rust regex 语法的正则表达式

规则保存后详情立即使用最新投影 后台扫描内容组并以投影哈希只提交真正变化的 Tantivy 文档 扫描游标和待删除任务持久化且允许连续保存时自动收敛到最新规则 /stats.filter 与 Web 系统页展示进度

内部文件隐藏后列表只返回可见文件但总大小和原始文件数量保持 Metadata 原值 visible_file_count 单独用于详情分页

采样去重和 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_succeededpeer_lookup_fallbacks 用于观察提前去重和单点优先效果

Linux 资源限制

生产环境仍可能同时使用较多 TCP socket

Linux 生产运行必须把文件描述符上限提高到至少 65536

prlimit --nofile=65536:65536 -- \
  /opt/dht-search/dht-search --config /opt/dht-search/config.toml

systemd 服务需要设置

[Service]
LimitNOFILE=65536

文件描述符上限过低时 DHT 连接会挤占 Tantivy 和 RocksDB 打开文件所需的描述符并导致服务安全停止

API

默认只监听 127.0.0.1:8080

GET /health
GET /ready
GET /stats
GET /diagnostics/current
GET /diagnostics/history?range_secs=3600&resolution=raw
GET /diagnostics/history?range_secs=86400&resolution=minute
GET /config
PUT /config
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 /contents/{content_key}?offset=0&limit=20
GET /torrents/{infohash}?file_offset=0&file_limit=100

limit 被限制在 1 到 100 之间且 offset 最大为 10000

搜索支持中文英文数字和文件名片段匹配

搜索支持不区分大小写的单扩展名快捷语法 例如 *.iso 直接匹配包含 ISO 文件的已索引内容 Ubuntu *.iso 表示普通关键词和扩展名条件同时满足 普通关键词继续匹配标题别名文件名和路径片段

搜索接口不接受正则模式 mode 参数 也不接受 *iso ubuntu* ? 或多段扩展名等任意通配符表达式

过滤参数还包括 min_files max_files first_seen_after first_seen_before last_seen_after last_seen_before availabilityheat

排序支持 relevance latest oldest heat size_desc size_ascdiscoveries

有关键词时默认按相关性排序 空查询默认按最近发现排序

搜索结果按 content_key 精确折叠并通过 variant_count 返回变体数量 /contents/{content_key} 用于分页查看全部 infohash 和磁力链接

搜索响应包含 heatavailability 摘要 详情响应包含完整验证时间 Peer 数和连续失败次数

详情文件列表默认返回 100 条且单次最多 200 条 使用 file_offset 翻页避免超大种子一次向浏览器返回全部文件

生产 Web 页面使用 / /system /workers/settings 四个路由 分别提供搜索运行诊断采集器管理和服务配置 API 路径继续保持独立避免单页回退冲突

数据恢复

RocksDB 是权威数据源而 Tantivy 是可重建索引

当 Tantivy Schema 或索引文档格式变化以及索引缺失损坏时 应用在独立代际目录构建影子索引 旧索引继续提供搜索且新收录内容在切换后统一可见 影子索引清空 RocksDB 待索引状态并通过文档数校验后原子更新活动指针

影子索引通过内部构建清单跨重启恢复 构建失败磁盘保护或进程退出不会删除活动索引 没有旧索引时会提供正在初始化的部分结果并明确标记结果尚不完整 切换成功后立即清理旧索引 Windows 文件占用造成的清理失败只记录警告而不影响服务

/statsindex 字段区分已保存 infohash 可搜索内容组活动索引文档影子索引文档和待索引数量 并返回重建原因状态进度开始完成时间和错误

每个内容组按文件大小降序和规范化路径升序选择最多 2048 个文件 完整路径文本总预算为 256 KiB 被选择文件的名称路径和扩展名参与搜索 文件总数大小和详情仍使用全部可见文件

默认相关性优先完整种子名称 名称片段 别名 文件名和完整路径 热度与最后发现时间只用于文本同分结果 用户显式选择的时间热度大小和发现次数排序不变

项目当前处于开发阶段 持久化结构变化时直接清理测试数据重新采集 不维护旧测试数据库兼容层

正常退出会先停止 DHT 和诊断采样 再排空持久化队列提交剩余索引最后关闭 HTTP 服务

规模基准

dht-benchmark 使用确定性数据调用真实 RocksDB 写入内容聚合待索引状态 Tantivy 索引和搜索接口

默认每十条记录生成一个相同内容的不同 infohash 用于同时覆盖精确去重和内容折叠场景

性能测量必须使用 release 构建并从小规模逐步增加

$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 提交的内容文档数量
--files-per-record 不指定 为大型文件集合基准固定每条记录的文件数量 范围 1 到 20000
--index-max-retries 20 Windows 临时 IO 错误的最大连续重试次数
--query-iterations 50 每类查询正式采样次数
--query-warmup 5 每类查询预热次数
--duplicate-every 10 每多少条创建一个相同内容的不同 infohash 零表示禁用
--file-name-ngram-max 8 实验文件名 N-Gram 最大长度 只支持 4 或 8
--file-name-ngram-positions 不启用 2..4 实验记录位置并使用长关键词短语查询
--file-name-ngram-split 不启用 将 2 至 3 字符 Basic 倒排与四字符位置字段拆分且必须同时启用位置索引
--output-dir benchmark-data 独立运行数据和 JSON 报告根目录
--cleanup 不启用 报告写入后删除本次 RocksDB 和 Tantivy 数据

终端和 JSON 报告包含写入吞吐索引吞吐查询平均值与 P50/P95/P99 RocksDB 与 Tantivy 字节占用每条平均占用和进程峰值内存

benchmark-data/runs 保存每次未清理的数据库和索引 benchmark-data/reports 始终保留 JSON 报告 两者均不进入 Git