资源限制
每项限制都有安全的默认值。如果某项限制为 0(明确表示”关闭”的除外)或超出范围,中继会拒绝启动。不存在”无限制”设置。
可配置的限制
Section titled “可配置的限制”除 [server.offline_queue] 下的两项外,所有配置项都位于 [server]。
| 配置项 | 默认值 | 允许范围 | 限制对象 | 超出时 |
|---|---|---|---|---|
max_connections | 10000 | ≥ 1 | 同时存在的 TCP 连接,从 accept() 起计数 | 新连接立即关闭 |
max_connections_per_ip | 64 | 0 – max_connections(0 = 关闭) | 来自同一 IPv4 地址或同一 IPv6 /64 的连接 | 新连接立即关闭 |
max_connections_per_ipv6_48 | 256 | 0 – max_connections(0 = 关闭) | 来自同一 IPv6 /48 的连接总数 | 新连接立即关闭 |
rate_limit_bps | 1048576 | ≥ 1024 | 每个连接每秒入站字节数 | 连接被限速,不会被关闭 |
max_message_bytes | 4194304(4 MiB) | 65536 – 67108864 | 客户端发送的最大 WebSocket 消息或帧 | 连接关闭 |
connection_timeout_secs | 300 | 60 – 86400 | 连接可以不发送任何数据的时长 | 连接关闭 |
identification_timeout_secs | 15 | 1 – 300,且 ≤ connection_timeout_secs | 新连接在证明身份之前可以停留的时长(只发送 hello 不算) | 连接关闭 |
max_pending_circuit_targets | 18000 | 1 – 1000000 | 全中继范围内有待处理会话请求的不同目标数 | 较轻的来源驱逐最重来源最早的请求,或返回 RateLimited |
max_pending_per_connection | 32 | 1 – max_pending_circuit_targets | 单个连接在 10 分钟内可以有待处理请求的不同目标数 | 请求被拒绝 |
max_pending_per_source | 1024 | 1 – max_pending_circuit_targets | 来自同一来源的待处理请求 | 请求被拒绝 |
max_circuits_per_connection | 256 | 1 – 65536 | 单个连接创建的电路,以及单个身份跨重连所属的电路 | 请求被拒绝 |
max_circuits | 200000 | 2 – 10000000 | 整个服务器中的电路路由条目(每对两个) | 较轻来源的新配对拆除最重来源最新的配对,或返回 RateLimited |
max_circuits_per_source | max_circuits / 64(3125) | 2 – max_circuits | 从同一来源发起请求的身份所持有的电路端 | 请求被拒绝 |
offline_queue_max_total_bytes | 268435456(256 MiB) | ≥ 单接收方上限 | 离线队列中所有接收方的总字节数 | 消息被拒绝,不确认 |
offline_queue_max_bytes_per_recipient | 16777216(16 MiB) | ≥ max_message_bytes | 为单个接收方排队的离线字节数 | 消息被拒绝,不确认 |
tls_handshake_timeout_secs | 10 | 1 – 120 | 完成 TLS 握手的时间 | 连接断开 |
[server.offline_queue] ttl_secs | 259200(72 小时) | 3600 – 259200(72 小时为上限;更大的值会导致启动失败) | 排队消息的存活时间 | 消息被删除 |
[server.offline_queue] knock_budget_bytes | 16777216(16 MiB) | 排队消息请求的字节数,与消息分开计算 | 驱逐最重来源最早的请求 |
启动时中继会记录主要的取值:Limits: max_connections=..., max_connections_per_ip=..., rate_limit_bps=..., max_message_bytes=..., connection_timeout_secs=...。
客户端接受来自中继、最大 4 MiB + 64 KiB 的帧,这是按默认的 max_message_bytes 设定的。调高 max_message_bytes 并不能让用户发送更大的消息。
max_connections 从连接被接受的那一刻开始计数,包括 TLS 握手、HTTP 升级和伪装响应。第 N+1 个连接在任何 TLS 或 HTTP 处理之前就被关闭。请让文件描述符上限(单元中的 LimitNOFILE)远高于该值。
max_connections_per_ip 将 IPv4 连接按地址分组,IPv6 连接按 /64 前缀分组。按地址计数的表只在连接存在期间保存每个地址的带密钥哈希,地址本身从不记录。在 TCP 前端之后,请把两个按地址的上限都设为 0,并在前端中限制客户端。
每个连接可以发送 6 次 hello 或密钥发布,之后每 10 秒一次。超出后连接会被关闭(reason="auth_rate")。
按来源的公平份额
Section titled “按来源的公平份额”来源是一个 IPv4 地址或一个 IPv6 /48。同一来源后面的所有人共享一份额度:
max_pending_per_source个待处理会话请求。当全中继的表已满时,来自较轻来源的请求会驱逐最重来源最早的请求(pending_evicted)。max_circuits_per_source个电路端。当电路表已满时,来自较轻来源的新配对会拆除最重来源最新的配对(circuit_evicted)。该配对中的双方都会收到CircuitClosed,并在下一次请求时重新配对。- 最多占消息请求预算的 1/16,以及每个请求方 24 小时新请求表的 1/16。
当 max_connections_per_ip = 0 时这些份额关闭(此时所有连接看起来都来自同一个地址),除非 proxy_protocol = true 从 TCP 前端提供客户端地址。
运营商级 NAT。 许多正常用户可能共享一个 IPv4 地址。该地址可能因此成为被削减或被驱逐的来源,其用户共享一份公平份额。对于有许多此类用户的中继,请调高 max_connections_per_ip、max_pending_per_source 和 max_circuits_per_source。
每个连接对其发送的字节使用令牌桶:
- 补充速率为每秒
rate_limit_bps字节(尽管名字里是 bps)。 - 突发容量为
max(rate_limit_bps, max_message_bytes),因此一条最大尺寸的消息总能通过。 - 每条消息的开销是其长度加 256 字节,因此大量极小帧也会受限。在默认速率下约为每秒 4096 条消息。
超出预算的连接被限速,而不是被断开。中继停止读取它,直到令牌桶补充,TCP 流量控制会让客户端放慢。
- 空闲: 在
connection_timeout_secs内没有发送任何数据的连接会被关闭。客户端每 22 到 38 秒发送一次心跳,因此已连接的应用不受影响。 - 身份证明: 新连接必须在
identification_timeout_secs内完成一次签名的密钥发布。 - 心跳: 中继每 25 到 35 秒发送一次 ping,60 秒内没有应答的连接会被关闭。
- 升级: 客户端必须在 5 秒内发送完整的 HTTP 请求头,并在 10 秒内完成 WebSocket 升级。请求头上限为 8 KiB。
只有双方都请求时电路才会建立,并在双方端点都连接时存在。任一方断开或某个成员关闭它时,电路结束。没有基于时间的过期。单方面的请求最多保留 10 分钟,首次请求会指明目标并携带一个消息请求(一个固定 5120 字节的 “knock”),为接收方排队。此后同两人之间的请求是盲化的:携带令牌而不是目标,不带 knock,在线路上大小相同。
消息请求存储有自己的预算(knock_budget_bytes),从不计入消息。除按来源的份额外:每个接收方最多 200 个,每对最多一个(较新的会替换旧的),每个请求方每 24 小时最多 100 个新请求。请求最多 72 小时后过期。
以下限制不可配置。
| 限制 | 值 |
|---|---|
| 每个连接的出站队列 | 256 条消息且 16 MiB |
| 所有出站队列合计 | 1 GiB |
| 所有 WebSocket 缓冲区合计 | 512 MiB |
| 等待接收方队列空间 | 5 秒,之后接收方被视为卡住并断开 |
| 写入停滞 | 有消息等待时 30 秒内未写出任何数据,则关闭连接 |
| 每个接收方的离线消息 | 1000 条 |
| 跟踪送达的消息 | 100,000 条 |
慢速消费者与内存预算
Section titled “慢速消费者与内存预算”当接收方停止读取时,转发的消息使用背压。发送方最多等待 5 秒以获得队列空间,在此期间中继停止读取发送方。如果没有空间腾出,接收方会被断开,消息进入离线队列。服务器生成的消息(确认、ping)无法等待;如果接收方的队列已满,它会被立即断开。
当全中继的出站预算(1 GiB)或 WebSocket 缓冲区预算(512 MiB)用尽时,中继会关闭占用最多的来源中最重的连接(outbound_shed、buffer_shed)。
离线队列与确认
Section titled “离线队列与确认”MessageAck 表示中继已接收该消息:它已为在线接收方排队,或为离线接收方暂存。上限在确认之前检查。放不下的消息会被拒绝:发送方收到 RateLimited 错误且没有确认。已排队的消息从不会为腾出空间而被丢弃。
排队条目默认在内存中保存 ttl_secs(72 小时)。可选的加密持久化参见离线队列。
已接受的联系人可以在接收方离线时填满其离线队列(1000 条消息或 16 MiB)。之后其他发送方会被拒绝,直到接收方重新连接或消息过期。他们会立即知道,因为收不到确认。
中继的发布构建使用可展开的 panic 并开启溢出检查。一个连接中的故障只会关闭该连接(task_panic),中继会继续为其他所有人服务。
当 [metrics] enabled = true(默认)时,中继在 127.0.0.1:9090 上提供 Prometheus 指标:
curl -s http://127.0.0.1:9090/metrics | grep '^rchat_'这些指标只是聚合计数器。每个指标都命名为 rchat_*(或标准的 process_*),唯一的标签是 reason。它们不含任何标识符或地址。
该端点没有认证。请保持在回环地址上,并通过 SSH 隧道访问。非回环的 bind_addr 会让中继在启动时停止,除非设置 [metrics] allow_public = true;此时请用防火墙保护它。它最多同时服务 8 个连接,每个连接的请求头超时 5 秒、总超时 30 秒。如果无法绑定,中继会记录 Metrics endpoint failed on ... 并继续转发。
rchat_rejections_total{reason=...}:
| 原因 | 含义 |
|---|---|
max_connections | 在全局上限处被拒绝的连接 |
per_ip_cap | 在按地址上限处被拒绝的连接 |
per_prefix_cap | 在 IPv6 /48 上限处被拒绝的连接 |
rate_limit | 因超过 rate_limit_bps 而暂停读取 |
oversized_frame | 因超过 max_message_bytes 而关闭的连接 |
handshake_timeout | 因未完成 WebSocket 升级而关闭的连接 |
idle_timeout | 因空闲而关闭的连接 |
identification_timeout | 因未及时证明身份而关闭的连接 |
auth_rate | 因超过 hello 与发布预算而关闭的连接 |
key_auth | 被拒绝的发布:密钥包签名无效,或连接证明与本 TLS 会话不匹配 |
pending_targets | 在待处理上限处被拒绝的请求 |
pending_evicted | 为较轻来源的请求而被驱逐的重来源待处理请求 |
circuit_cap | 在电路上限处被拒绝的请求 |
circuit_evicted | 为较轻来源而被拆除的重来源最新配对 |
knock_quota | 消息请求未排队:请求方的 24 小时配额已用完 |
knock_evicted | 被驱逐的排队消息请求(预算或单接收方上限) |
queue_overflow | 因离线队列已满而被拒绝的消息 |
slow_consumer | 因未清空队列而关闭的连接 |
writer_stall | 因套接字 30 秒内未接受任何数据而关闭的连接 |
outbound_shed | 因占用耗尽的出站预算最多而关闭的连接 |
global_outbound | 遇到出站预算已耗尽的发送 |
buffer_shed | 因占用 WebSocket 缓冲区预算最多而关闭的连接 |
task_panic | 某个连接任务发生 panic;只关闭了该连接 |
proxy_header | proxy_protocol = true 时:PROXY v2 头缺失、格式错误或迟到 |
proxy_not_loopback | proxy_protocol = true 时:拒绝来自非回环对端的连接 |
rchat_tls_cert_not_after_seconds 是当前所用证书的到期时间(Unix 时间)。中继还维护连接、电路和转发流量计数器(rchat_connections_total、rchat_circuits_total、rchat_relay_bytes_total 等)。
有用的告警:
rchat_tls_cert_not_after_seconds - time() < 7 * 86400:证书将在一周内到期,说明续期失败。max_connections、circuit_cap、circuit_evicted或queue_overflow的rchat_rejections_total持续上升:某项限制经常被触发。请判断是滥用还是真实增长。- 出现任何
task_panic:请报告。