运行中继
针对你的 nostrfy 中继:启动与停止、日志与统计、配置热重载、多实例和大规模调优。
启动与停止
以后台守护进程方式启动中继:
nostrfy --config nostrfy.toml start=> nostrfy started (pid 12345)
或在终端中前台运行:
nostrfy --config nostrfy.toml start --foreground停止:
nostrfy --config nostrfy.toml stop验证已启动:
curl http://127.0.0.1:8080/health=> {"status":"ok"}
日志与统计
守护进程写入 daemon.log_file。当文件超过 max_log_size_bytes 时会
自动轮转(nostrfy.log.1、.2……最多 max_log_files 代):
tail -f nostrfy.log日志级别由 RUST_LOG 环境变量控制(例如 RUST_LOG=debug)。
统计
从 CLI 获取实时统计:
nostrfy stats或通过 HTTP:
curl http://127.0.0.1:8080/relay/stats显示连接数、接受/拒绝的事件、数据库大小等。
Prometheus 指标
curl http://127.0.0.1:8080/metrics热重载(SIGHUP)
编辑配置文件后,无需重启即可重载:
kill -HUP $(cat nostrfy.pid)| 重载时生效 | 需要重启 |
|---|---|
| 中继身份、public_url | private_key |
| 大部分 [limits]、reject_ephemeral、enabled_git、enabled_nip78_auth | api_host、metrics_enabled、LiveKit 设置 |
| — | enabled_nips / disabled_nips、server.host / port / ws_paths |
| — | database.*(含 search_index)、blossom.* |
当需要重启的设置发生变化时,日志会发出警告。
运行多个实例
nostrfy 支持在同一台服务器上运行多个独立中继(不同端口)。每个实例需要自己的 server.port、[daemon] 的 pid_file/log_file/stats_file (共享值会让第二个实例以 already running 拒绝启动)、 database.path,以及(如使用)各自的 api_host / blossom.host:
[server]
port = 8080
[database]
path = "/var/lib/nostrfy-a"
[daemon]
pid_file = "/var/run/nostrfy-a.pid"
log_file = "/var/log/nostrfy-a.log"
stats_file = "/var/lib/nostrfy-a/stats.json"
[server]
port = 8081
[database]
path = "/var/lib/nostrfy-b"
[daemon]
pid_file = "/var/run/nostrfy-b.pid"
log_file = "/var/log/nostrfy-b.log"
stats_file = "/var/lib/nostrfy-b/stats.json"每个实例用各自的配置管理:nostrfy --config /etc/nostrfy/a.toml start 等。
大规模部署
该中继设计为可在单个主机上扩展到数十万连接 — 实时投递只唤醒能匹配事件的订阅者, 且每连接内存占用很小。推向百万级需要主机级调优:
| 设置 | 值 | 原因 |
|---|---|---|
ulimit -n / systemd LimitNOFILE | ≥ 目标连接数的 2 倍(+1000) | 每个连接占用一个 fd |
net.core.somaxconn | ≥ 1024 | 连接突发时的待处理 accept 队列 |
net.ipv4.tcp_fin_timeout | 较低(例如 10) | 更快回收 TIME_WAIT socket |
vm.overcommit_memory | 1 或 2 | LMDB 映射是大型稀疏虚拟预留 |
在 FreeBSD 上,对应的参数是 kern.maxfiles / kern.maxfilesperproc 加 ulimit -n,并用 kern.ipc.somaxconn 替代 net.core.somaxconn。
每连接的内核内存约 80 KiB、用户空间约 10 KiB,因此一百万连接在数据库之外大约需要 90 GiB 的
内核 + 用户内存。
吞吐量(每秒事件数)
事件写入受两项成本约束:Schnorr 签名验证(每个事件约 30-50 µs)和 LMDB 写入器在每个提交批次后
执行的同步磁盘刷新。两者都可以在 nostrfy.toml 中调整:
| 设置 | 原因 |
|---|---|
database.disabled_fsync = true | 提交到操作系统页缓存(微秒级);断电只会丢失上次刷新以来的写入 — 从这里开始 |
| CPU 核心 ≥ 8 vCPU | 批量 EVENT 路径跨核心并行验证签名 |
database.search_index = false | 为写入密集型实例去掉每事件的 NIP-50 词索引写入 |
并行签名验证在待处理批次中一次验证所有签名,使用工作线程池 (上限 8;少于 16 个事件的批次内联验证)。每个事件的廉价检查仍然先行,因此拒绝 原因文本与顺序路径完全相同 — 只有 Schnorr 工作分散到各核心。单线程 构建保持顺序执行。
固定的反滥用边界
为保持中继在滥用下仍能响应,有几项硬边界是固定的(不可配置):
- 单个过滤器最多携带 512 个
ids、authors或kinds条目;#...标签值每个过滤器共享单独的 512 值预算。更大的过滤器会被拒绝(CLOSED invalid: ...)。 max_connections_per_sec_per_ip最多跟踪 10,000 个源 IP;满时,未见过的 IP 会被拒绝(fail closed)。ids过滤器中的事件 id 可以是前缀,但只有完整的 32 字节 id 和偶数长度 前缀才能匹配(奇数长度/空条目在历史和实时投递中都会被忽略)。- 过长的索引键(标签值、内容词、超出 LMDB 键大小限制的
d标签)在 索引时会被跳过;事件仍会被存储。