运行中继

针对你的 nostrfy 中继:启动与停止、日志与统计、配置热重载、多实例和大规模调优。

启动与停止

以后台守护进程方式启动中继:

sh
nostrfy --config nostrfy.toml start

=> nostrfy started (pid 12345)

或在终端中前台运行:

sh
nostrfy --config nostrfy.toml start --foreground

停止:

sh
nostrfy --config nostrfy.toml stop

验证已启动:

sh
curl http://127.0.0.1:8080/health

=> {"status":"ok"}

日志与统计

守护进程写入 daemon.log_file。当文件超过 max_log_size_bytes 时会 自动轮转(nostrfy.log.1、.2……最多 max_log_files 代):

sh
tail -f nostrfy.log

日志级别由 RUST_LOG 环境变量控制(例如 RUST_LOG=debug)。

统计

从 CLI 获取实时统计:

sh
nostrfy stats

或通过 HTTP:

sh
curl http://127.0.0.1:8080/relay/stats

显示连接数、接受/拒绝的事件、数据库大小等。

Prometheus 指标

sh
curl http://127.0.0.1:8080/metrics

热重载(SIGHUP)

编辑配置文件后,无需重启即可重载:

sh
kill -HUP $(cat nostrfy.pid)
重载时生效需要重启
中继身份、public_urlprivate_key
大部分 [limits]、reject_ephemeral、enabled_git、enabled_nip78_authapi_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:

toml
[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_memory1 或 2LMDB 映射是大型稀疏虚拟预留

在 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 标签)在 索引时会被跳过;事件仍会被存储。