Wholesale 集群经典配置参考
2 节点 Wholesale 集群的完整配置参考(MySQL 或 PostgreSQL)。展示每个节点的完整配置,标注节点间差异字段。
网络拓扑
┌──────────────────────────────────────────────────────────────┐
│ SIP 客户端 / 运营商网关 │
│ (通过 SIP 负载均衡或直连节点 IP) │
└──────────┬──────────────────────────────────┬───────────────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Node 1 │◄── SIP MESSAGE ──►│ Node 2 │
│ 10.0.0.11 │ │ 10.0.0.12 │
│ 主节点 │ │ 从节点 │
│ + SipFlow │ │ │
└──────┬──────┘ └──────┬──────┘
│ │
│ ┌──────────────┐ │
└────────►│ MySQL │◄────────┘
│ 10.0.0.20 │
└──────────────┘
- SipFlow:仅在 Node 1 上以本地模式运行
- 最少 2 节点实现高可用;生产环境建议 3 节点以上
Node 1 — 10.0.0.11(主节点 + SipFlow)
config.toml
# ── 通用配置 ────────────────────────────────────────────────────
http_addr = "0.0.0.0:8080"
# https_addr = "0.0.0.0:8443" # 启用 HTTPS 时取消注释
database_url = "mysql://rustpbx:WsDbP@ss2026@10.0.0.20:3306/rustpbx"
external_ip = "10.0.0.11" # ← 节点特有:本节点 IP
log_level = "info"
# log_file = "/var/log/rustpbx.log"
# log_rotation = "daily"
# ── RTP 媒体端口 ────────────────────────────────────────────────
rtp_start_port = 20000 # RTP UDP 端口范围
rtp_end_port = 40000
# ── WebRTC(可选)──────────────────────────────────────────────
# webrtc_port_start = 30000
# webrtc_port_end = 40000
# ── SIP 代理 ──────────────────────────────────────────────────
[proxy]
addr = "0.0.0.0"
udp_port = 5060
# tcp_port = 5060 # SIP TCP 传输
# tls_port = 5061 # SIP TLS(需 ssl_certificate)
# ws_port = 5062 # SIP WebSocket
modules = ["acl", "auth", "registrar", "call"]
media_proxy = "auto" # all | auto | nat | none | bypass
max_concurrency = 1000
# NAT 穿透 realms —— 列出所有节点 IP + 公网 VIP
realms = ["10.0.0.11", "10.0.0.12", "203.0.113.100"]
# 启用 Addon
addons = ["wholesale"]
# 配置文件路径
generated_dir = "/static/docs-zh/wholesale/config"
routes_files = ["config/routes/*.toml"]
trunks_files = ["config/trunks/*.toml"]
queues_files = ["config/queue/*.toml"]
# DoS 防护
[proxy.dos]
enabled = true
max_cps_per_ip = 50
max_concurrent_per_ip = 200
scan_detection = true
# ── 控制台(Web 管理)─────────────────────────────────────────
[console]
session_secret = "wholesale-cluster-secret-2026" # ← 所有节点必须一致
allow_registration = false
# base_path = "/" # 反向代理路径前缀
# api_prefix = "/api" # API 路由前缀
# ── 核心集群(注册/在线状态同步)──────────────────────────────
[cluster]
peers = [ # ← 节点特有:仅列出其他节点
{ addr = "10.0.0.12", sip_port = 5060, ami_port = 8080 },
]
# ── SipFlow(本节点本地模式)──────────────────────────────────
[sipflow]
[sipflow.local]
subdirs = "daily"
flush_count = 100
flush_interval_secs = 5
id_cache_size = 10000
# ── 录音(可选)───────────────────────────────────────────────
[recording]
enabled = false
# auto_start = false
# type = "local" # local | http | s3
# ── CDR ────────────────────────────────────────────────────────
[callrecord]
enabled = true
config/wholesale.toml
# ── Wholesale 集群 ──────────────────────────────────────────
[cluster]
[[cluster.peers]]
name = "node-2"
url = "http://10.0.0.12:8080"
api_key = "wholesale-cluster-api-key-2026" # ← 所有节点必须一致
# ── 路由缓存 ────────────────────────────────────────────────
[route_cache]
capacity = 10000
ttl_secs = 30
# ── 熔断器 ──────────────────────────────────────────────────
[circuit_breaker]
failure_threshold = 5
open_duration_secs = 30
half_open_probes = 1
failure_codes = [503, 408, 504]
# ── 滑动窗口 ASR/ACD 监控 ──────────────────────────────────
[sliding_window]
enabled = true
window_secs = 300
max_events_per_trunk = 10000
asr_alert_threshold = 30.0
min_calls_for_stats = 10
Node 2 — 10.0.0.12(从节点)
config.toml
# ── 通用配置 ────────────────────────────────────────────────────
http_addr = "0.0.0.0:8080"
database_url = "mysql://rustpbx:WsDbP@ss2026@10.0.0.20:3306/rustpbx"
external_ip = "10.0.0.12" # ← 不同:Node 2 的 IP
log_level = "info"
# ── RTP 媒体端口 ────────────────────────────────────────────────
rtp_start_port = 20000
rtp_end_port = 40000
# ── SIP 代理 ──────────────────────────────────────────────────
[proxy]
addr = "0.0.0.0"
udp_port = 5060
modules = ["acl", "auth", "registrar", "call"]
media_proxy = "auto"
max_concurrency = 1000
realms = ["10.0.0.11", "10.0.0.12", "203.0.113.100"]
addons = ["wholesale"]
generated_dir = "/static/docs-zh/wholesale/config"
routes_files = ["config/routes/*.toml"]
trunks_files = ["config/trunks/*.toml"]
queues_files = ["config/queue/*.toml"]
[proxy.dos]
enabled = true
max_cps_per_ip = 50
max_concurrent_per_ip = 200
scan_detection = true
# ── 控制台 ────────────────────────────────────────────────────
[console]
session_secret = "wholesale-cluster-secret-2026" # ← 一致
allow_registration = false
# ── 核心集群 ──────────────────────────────────────────────────
[cluster]
peers = [ # ← 不同:列出其他节点
{ addr = "10.0.0.11", sip_port = 5060, ami_port = 8080 },
]
# ── SipFlow(远程模式 —— 连接到 Node 1)─────────────────────
[sipflow]
[sipflow.remote]
flush_interval_secs = 5
[[sipflow.remote.nodes]]
udp = "10.0.0.11:6060"
http = "http://10.0.0.11:6060"
# ── 录音 ──────────────────────────────────────────────────────
[recording]
enabled = false
# ── CDR ────────────────────────────────────────────────────────
[callrecord]
enabled = true
config/wholesale.toml
[cluster]
[[cluster.peers]]
name = "node-1"
url = "http://10.0.0.11:8080"
api_key = "wholesale-cluster-api-key-2026" # ← 一致
[route_cache]
capacity = 10000
ttl_secs = 30
[circuit_breaker]
failure_threshold = 5
open_duration_secs = 30
half_open_probes = 1
failure_codes = [503, 408, 504]
[sliding_window]
enabled = true
window_secs = 300
max_events_per_trunk = 10000
asr_alert_threshold = 30.0
min_calls_for_stats = 10
节点间差异汇总
| 字段 | Node 1 (10.0.0.11) | Node 2 (10.0.0.12) | Node N |
|---|---|---|---|
external_ip | 10.0.0.11 | 10.0.0.12 | 本节点 IP |
[cluster].peers | [10.0.0.12] | [10.0.0.11] | 所有其他节点 |
[sipflow] | local 本地模式 | remote → Node 1 | remote → Node 1 |
wholesale [cluster].peers | node-2 @ 10.0.0.12 | node-1 @ 10.0.0.11 | 所有其他节点 |
所有节点必须一致的配置:
| 字段 | 原因 |
|---|---|
database_url | 共享数据库 |
console.session_secret | Session Cookie 验证 |
wholesale api_key | 节点间 API 认证 |
realms | NAT 穿透需包含所有节点 IP |
rtp_start_port / rtp_end_port | 集群内端口必须一致 |
addons | 每个节点功能集相同 |
| 中继/路由 TOML 文件 | 呼叫路由必须一致 |
PostgreSQL 变体
将各节点 config.toml 中的 database_url 替换为:
database_url = "postgres://rustpbx:WsDbP@ss2026@10.0.0.20:5432/rustpbx"
PostgreSQL 安装
# 安装
docker run -d --name postgres \
--restart always \
-e POSTGRES_DB=rustpbx \
-e POSTGRES_USER=rustpbx \
-e POSTGRES_PASSWORD=WsDbP@ss2026 \
-v /data/postgres:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16
# 验证
psql -h 10.0.0.20 -U rustpbx -d rustpbx -c "SELECT 1"
# 调优参数(postgresql.conf)
# shared_buffers = 4GB
# max_connections = 300
# effective_cache_size = 12GB
# work_mem = 64MB
PostgreSQL CDR 按月分区
-- 按月分区(PG 10+)
CREATE TABLE wholesale_cdrs (
id BIGSERIAL,
call_id TEXT,
tenant_id BIGINT,
trunk_id BIGINT,
caller TEXT,
callee TEXT,
call_start TIMESTAMPTZ,
call_end TIMESTAMPTZ,
talk_duration INTEGER,
billable_duration INTEGER,
sell_rate DOUBLE PRECISION,
sell_price DOUBLE PRECISION,
buy_rate DOUBLE PRECISION,
buy_price DOUBLE PRECISION,
profit DOUBLE PRECISION,
sip_code INTEGER,
answered BOOLEAN
) PARTITION BY RANGE (call_start);
CREATE TABLE wholesale_cdrs_202605 PARTITION OF wholesale_cdrs
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE TABLE wholesale_cdrs_202606 PARTITION OF wholesale_cdrs
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
CREATE TABLE wholesale_cdrs_default PARTITION OF wholesale_cdrs DEFAULT;
SipFlow 在 Node 1 上的说明
SipFlow 在 Node 1 上以本地模式运行,使用 RustPBX 进程内嵌的 SipFlow —— 无需独立部署二进制。
Node 2(及其他节点)通过远程后端连接到 Node 1 的 SipFlow:
# Node 2 的 config.toml —— 远程 SipFlow 指向 Node 1
[sipflow]
[sipflow.remote]
flush_interval_secs = 5
[[sipflow.remote.nodes]]
udp = "10.0.0.11:6060"
http = "http://10.0.0.11:6060"
Node 1 的 SipFlow 本地后端默认监听,远程节点只需知道 Node 1 的 SipFlow UDP 和 HTTP 端口即可。
如需更高的 SipFlow 可用性,可在独立服务器上部署 sipflow 二进制,所有节点均配置为 remote 模式。详见 SipFlow 集群。
Docker Compose 参考
2 节点集群的 docker-compose.yml:
version: "3.8"
services:
mysql:
image: mysql:8.4-oracle
restart: always
environment:
MYSQL_ROOT_PASSWORD: RootP@ss2026
MYSQL_DATABASE: rustpbx
MYSQL_CHARACTER_SET_SERVER: utf8mb4
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
command: >
--max-connections=500
--innodb-buffer-pool-size=4G
--character-set-server=utf8mb4
node1:
image: docker.cnb.cool/miuda.ai/rustpbx:0.4.5
restart: always
network_mode: host
volumes:
- /static/docs-zh/wholesale/node1/config.toml:/app/config.toml
- /static/docs-zh/wholesale/node1/config:/app/config
depends_on:
- mysql
node2:
image: docker.cnb.cool/miuda.ai/rustpbx:0.4.5
restart: always
network_mode: host
volumes:
- /static/docs-zh/wholesale/node2/config.toml:/app/config.toml
- /static/docs-zh/wholesale/node2/config:/app/config
depends_on:
- mysql
volumes:
mysql_data:
目录结构:
/static/docs-zh/wholesale/
├── docker-compose.yml
├── node1/
│ ├── config.toml ← Node 1 配置(external_ip=10.0.0.11, SipFlow local)
│ └── config/
│ ├── wholesale.toml ← Wholesale 集群配置
│ ├── trunks/ ← 共享中继定义
│ ├── routes/ ← 共享路由定义
│ └── acl/ ← 共享 ACL 规则
└── node2/
├── config.toml ← Node 2 配置(external_ip=10.0.0.12, SipFlow remote)
└── config/
├── wholesale.toml
├── trunks/ ← 与 node1 相同
├── routes/
└── acl/
快速启动命令
# 1. 创建目录结构
mkdir -p node1/config/{trunks,routes,acl} node2/config/{trunks,routes,acl}
# 2. 写入配置(使用上面完整示例)
# ... 为每个节点写入 config.toml 和 wholesale.toml ...
# 3. 同步共享配置文件
cp -r node1/config/trunks/* node2/config/trunks/
cp -r node1/config/routes/* node2/config/routes/
cp -r node1/config/acl/* node2/config/acl/
# 4. 启动集群
docker compose up -d
# 5. 等待 Node 1 完成 migration,验证
sleep 10
curl http://10.0.0.11:8080/healthz
curl http://10.0.0.12:8080/healthz
# 6. 创建超级用户(任一节点执行一次)
docker compose exec node1 /app/rustpbx --create-superuser admin Admin@2026