地理联邦
概述
OmniMessage 使用 基于 HTTP 的联邦 来支持跨数据中心或区域的多控制器部署。每个控制器独立运行——管理自己的消息队列、路由表和连接的前端。控制器通过 DNS SRV 记录(或静态配置)发现彼此,通过 HTTPS 交换健康和前端注册信息,并在路由确定消息属于远程站点时,将消息转发到其前端服务目标的控制器。
当站点间链接中断时,消息会在本地排队,并在链接恢复时自动转发到远程控制器。没有消息丢失,并且在分区期间不会重新路由到本地前端。
关键特性
| 方面 | 细节 |
|---|---|
| 网络 | 在任何 WAN 上工作,包括不可靠的链接 |
| 共享状态 | 无 — 每个控制器都是独立的 |
| 安全性 | 使用 TLS 的 HTTPS |
| 端口 | 单一 HTTPS 端口(8443) |
| 规模目标 | 5-20 个控制器 |
| 分区处理 | 排队等待(无��裂脑) |
架构
关键设计决策
- 消息流:源控制器通过 HTTPS 将消息推送到目标控制器。前端仅与其主控制器通信。
- 分区处理:排队等待。当远���控制器不可达时,不会重新路由到本地前端。
- 配置同步:无。每个控制器独立管理自己的路由表。
- 对等网:全网格。所有控制器与所有其他控制器交换健康和前端注册信息。
- 发现:DNS SRV 记录(推荐)或静态对等列表(用于没有动态 DNS 的环境)。
消息转发流程
当消息到达控制器并且路由确定目标前端位于远程控制器上时,消息通过 HTTPS 转发到远程控制器以进行本地交付。
对等健康和注册同步
联邦控制器通过两个周期性循环保持对彼此的意识:
健康检查周期
每 10 秒(可配置),每个控制器在每个已知对等体上调用 POST /api/federation/health。该调用是双向的——调用者发送自己的状态并在响应中接收对等体的状态。对等体在连续 3 次失败后被标记为 不健康。
注册同步周期
每 15 秒(可配置),每个控制器在每个健康的对等体上调用 POST /api/federation/registry。该调用交换前端列表——调用者发送其活动前端并接收对等体的活动前端。这是控制器了解哪个对等体拥有哪个前端的方式。
对等体状态及其对消息转发的影响:
| 状态 | 健康检查 | 消息转发 | 注册同步 |
|---|---|---|---|
| 未知 | 进行中 | 排队 | 未尝试 |
| 健康 | 通过 | 立即转发 | 活跃 |
| 降级 | 1-2 次失败 | 排队 | 未尝试 |
| 不健康 | 3 次以上失败 | 排队 | 未尝试 |
消息状态
联邦引入了三种额外的消息状态,这些状态在消息队列中可见:
| 状态 | 意义 |
|---|---|
:forwarded | 消息已成功发送到目标控制器 |
:forward_queued | 消息在转发队列中等待,因为目标控制器不可达 |
:forward_failed | 消息超过最大重试次数,将不再重试 |
这些状态与标准状态(:pending、:delivered、:failed、:retry)一起出现在消息队列 Web UI 和 API 响应中。
配置
联邦在 config/runtime.exs 中的 :federation 键下进行配置。默认情况下是 禁用 的,必须显式启用。
最小配置(DNS SRV 发现)
# config/runtime.exs
config :sms_c, :federation,
enabled: true,
dns_srv_domain: "_smsc._tcp.smsc.example.com"
完整配置参考
# config/runtime.exs
config :sms_c, :federation,
# 主开关 — 当为 false 时,联邦完全不活动
enabled: true,
# 用于对等发现的 DNS SRV 域(留空以使用 static_peers)
dns_srv_domain: "_smsc._tcp.smsc.example.com",
# 重新解析 DNS SRV 记录的频率(毫秒)
dns_poll_interval_ms: 30_000,
# 与所有已知对等体交换健康状态的频率(毫秒)
health_check_interval_ms: 10_000,
# 与健康对等体交换前端注册的频率(毫秒)
registry_sync_interval_ms: 15_000,
# 转发工作者重试排队消息的频率(毫秒)
forward_retry_interval_ms: 5_000,
# 在将排队消息标记为 :forward_failed 之前的最大重试次数
forward_max_retries: 50,
# 所有对等 API 调用的 HTTP 超时(毫秒)
http_timeout_ms: 5_000,
# 从 DNS SRV 记录构建对等 URL 时使用的 API 端口
api_port: 8443,
# 静态对等列表 — 当 dns_srv_domain 为空时使用
static_peers: []
联邦参数
| 参数 | 类型 | 必需 | 默认 | 描述 |
|---|---|---|---|---|
enabled | 布尔 | 是 | false | 主开关。当为 false 时,联邦服务启动但不发现或联系对等体。 |
dns_srv_domain | 字符串 | 否 | "" | 用于自动对等发现的 DNS SRV 域。当为空时,使用 static_peers。 |
dns_poll_interval_ms | 整数 | 否 | 30000 | DNS SRV 重新解析尝试之间的间隔。较低的值可以更快地检测到新对等体,但会增加 DNS 负载。 |
health_check_interval_ms | 整数 | 否 | 10000 | 健康检查轮次之间的间隔。每轮联系每个已知对等体。 |
registry_sync_interval_ms | 整数 | 否 | 15000 | 前端注册同步轮次之间的间隔。每轮联系每个健康对等体。 |
forward_retry_interval_ms | 整数 | 否 | 5000 | 转发工作者检查健康对等体并重试排队消息的频率。 |
forward_max_retries | 整数 | 否 | 50 | 每条消息的最大转发尝试次数,超过此次数将标记为 :forward_failed。在默认重试间隔下,50 次重试 = ~4 分钟的排队。 |
http_timeout_ms | 整数 | 否 | 5000 | 对每个对等体的单独 HTTPS 调用的超时。适用于健康检查、注册同步和消息转发。 |
api_port | 整数 | 否 | 8443 | 从 DNS SRV 记录构建对等 URL 时使用的 API 端口。必须与 config :api_ex 中配置的 API 端口匹配。 |
static_peers | 列表 | 否 | [] | 用于没有 DNS SRV 的环境的对等体配置列表。每个条目是一个包含 host 和 port 键的映射。 |
静态对等配置
对于没有 DNS SRV 的环境,显式配置对等体:
config :sms_c, :federation,
enabled: true,
dns_srv_domain: "",
static_peers: [
%{host: "10.0.1.2", port: 8443},
%{host: "10.0.2.2", port: 8443},
%{host: "10.0.3.2", port: 8443}
]
| 参数 | 类型 | 必需 | 默认 | 描述 |
|---|---|---|---|---|
host | 字符串 | 是 | - | 远程控制器的 IP 地址或主机名。 |
port | 整数 | 否 | 8443 | 远程控制器的 HTTPS API 端口。 |
DNS SRV 记录设置
DNS SRV 记录允许控制器自动发现彼此。每个控制器解析配置的域,并使用结果的主机/端口对作为对等体。
DNS 区域配置:
; 优先级 权重 端口 目标
_smsc._tcp.smsc.example.com. 86400 IN SRV 10 100 8443 smsc-alpha.smsc.example.com.
_smsc._tcp.smsc.example.com. 86400 IN SRV 10 100 8443 smsc-bravo.smsc.example.com.
_smsc._tcp.smsc.example.com. 86400 IN SRV 10 100 8443 smsc-charlie.smsc.example.com.
; 目标的 A 记录
smsc-alpha.smsc.example.com. 86400 IN A 10.0.1.2
smsc-bravo.smsc.example.com. 86400 IN A 10.0.2.2
smsc-charlie.smsc.example.com. 86400 IN A 10.0.3.2
添加新站点:向 DNS 添加新的 SRV 记录。所有现有控制器将在一个 dns_poll_interval_ms 周期内发现新的对等体(默认 30 秒)。
移除站点:从 DNS 中移除 SRV 记录。现有控制器将在一个轮询周期内移除已离开的对等体。任何排队给已移除对等体的消息将继续重试,直到达到 forward_max_retries,然后标记为 :forward_failed。
网络要求
联邦只需要在站点之间的单一 HTTPS 端口。
| 端口 | 协议 | 方向 | 目的 |
|---|---|---|---|
| 8443 | TCP/TLS | 双向 | 所有联邦流量(健康、注册、转发、交付状态) |
| 53 | UDP/TCP | 出站 | DNS SRV 解析(如果使用 DNS 发现) |
防火墙规则(每个站点):
# 允许来自对等控制器 IP 的联邦流量
iptables -A INPUT -p tcp -s 10.0.1.0/24 --dport 8443 -j ACCEPT
iptables -A INPUT -p tcp -s 10.0.2.0/24 --dport 8443 -j ACCEPT
iptables -A INPUT -p tcp -s 10.0.3.0/24 --dport 8443 -j ACCEPT
联邦 API 端点
所有联邦端点都在标准 API 端口(8443)下的 /api/federation/ 提供。这些端点由对等控制器调用,而不是由外部客户端或前端调用。
| 端点 | 方法 | 目的 |
|---|---|---|
/api/federation/identity | GET | 返回此控制器的节点名称和协议版本 |
/api/federation/health | POST | 双向健康交换 — 调用者发送状态,被���用者响应自己的状态 |
/api/federation/registry | POST | 双向前端注册交换 |
/api/federation/forward | POST | 接收转发的消息以进行本地交付 |
/api/federation/delivery_status | POST | 接收来自目标控制器的交付确认 |
所有请求和响应均使用 JSON。对等体标识通过 X-Federation-Node 头部传递。
部署示例
示例 1:使用 DNS SRV 的三站点部署
三个数据中心,每个数据中心都有本地前端以服务其订阅者基础。DNS SRV 提供发现。
# 站点 A — 数据中心 Alpha (config/runtime.exs)
config :sms_c,
smsc_node_name: "alpha-dc01-smsc01"
config :sms_c, :federation,
enabled: true,
dns_srv_domain: "_smsc._tcp.smsc.example.com"
# 站点 B — 数据中心 Bravo (config/runtime.exs)
config :sms_c,
smsc_node_name: "bravo-dc01-smsc01"
config :sms_c, :federation,
enabled: true,
dns_srv_domain: "_smsc._tcp.smsc.example.com"
# 站点 C — 数据中心 Charlie (config/runtime.exs)
config :sms_c,
smsc_node_name: "charlie-dc01-smsc01"
config :sms_c, :federation,
enabled: true,
dns_srv_domain: "_smsc._tcp.smsc.example.com"
工作原理:所有三个控制器解析相同的 SRV 域并自动发现彼此。每个控制器注��其本地前端。当到达数据中心 Alpha 的消息目标是由数据中心 Bravo 服务的订阅者时,路由表选择 bravo-dc01-smsc01 作为目标。联邦层检测到该前端属于控制器 B,并通过 HTTPS 转发消息。
示例 2:使用静态对等体的双站点主动-主动
两个通过专用链接连接的数据中心。没有 DNS SRV 可用。
# 主数据中心 (config/runtime.exs)
config :sms_c,
smsc_node_name: "primary-smsc01"
config :sms_c, :federation,
enabled: true,
dns_srv_domain: "",
static_peers: [
%{host: "10.200.1.5", port: 8443}
]
# 次要数据中心 (config/runtime.exs)
config :sms_c,
smsc_node_name: "secondary-smsc01"
config :sms_c, :federation,
enabled: true,
dns_srv_domain: "",
static_peers: [
%{host: "10.100.1.5", port: 8443}
]
工作原理:每个控制器都配置了对方的 IP 地址。健康检查和注册同步通过专用链接进行。如果链接失败,消息会在本地排队,并在恢复时排出。
示例 3:低延迟链接的激进重试
对于通过可靠、低延迟链接连接的站点,其中快速转发恢复很重要:
config :sms_c, :federation,
enabled: true,
dns_srv_domain: "_smsc._tcp.cluster.internal",
health_check_interval_ms: 5_000,
registry_sync_interval_ms: 5_000,
forward_retry_interval_ms: 1_000,
http_timeout_ms: 2_000
用例:校园或城市区域部署,其中站点通过 < 10 毫秒 RTT 链接连接,快速故障转移比最小化控制流量更重要。
指标
联邦公开了以下可以通过 Prometheus 在 9568 端口观察的遥测事件。
指标: sms_c_federation_forward_completed_count
类型: 计数器
描述: 成功转发到远程控制器的消息数量(包括来自转发队列的重试)
标签:
dest_controller- 目标控制器标识符(主机:端口)
示例查询:
# 每分钟转发速率
rate(sms_c_federation_forward_completed_count[5m]) * 60
# 按目标控制器的转发数量
sum by (dest_controller) (rate(sms_c_federation_forward_completed_count[5m]))
此外,标准消息指标(:forwarded、:forward_queued、:forward_failed 状态)出现在现有队列统计端点和 Web UI 中。
关键指标监控
| 监控内容 | 位置 | 警报阈值 |
|---|---|---|
| 转发队列深度 | GET /api/status 或 Web UI | > 100 待处理(调查链接) |
| 对等健康状态 | 在每个对等体上 GET /api/federation/identity | 任何对等体不健康 > 5 分钟 |
| 转发失败率 | Prometheus :forward_failed 状态计数 | 任何非零(消息被丢弃) |
| 健康检查延迟 | 应用日志 | > 2 秒(链接降级) |
故障排除
对等体未发现
症状:控制器日志显示没有发现对等体;cluster_status 返回为空。
可能原因:
- DNS SRV 域不正确或无法解析
- 控制器主机无法访问 DNS 服务器
- SRV 记录尚未传播
- 联邦未启用(
enabled: false)
解决方案:
- 验证控制器主机的 DNS SRV 解析:
dig SRV _smsc._tcp.smsc.example.com - 检查联邦配置中是否设置了
enabled: true - 检查应用日志中是否有“联邦 DNS 发现”消息
- 如果使用静态对等体,验证
static_peers列表是否包含正确的主机/端口条目
对等体标记为不健康
症状:发送到特定站点的消息被排队而不是转发。日志显示“健康检查失败”消息。
可能原因:
- 远程控制器已关闭
- 防火墙阻止站点之间的 8443 端口
- TLS 证书问题
- 站点之间的网络分区
解决方案:
- 验证远程控制器是否正在运行:
curl -k https://<peer-host>:8443/api/federation/identity - 检查防火墙规则是否允许来自本地控制器 IP 的 8443 端口流量
- 检查双方的应用日志以获取 TLS 或连接错误
- 检查站点��间的网络连接
消息卡在转发队列中
症状:forward_queued 消息计数在增加;消息未能送达远程站点。
可能原因:
- 目标控制器不健康(转发工作者仅重试健康对等体)
- 转发工作者未运行
- 目标控制器的 API 在
/api/federation/forward上返回错误
解决方案:
- 检查对等体状态——转发工作者仅重试健康对等体
- 验证转发工作者是否在监督树中(检查应用日志中的“ForwardWorker”)
- 手动测试转发端点:
curl -k -X POST https://<peer>:8443/api/federation/forward -H 'Content-Type: application/json' -d '{"source_msisdn":"test","destination_msisdn":"test","message_body":"test","source_smsc":"test","dest_smsc":"test"}' - 检查
forward_max_retries——超过此计数的消息将标记为:forward_failed,并且不会重试
消息标记为 forward_failed
症状:队列中有状态为 :forward_failed 的消息。
可能原因:
- 目标控制器在
forward_max_retries * forward_retry_interval_ms期间不可达 - 在默认设置下(50 次重试,5 秒间隔),这意味着对等体在大约 4 分钟内不可达
解决方案:
- 调查为什么对等体在重试窗口内不可达
- 如果对等体已恢复,这些消息必须���动重新提交——它们不会自动重试
- 考虑增加
forward_max_retries,以适应预期的较长停机窗口 - 对于非常不可靠的链接,考虑使用
forward_max_retries: 1000(在 5 秒间隔下大约 83 分钟的排队)