docs: 精简项目文档结构

This commit is contained in:
chuan
2026-08-10 19:34:30 +08:00
parent 4671a1d0ed
commit 3ad10c3246
7 changed files with 326 additions and 249 deletions
+1 -1
View File
@@ -102,7 +102,7 @@ Bloom Filter 只能作为前置加速结构不得作为最终去重依据
- 文件长度不是机械拆分标准 单一状态机或单一 adapter 可以集中维护 强拆会产生私有状态穿透时应保持内聚
- 删除没有调用方的预留模块 新扩展在产生真实行为时再创建文件
测试分层位置和依赖规则统一记录在根目录 `TESTING.md`
测试分层位置默认验证命令和依赖规则统一记录在根目录 `README.md`
## 资源约束
-72
View File
@@ -1,72 +0,0 @@
# DHT Search 性能基线
本文档保存可重复的规模基准条件目标和已验证结果
完整运行方式和参数说明见 [`src/search/README.md`](src/search/README.md)
## 基准条件
| 项目 | 值 |
|---|---|
| 日期 | 2026-08-10 |
| 平台 | Windows x86_64 |
| 逻辑处理器 | 32 |
| 构建模式 | release |
| 内容重复比例 | 每十条记录包含一条相同内容的不同 infohash |
| 索引批量 | 每次 1000 个内容文档 |
| 查询预热 | 每类 5 次 |
| 正式查询 | 10 万和 100 万规模每类 50 次 |
| 数据目录 | 独立生成并在报告完成后清理 |
该基准使用确定性合成名称文件路径大小时间和内容变体 适合比较版本变化但不能替代真实 DHT 数据分布和长期运行测试
## 验收目标
| 指标 | 百万级目标 |
|---|---:|
| 普通搜索过滤排序 P95 | 不超过 50 ms |
| 精确 infohash P95 | 不超过 10 ms |
| 大命中集合正则 P95 | 不超过 1 s |
| 全量索引吞吐 | 不低于 2000 文档/秒 |
| 总磁盘占用 | 不超过 6 GiB/百万条 |
| 基准进程峰值内存 | 不超过 2 GiB |
## 规模结果
| 记录数 | 内容文档 | RocksDB 写入 | Tantivy 索引 | 总磁盘 | 峰值内存 |
|---:|---:|---:|---:|---:|---:|
| 10,000 | 9,000 | 149,584 条/秒 | 3,794 文档/秒 | 52.42 MiB | 86.21 MiB |
| 100,000 | 90,000 | 118,833 条/秒 | 3,261 文档/秒 | 452.65 MiB | 360.04 MiB |
| 1,000,000 | 900,000 | 102,115 条/秒 | 2,520 文档/秒 | 4.36 GiB | 1.70 GiB |
百万级 RocksDB 占用 442.38 MiB 平均每条 463.9 字节
百万级 Tantivy 占用 3.93 GiB 平均每个内容文档 4683.1 字节
## 百万级查询结果
| 查询类型 | 命中数 | P50 | P95 | P99 |
|---|---:|---:|---:|---:|
| 中文关键词 | 250,000 | 2.541 ms | 2.571 ms | 2.667 ms |
| 英文关键词 | 200,000 | 5.681 ms | 5.793 ms | 5.804 ms |
| 文件路径片段 | 10,000 | 0.107 ms | 0.110 ms | 0.113 ms |
| 精确 infohash | 1 | 0.007 ms | 0.008 ms | 0.008 ms |
| 有限状态正则 | 200,000 | 648.231 ms | 656.086 ms | 764.102 ms |
| 最近收录排序 | 900,000 | 3.322 ms | 3.346 ms | 3.358 ms |
| 大小扩展名过滤 | 794,074 | 6.391 ms | 6.451 ms | 6.526 ms |
普通全文搜索过滤排序和精确 infohash 均明显低于目标
大命中集合正则达到一秒内目标但随文档数近似线性增长 是继续扩大数据规模前最值得优化的查询路径
## 基准中发现并修复的问题
- 修复四十位 infohash 被全文查询拆成二十字符窗口导致精确搜索结果为空的问题
- 磁盘统计改为关闭 RocksDB 和 Tantivy 后执行避免漏掉尚未刷盘的数据
- 基准索引增加 Windows 临时文件占用的指数退避并记录重试次数和累计等待时间
## 当前结论
百万级规模下 RocksDB 写入 Tantivy 索引普通查询磁盘和峰值内存均达到当前目标
下一阶段使用真实采集数据进行一小时和二十四小时持续运行 验证内存队列磁盘增长和网络稳定性
-107
View File
@@ -1,107 +0,0 @@
# Docker 构建和运行
## 镜像结构
镜像使用三个构建阶段
- `oven/bun:1.3.14-debian` 负责编译 Web 静态资源
- `rust:1.97.1-trixie` 负责构建 Rust 和 RocksDB
- `debian:trixie-slim` 只保留运行所需的二进制静态资源和 C++ 运行库
最终容器只运行一个非 root `dht-search` 进程
## 构建
在仓库根目录执行
```shell
docker build --platform linux/amd64 -t dht-search:dev .
```
构建会复用 Cargo registry Git 和 target BuildKit 缓存
可以使用以下命令检查镜像
```shell
docker image inspect dht-search:dev
docker run --rm --entrypoint id dht-search:dev
docker run --rm --entrypoint ldd dht-search:dev /dht-search/dht-search
```
## Compose 运行
在仓库根目录执行
```shell
docker compose up -d --build
docker compose logs -f
```
容器内应用统一位于 `/dht-search`
```text
/dht-search/
├── dht-search
├── config.toml
├── web/
└── data/
```
Compose 将仓库根目录的 `config.toml` 直接映射到 `/dht-search/config.toml` 网页保存配置时会同步修改宿主机文件
Compose 只创建一个 `dht-search-data` 命名卷并挂载到 `/dht-search/data`
RocksDB Tantivy SQLite 日志和检查点全部位于该数据卷
配置保存通常使用临时文件原子替换 在 Docker 单文件挂载环境中会自动改用同步覆盖写入
本地配置保留 `127.0.0.1:8080` `src/web/dist` 和文件日志方便直接运行 容器启动参数会覆盖监听地址和静态目录并将日志切换到容器标准输出
HTTP 默认只发布到宿主机回环地址 UDP DHT 端口默认公开 如果端口冲突可以临时覆盖
```powershell
$env:DHT_HTTP_BIND = "127.0.0.1:18080"
$env:DHT_UDP_PORT = "22313"
docker compose up -d
```
## 验证
```shell
curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/ready
docker compose logs -f
```
浏览器访问 `http://127.0.0.1:8080`
公网访问 Web 应使用反向代理或 SSH 通道
## 停止和重启
```shell
docker compose stop
docker compose start
```
更新镜像或重新创建容器时执行 `docker compose up -d --build` 即可复用根配置和数据卷
`docker compose down` 只删除容器和网络 不删除数据卷
不要执行 `docker compose down --volumes` 除非已经确认 RocksDB 权威数据和其他运行数据都不再需要
## 远程设备
远程设备是 x86_64 时构建 `linux/amd64`
需要导出镜像时执行
```shell
docker save dht-search:dev -o dht-search-dev.tar
```
将文件复制到远程设备后执行
```shell
docker load -i dht-search-dev.tar
```
+131 -15
View File
@@ -1,34 +1,150 @@
# DHT 元数据搜索服务
这是一个用于发现持久化索引和搜索 BitTorrent DHT 元数据的 Rust workspace
这是一个用于持续发现持久化索引和搜索 BitTorrent DHT 元数据的 Rust 服务
## 目录
当前已经具备 DHT 采集 Metadata 下载 RocksDB 精确去重 Tantivy 全文搜索 内容聚合 可用性验证 HTTP API Web 搜索界面 运行诊断 配置管理 备份恢复和 Docker 部署能力
## 项目结构
```text
src/search/ 最终运行的采集存储搜索和接口应用
src/crawler/ 可独立复用的 DHT 协议与 Metadata 获取基础库
src/web/ 基于 Vue 和 shadcn-vue 的本地搜索界面
opencodes/ 不参与构建的参考项目
src/web/ 基于 Vue 和 shadcn-vue 的搜索界面
opencodes/ 不参与构建且不得修改的参考项目
```
当前已完成 DHT 采集持久化全文搜索内容聚合可用性验证 HTTP API 本地 Web 搜索界面 有界 SQLite 运行诊断历史和配置管理
RocksDB 是唯一权威数据源 Tantivy 索引可以从 RocksDB 完整重建
基础库的使用方式和指标说明见 [`src/crawler/README.md`](src/crawler/README.md)
应用全部配置和内容隐藏规则统一位于 [`config.toml`](config.toml)
当前实施阶段和后续计划见 [`TODOS.md`](TODOS.md)
## 本地运行
百万级性能基线和验收目标见 [`BENCHMARKS.md`](BENCHMARKS.md)
Windows 可以在仓库根目录执行
Rust 测试分层和默认验证命令见 [`TESTING.md`](TESTING.md)
```powershell
scripts\run.bat
```
应用全部配置和无效文件隐藏规则统一位于 [`config.toml`](config.toml)
脚本会在当前窗口同时启动 Rust 后端和 Vite 前端 按一次 `Ctrl+C` 即可统一停止
应用构建运行和 API 文档见 [`src/search/README.md`](src/search/README.md)
默认地址
Web 开发和构建方式见 [`src/web/README.md`](src/web/README.md)
| 服务 | 地址 |
|---|---|
| Web 开发界面 | `http://127.0.0.1:5173` |
| HTTP API | `http://127.0.0.1:8080` |
| 健康检查 | `http://127.0.0.1:8080/health` |
| 就绪检查 | `http://127.0.0.1:8080/ready` |
开发环境可以直接运行 `scripts\run.bat` 在当前窗口同时启动 Rust 后端和 Web 前端 按一次 `Ctrl+C` 即可统一停止
也可以分别启动
启动脚本和 Web 配置页统一读取并保存根目录 `config.toml`
```powershell
cargo run -p dht-search --bin dht-search -- --config config.toml
任一服务异常退出时启动脚本会清理 Cargo Bun 及其子进程树 避免遗留 Vite 或后端进程
cd src/web
bun install
bun run dev -- --host 127.0.0.1
```
Windows 构建 RocksDB 需要 LLVM 并让 `LIBCLANG_PATH` 指向包含 `libclang.dll` 的目录
## Docker
镜像使用 Bun Rust 和 `debian:trixie-slim` 三个阶段构建 最终容器只运行非 root `dht-search` 进程
```shell
docker build --platform linux/amd64 -t dht-search:dev .
docker compose up -d
docker compose logs -f
```
默认 Compose 行为
- `config.toml` 映射到 `/dht-search/config.toml`
- `dht-search-data` 命名卷保存 RocksDB Tantivy SQLite 日志和检查点
- HTTP 只发布到宿主机 `127.0.0.1:8080`
- DHT UDP 发布到宿主机 `12313/udp`
- 容器文件句柄上限为 `65536`
- 停止宽限时间为 120 秒
端口冲突时可以临时覆盖
```powershell
$env:DHT_HTTP_BIND = "127.0.0.1:18080"
$env:DHT_UDP_PORT = "22313"
docker compose up -d
```
常用管理命令
```shell
docker compose ps
docker compose logs -f
docker compose stop
docker compose start
docker compose down
```
`docker compose down` 不删除数据卷 不要执行 `docker compose down --volumes` 除非已经确认权威数据不再需要
导出并传输镜像
```shell
docker save dht-search:dev -o dht-search-dev.tar
docker load -i dht-search-dev.tar
```
如果宿主机启用了 Xray TProxy 等全局透明代理 DHT 流量可能需要单独旁路 判断和处理方式见 [`docs/xray-transparent-proxy.md`](docs/xray-transparent-proxy.md)
## 测试
单个规则和私有状态机测试放在对应 Rust 模块底部 跨层公开契约测试放在 crate 的 `tests/` 目录 真实 DHT 长时间运行远程部署和浏览器交互不进入默认 `cargo test`
默认 Rust 验证命令
```powershell
$env:LIBCLANG_PATH = "$PWD\.tools\libclang\clang\native"
cargo fmt --all --check
cargo test --workspace --all-targets --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
Web 验证命令
```shell
cd src/web
bun run typecheck
bun run build
```
## 性能基线
基准使用 Windows x86_64 release 构建和确定性合成数据 内容重复比例为十分之一 每批索引 1000 个内容文档
| 记录数 | 内容文档 | RocksDB 写入 | Tantivy 索引 | 总磁盘 | 峰值内存 |
|---:|---:|---:|---:|---:|---:|
| 10,000 | 9,000 | 149,584 条/秒 | 3,794 文档/秒 | 52.42 MiB | 86.21 MiB |
| 100,000 | 90,000 | 118,833 条/秒 | 3,261 文档/秒 | 452.65 MiB | 360.04 MiB |
| 1,000,000 | 900,000 | 102,115 条/秒 | 2,520 文档/秒 | 4.36 GiB | 1.70 GiB |
百万级查询 P95
| 查询类型 | P95 |
|---|---:|
| 中文关键词 | 2.571 ms |
| 英文关键词 | 5.793 ms |
| 文件路径片段 | 0.110 ms |
| 精确 infohash | 0.008 ms |
| 有限状态正则 | 656.086 ms |
| 最近收录排序 | 3.346 ms |
| 大小扩展名过滤 | 6.451 ms |
百万级基准中普通搜索过滤排序精确哈希索引吞吐磁盘和峰值内存均达到当前目标 大命中集合正则仍是继续扩大规模前最值得优化的查询路径
## 相关文档
- 当前实施状态和后续计划见 [`TODOS.md`](TODOS.md)
- 开发约定和架构边界见 [`AGENTS.md`](AGENTS.md)
- 应用命令参数 API 和基准工具见 [`src/search/README.md`](src/search/README.md)
- DHT 基础库用法和指标见 [`src/crawler/README.md`](src/crawler/README.md)
- Web 开发说明见 [`src/web/README.md`](src/web/README.md)
-52
View File
@@ -1,52 +0,0 @@
# Rust 测试分层约定
本文档说明测试应该放在哪里以及每一层允许依赖什么
## 单元测试
单个规则私有状态机编码函数和边界计算使用源码文件底部的 `#[cfg(test)] mod tests`
单元测试可以通过 `use super::*` 访问当前模块私有实现 但不应跨多个业务模块组装完整应用
当前示例包括 Metadata 限制 路径校验 内容组代表选择 响应限流和查询分位数
## 组件测试
需要模块私有装配状态的组件测试保留在对应模块中
例如 Axum router 测试需要私有 `ApiState` 和测试用验证入口 因此与 `api` 模块放在一起而不是为了目录形式公开内部 API
RocksDB adapter 测试需要验证原子批处理私有键空间租约和损坏状态 因此保留在 `storage::rocks` 内部
SQLite 诊断组件测试需要验证 WAL 持久化分钟合并保留清理和 writer 关闭 因此保留在 `diagnostics` 模块内部
## 集成测试
只使用 crate 公开 API 的跨层契约放在 crate 根目录 `tests/`
`src/search/tests/storage_search_flow.rs` 从外部组合领域模型 RocksDB repository 和 Tantivy search 验证写入索引查询关闭重开和恢复
集成测试不得依赖 `pub(crate)` 或为测试扩大生产 API 可见性
## 端到端和运行测试
真实 DHT 网络长时间运行远程部署和浏览器交互依赖外部环境 不放入默认 `cargo test`
这类测试的条件命令结果和验收结论记录到 `TODOS.md` `BENCHMARKS.md` 或部署文档
## 性能基准
可重复的规模测量使用独立 `dht-benchmark` 二进制
基准模块按参数数据集工作负载采样报告和编排拆分 单元测试只验证确定性生成分位数退避和格式化规则
性能结论必须来自 release 构建 默认 debug 运行只用于流程冒烟
## 默认验证命令
```powershell
$env:LIBCLANG_PATH = "$PWD\.tools\libclang\clang\native"
cargo fmt --all --check
cargo test --workspace --all-targets --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
+7 -2
View File
@@ -154,7 +154,7 @@
- [x] 新写入记录在目标延迟内可搜索
- [x] 搜索索引删除后可以从 RocksDB 完整重建
- [x] 索引过程中异常退出不会永久丢失文档
- [x] 百万级测试数据常用查询延迟达到 `BENCHMARKS.md` 约定目标
- [x] 百万级测试数据常用查询延迟达到 `README.md` 记录的目标
## 阶段四 HTTP 搜索服务
@@ -317,7 +317,10 @@
- [x] 编写 Compose 配置管理端口数据卷重启策略和文件句柄限制
- [x] Docker 将根目录唯一 `config.toml` 映射到容器并使用单独数据卷保存全部运行数据
- [x] 使用专用低权限 UID 运行验证
- [x] 在公网 Debian 设备验证镜像传输内部 Docker 网络 Caddy 反向代理 Compose 重启数据复用和优雅停止
- [x] 固化 Xray 环境下的最小范围网络旁路
- [x] 为 Docker DHT 容器增加独立直连网络和持久化 Xray 来源网段旁路
- [x] 验证旁路后一分钟内 Metadata 成功入库且 Xray 文件描述符和主机 UDP socket 恢复稳定
- [x] 验证高并发运行需要 `LimitNOFILE=65536`
- [ ] 实现启动前数据目录权限检查
- [ ] 实现优雅升级和回滚流程
@@ -351,9 +354,11 @@
- [x] 在系统诊断中区分 Peer 下载结果和 Metadata 内容有效性并展示 Peer 失败原因构成
- [x] 使用页签拆分配置分类并隐藏底层配置存储位置
- [x] 将主配置和内容过滤规则合并为唯一 `config.toml`
- [x] 将 Docker 测试和性能基线核心内容合并到根目录 `README.md`
- [x] 将 Xray 透明代理异常环境的判断处理和回滚方案独立到 `docs`
完成二十四小时持续运行并继续观察私有内存 Metadata 成功率候选队列深度和每条成功 Metadata 的网络成本
RocksDB Tantivy HTTP 和进程资源指标已经接入诊断历史 后续根据长期实测继续调优
下一步在公网 Linux 设备验证 Compose 容器网络重启策略数据卷权限和优雅停止
下一步持续观察公网设备 Metadata 成功率 Xray 文件描述符主机 socket 和磁盘增长是否长期稳定
+187
View File
@@ -0,0 +1,187 @@
# Xray 透明代理与 DHT 直连处理
本文只适用于宿主机启用了 Xray TProxy 或其他全局透明代理的特殊部署环境
普通 Docker 主机直接使用根目录 `compose.yaml` 不需要这里的专用网络或 nftables 规则
## 为什么会发生
DHT 会向大量公网节点发送 UDP 查询 并尝试连接大量 Peer 的随机 TCP 端口
全局 TProxy 如果在 `prerouting` 中接管所有公网 TCP 和 UDP Docker 容器流量也会进入 Xray
Xray 即使最终选择直连出站仍然需要维护这些 UDP 会话 因此可能积累大量 socket 并让 Metadata TCP 连接失败
## 典型现象
- DHT 节点 Peer 和采样 infohash 持续增长
- Metadata 成功长期为零或远低于正常水平
- Metadata 失败几乎全部是连接失败
- Xray 文件描述符和宿主机 UDP socket 持续增长
- SSH 或其他新连接开始偶发断开
- `dht-search` 容器自身文件描述符并不高
仅看到 Metadata 超时不能直接判断为 Xray 问题 公网 DHT 本身就包含大量不可达 Peer
## 如何确认
查看服务统计
```shell
docker run --rm --network web-network curlimages/curl:8.16.0 -fsS http://dht-search:8080/stats
```
重点比较以下字段
```text
metadata_ok
metadata_failed
metadata_failure_connect
metadata_failure_timeout
metadata_in_flight
```
查看宿主机和 Xray socket
```shell
cat /proc/net/sockstat
xray_pid="$(pidof xray)"
find "/proc/${xray_pid}/fd" -maxdepth 1 -type l | wc -l
grep "Max open files" "/proc/${xray_pid}/limits"
```
查看 DHT 容器自身 socket
```shell
docker exec dht-search cat /proc/net/sockstat
docker exec dht-search sh -c 'ls /proc/1/fd | wc -l'
```
查看透明代理规则
```shell
nft list ruleset
ip rule show
```
如果存在类似规则 Docker 公网流量会进入 Xray
```nft
meta l4proto { tcp, udp } tproxy to :12345 meta mark set 1 accept
```
## 推荐解决方案
让 DHT 同时连接反向代理网络和独立直连网络
```yaml
networks:
web-network:
external: true
name: web-network
dht-direct:
name: dht-direct
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/24
services:
dht-search:
networks:
web-network:
dht-direct:
gw_priority: 1
```
`web-network` 只负责 Caddy 到 `dht-search:8080`
`dht-direct` 具有更高网关优先级并负责 DHT 和 Peer 公网流量
部署前必须确认 `172.30.0.0/24` 没有与宿主机路由或其他 Docker 网络冲突
```shell
ip route show 172.30.0.0/24
docker network inspect dht-direct
```
## 配置 Xray 来源旁路
在 Xray 持久化 nftables 文件中增加来源集合
```nft
set bypass_source_ipv4 {
type ipv4_addr
flags interval
elements = {
172.30.0.0/24
}
}
```
`prerouting` 链进入 TProxy 之前增加
```nft
ip saddr @bypass_source_ipv4 return
```
必须修改 Xray 服务启动时实际加载的持久化文件 只向当前运行规则临时执行 `nft add rule` 会在重启后失效
## 安全实施顺序
1. 使用 `docker compose stop` 优雅停止 DHT
2. 备份 Compose 和 Xray nftables 文件
3. 增加 `dht-direct` 网络和来源旁路
4. 使用 `docker compose config --quiet` 校验 Compose
5. 使用 `nft -c -f <规则文件>` 校验 nftables
6. 重启 Xray 释放已经积累的旧会话
7. 使用 `docker compose up -d` 重新创建 DHT 容器
8. 验证默认网关 Metadata 成功率和 socket 数量
重启 Xray 会短暂中断现有代理连接 应选择允许短暂中断的时间执行
## 验收
确认容器同时加入两个网络且没有宿主机端口绑定
```shell
docker compose ps
docker inspect dht-search --format '{{json .HostConfig.PortBindings}}'
docker inspect dht-search --format '{{json .NetworkSettings.Networks}}'
docker exec dht-search cat /proc/net/route
```
默认网关应该属于 `dht-direct` 然后持续观察
- `metadata_ok``persistence_inserted` 开始增长
- Metadata 失败不再全部是连接失败
- Xray 文件描述符不再随 DHT 流量快速增长
- 宿主机 UDP socket 恢复到正常范围
- Caddy 仍能通过 `web-network` 访问服务
## 实际案例
一次远端 Debian 部署中修复前 Metadata 尝试超过一百万次但成功为零 Xray 文件描述符增长到约三十九万 主机 UDP socket 超过三十三万
增加独立直连网络并重启 Xray 后两分钟内 Metadata 成功 1222 条并入库 1218 条 Xray 文件描述符稳定在 12 主机 UDP socket 稳定在 6
## 不建议的处理
- 只提高 Xray `LimitNOFILE`
- 只增加机器内存
- 只降低 Metadata 并发
- 让 DHT 流量进入 Xray 后再选择 direct outbound
这些方法只能延迟资源耗尽或仍然让 Xray 维护海量 UDP 会话 不能消除错误流量边界
## 回滚
如果专用网络启动失败
1. 停止 DHT 容器
2. 恢复原 Compose 和 nftables 备份
3. 校验并重启 Xray
4. 保持 DHT 停止直到重新确认路由方案
不要在恢复了全局 TProxy 且没有来源旁路的情况下重新启动高并发 DHT