Files
dht/dht-search

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 只用于本地构建不会部署到运行设备

配置

复制根目录的配置模板

Copy-Item dht-search.example.toml dht-search.toml

相对 data_dir 以配置文件所在目录为基准解析

content_filter_file 指向独立的无效文件过滤配置 相对路径同样以主配置文件所在目录为基准解析 推荐直接使用根目录的 content-filters.toml

也可以通过命令行覆盖数据目录和本次运行时长

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 activepossibly_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 dailynever
logging.retain_files 7 最多保留的匹配日志文件数量
logging.file_prefix dht-search 日志文件名前缀

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

开发时需要直接观察终端日志可以设置 console_enabled = true 文件日志和终端日志不能同时关闭

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 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-namefile-path 匹配方式支持 exact prefix suffix contains wildcardregex 动作只允许安全的 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_succeededpeer_lookup_fallbacks 用于观察提前去重和单点优先效果

Linux 资源限制

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

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

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

systemd 服务需要设置

[Service]
LimitNOFILE=65536

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

API

默认只监听 127.0.0.1:8080

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 availabilityheat

排序支持 relevance latest oldest heat size_desc size_ascdiscoveries

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

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

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

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

数据恢复

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

当 Tantivy 目录不存在或结构不匹配时应用会直接创建新索引并从 RocksDB 的内容组状态完成全量重建

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

正常退出会先停止 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 提交的内容文档数量
--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