> ## Documentation Index
> Fetch the complete documentation index at: https://docs.molesignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 分角色部署

> 把 MoleSignal 的角色拆到不同进程中运行：各角色的职责、节点如何互相发现，以及拆分角色集群目前的限制。

MoleSignal 是单个二进制文件。`[node].roles` 配置决定进程承担哪些职责。默认值 `standalone` 在一个进程内运行全部角色。拆分角色后，接入、查询和后台任务可以独立扩缩容和独立故障。

`router` 角色是分角色集群的入口：它提供完整的 HTTP API 与网页界面，把写入转发给 intake 节点，把查询交给 querier 节点。其余角色各自提供一个内部 HTTP 监听，用于健康检查与指标。

## 角色

| 角色 | 运行内容 | 状态 | 扩缩容 |
| - | - | - | - |
| `standalone` | 同进程内的全部角色、完整 HTTP API、网页界面、内部 gRPC、OTLP gRPC 以及 Probe Agent gRPC 端点 | 有状态：内嵌带 WAL 的 intake | 单进程，不是水平扩展单元。 |
| `router` | 完整的 HTTP API 与网页界面、OTLP gRPC 接收端、把写入转发给 intake 节点、把查询转发给 querier 节点，以及按组织限流 | 无状态 | 在负载均衡器之后水平扩展。 |
| `intake` | WAL 重放、内存缓冲、周期性刷写为 Parquet、实时告警规则、内部 gRPC 接入端点，以及 OTLP gRPC 接收端 | 有状态：最近一个刷写窗口内的 WAL | 按数据流做 rendezvous 哈希分片。扩缩容前先规划 WAL 卷与再平衡。 |
| `querier` | 执行其它节点发来的 SQL 与 PromQL 查询、面向联邦查询的 Arrow Flight 扫描端点，以及异步搜索任务 | 无状态 | 水平扩展。 |
| `compactor` | 文件合并、保留策略清理，以及存储维护：索引重建、延迟对象删除与目录对账 | 两次运行之间无状态 | 水平扩展。多个节点按数据流与组织分工，参见 [Compactor 节点之间的分工](#compactor-节点之间的分工)。 |
| `alert` | 规则评估、升级派发、定时报告、定时日志巡检、连接器运行、流水线运行、只在一个节点上运行的后台任务，以及 Probe Agent gRPC 端点 | 两次运行之间无状态：评估状态与事件保存在元数据库 | 水平扩展。多个节点按规则、报告、流水线与连接器分工，由一个主节点运行只能运行一份的任务，参见 [Alert 节点之间的分工](#alert-节点之间的分工)。 |

一个进程可以承担多个角色，例如 `["intake", "querier"]`。

### 监听端口

| 角色 | `[http].port`（5080） | 内部 gRPC（5082） | OTLP gRPC（4317） | 其他 |
| - | - | - | - | - |
| `standalone` | 完整 HTTP API 与网页界面 | 是 | 是 | Probe Agent 端口 `5084` 与 `5085`。启用时有 `5083` 上的 Flight SQL。 |
| `router` | 完整 HTTP API 与网页界面 | 否 | 是 | 启用时有 `5083` 上的 Flight SQL。 |
| `intake` | 健康、就绪与指标 | 是 | 是 | |
| `querier` | 健康、就绪与指标 | 是 | 否 | 启用时有 `5083` 上的 Flight SQL。 |
| `compactor` | 健康、就绪与指标 | 否 | 否 | |
| `alert` | 健康、就绪与指标 | 否 | 否 | Probe Agent 端口 `5084` 与 `5085`。 |

不提供完整 API 的节点，其健康、就绪与指标监听只响应三个不需要认证的路径，不响应其他任何路径：

| 路径 | 响应 |
| - | - |
| `GET /api/v1/healthz` | `200`；子系统降级时返回 `503` 并附带 `reason`。 |
| `GET /api/v1/readyz` | WAL 重放完成前返回 `503`，完成后返回 `200`。 |
| `GET /metrics` | Prometheus 指标。 |

请把这个监听放在内部网络中。

## 选择角色

在配置文件中或通过环境变量设置角色。环境变量使用 `MS_` 前缀，并用单个下划线连接配置路径的每一层：`[node].roles` 对应 `MS_NODE_ROLES`，`[cluster].advertise_addr` 对应 `MS_CLUSTER_ADVERTISE_ADDR`。带点号的名称不会被识别。

**TOML**

```toml theme={null}
[node]
id = "intake-a1"
roles = ["intake"]

[cluster]
advertise_addr = "10.0.1.21:5082"
heartbeat_interval_secs = 5
peer_timeout_secs = 15
```

**环境变量**

```bash theme={null}
export MS_NODE_ROLES='["intake"]'
export MS_NODE_ID=intake-a1
export MS_CLUSTER_ADVERTISE_ADDR=10.0.1.21:5082
export MS_SELF_TELEMETRY_CLUSTER_TOKEN="<共享密钥>"

molesignal --config ./conf/config.toml
```

**每个角色一个进程**

```bash theme={null}
MS_NODE_ROLES='["router"]'
MS_NODE_ROLES='["intake"]'
MS_NODE_ROLES='["querier"]'
MS_NODE_ROLES='["compactor"]'
MS_NODE_ROLES='["alert"]'
```

集群中的所有节点必须使用同一个 PostgreSQL 数据库和同一个对象存储，并共用以下设置：`MS_CIPHER_KEY`、`MS_AUTH_JWT_SECRET_OVERRIDE`（需要固定签名密钥时）与 `MS_SELF_TELEMETRY_CLUSTER_TOKEN`。为每个节点设置唯一的 `[node].id`，以及对端能通过内部 gRPC 端口访问到的 `advertise_addr`。

两个进程以同一个 `[node].id` 发布同一个数据集时会互相栅栏：其中一个发布过之后，另一个的刷写会被拒绝。被栅栏的进程会丢弃仍在内存中的行，WAL 保留在磁盘上，并且对该数据集的写入返回 `503`，直到它换用唯一的 id 重启。

## 数据流向

### 接入路径

发往 router 接入端点（`/api/v1/intake/*` 或 OTLP gRPC）的请求，先在 router 上完成认证与解码。随后 router 经内部 gRPC 端口把批次转发给一个 intake 节点：按组织与数据流，在存活 intake 节点的 ID 上做 rendezvous 哈希选出。各个 router 把同一条数据流的记录发给同一个节点；该节点不可达时，各个 router 也按同样的顺序改投下一个，最多尝试三个节点。节点加入或离开时，只迁移它自己名下的数据流。一条数据流的写入全部进同一个节点，数据量大的来源可以拆成几条数据流来分摊。没有存活的 intake 节点时，router 返回 `503`。

intake 节点执行流水线、脱敏与 schema 演化，先把每个批次追加到 WAL，再写入内存缓冲，并向 router 返回与 standalone 节点相同的结果。后台刷写把缓冲编码为 Parquet 文件和检索索引，上传到对象存储，把它们登记到 PostgreSQL 中的文件目录，并截断已刷写的 WAL 分段。大小达到 `[object_store].multipart_threshold_size` 的对象分片上传。

采集器也可以把 OTLP gRPC 直接发给 intake 节点。其他角色自身产生的数据（例如派生数据集）在各自节点上用同样的 WAL 与刷写写入。

![接入路径](https://mintcdn.com/molesignal/W03b-Z-TATDejvIA/images/architecture/intake_zh-Hans_light.svg?fit=max\&auto=format\&n=W03b-Z-TATDejvIA\&q=85\&s=1994d532aded0f9df1b080742bff8a5e)

接入路径

WAL 目录、分段大小、刷写策略、缓冲大小与刷写间隔由[配置](https://docs.molesignal.com/zh-Hans/configuration.md)中的 `[wal]` 与 `[intake]` 控制。

### 查询路径

SQL 或 PromQL 请求到达 router 后，router 检查授权、准入与结果缓存，再经内部 gRPC 端口把请求交给轮流选出的 querier 节点。querier 用自己的引擎完整执行查询并返回结果。选中的 querier 不可达时，router 会尝试其余 querier；没有存活的 querier 时返回 `503` 与 `no querier node is available`。查询指定了远端集群时，router 把它们的结果合并进来。

![查询路径](https://mintcdn.com/molesignal/NJoXrfidjTl0w4wV/images/architecture/query_zh-Hans_light.svg?fit=max\&auto=format\&n=NJoXrfidjTl0w4wV\&q=85\&s=e21d429a580ac5a2e2de4646e833c0b3)

查询路径

查询读取文件目录列出的文件。在分角色集群中，intake 节点仍缓冲着的记录要等下一次刷写之后才对查询可见，时间在 `[intake].flush_interval_secs`（默认 30 秒）之内，缓冲达到大小上限时更早。

### 异步搜索任务

估算规模超过 `[querier].auto_async_threshold_rows` 的查询会成为持久化的搜索任务。router 把任务存入 PostgreSQL，querier 节点上的 worker 领取任务，同一个任务不会被重复执行。参见[配置](https://docs.molesignal.com/zh-Hans/configuration.md)中的 `[search_jobs]`。

![异步搜索任务流水线](https://mintcdn.com/molesignal/LKKF1DKLWF5Aik4g/images/architecture/async_zh-Hans_light.svg?fit=max\&auto=format\&n=LKKF1DKLWF5Aik4g\&q=85\&s=cf5b72a0258e05ca5bc25570e4074930)

异步搜索任务流水线

### 联邦查询

在查询中添加 `?clusters=local,<cluster-name>` 即可包含远端集群。接收请求的节点先扫描自己的部分，再携带 Bearer Token 向每个已启用的远端集群请求扫描，合并各批数据后执行完整 SQL。无法访问的远端集群会在联邦元数据中标记为降级，而不会悄悄变成本地数据。远端集群在 **设置** 中管理。

![联邦查询](https://mintcdn.com/molesignal/VFApFmnpGfjQnMkz/images/architecture/federated_zh-Hans_light.svg?fit=max\&auto=format\&n=VFApFmnpGfjQnMkz\&q=85\&s=3114ac6eef79497dc3116e4c8bb89cad)

联邦查询

## 集群成员

没有 gossip 或共识组件。每个节点每隔 `[cluster].heartbeat_interval_secs`（默认 5 秒）向 PostgreSQL 写入一行心跳，包含节点 id、角色和 `advertise_addr`。最后一次心跳早于 `[cluster].peer_timeout_secs`（默认 15 秒）的对端会被忽略，静默超过五分钟的行会被清理。节点通过读取这张表并按角色过滤来发现对端，结果最多缓存 2 秒。`standalone` 节点在本进程内处理写入与查询，不做转发。

收到 `SIGTERM` 或 `SIGINT` 的节点先删除自己的行，再处理完手上的工作，因此对端在 2 秒内就不再使用它，不必等待超时。通过 `POST /api/v1/node/drain` 排空的节点在下一次心跳时删除自己的行。

内部 gRPC 端口 `5082` 在节点之间传输写入转发、查询转发与扫描流量，不做认证，请把它放在可信网络中。

### Compactor 节点之间的分工

可以同时运行多个 `compactor` 节点。每个数据流和每个组织在存活的 compactor 节点中只有一个负责节点，由它执行该数据流或组织的工作。`standalone` 节点也算作 compactor 节点之一。可以重复执行的删除在每个节点上运行。

| 工作 | 划分单位 | 执行节点 |
| - | - | - |
| 文件合并、指标降采样与保留策略清理 | 数据流 | 负责该数据流的节点 |
| 分区清单封存 | 数据流 | 负责该数据流的节点 |
| 索引重建与目录对账 | 组织 | 负责该组织的节点 |
| 延迟对象删除 | 删除队列条目 | 每个 compactor 节点。节点先以 `[storage.gc].lease_secs` 的租约领取条目，再删除对象。 |
| 会话回放 Segment 与调查栈的过期清理 | 不划分 | 每个 compactor 节点。重复删除已过期的条目没有影响。 |

负责节点由对存活 compactor 节点的 id 做 rendezvous 哈希得出。看到相同成员的节点会算出同一个结果，不需要协调者或锁表。节点加入或离开时，只有该节点名下的数据流与组织会迁移。

* **节点故障。** 停止心跳的节点在 `[cluster].peer_timeout_secs` 之后退出成员列表。节点发现结果最多缓存 2 秒，因此默认配置下，其余节点在最后一次心跳之后约 17 秒接管它的数据流与组织，并在各项任务的下一轮中处理。
* **节点退出。** 收到 `SIGTERM` 或 `SIGINT` 的节点先退出成员列表，再处理完手上的工作。其余节点约 2 秒后接管它的数据流与组织，并在各项任务的下一轮中处理。
* **新节点。** 节点在成员列表中出现满 `[cluster].peer_timeout_secs` 之后才开始承担工作。其余节点一看到它就让出它名下的部分，因此这部分工作会等待一个超时窗口，而不会被处理两次。
* **重叠。** 各节点读取成员列表的时刻略有先后，成员变化期间两个节点可能短暂处理同一个数据流。每个结果都通过一次原子操作发布到文件目录，落败的节点丢弃自己的产物。重叠只多耗一些 I/O，不会造成数据重复或丢失。

为每个节点设置唯一的 `[node].id`。id 相同的节点会被算作同一个成员，各自处理该成员名下的全部工作。

每个 compactor 节点各自应用 `[compactor].max_concurrent_groups`，因此总的合并并发度随节点数增长。

`/metrics` 中的 `work_ownership_members`、`work_ownership_active` 与 `work_ownership_owned_units` 分别表示节点看到的成员数、节点当前是否承担工作，以及它在最近一轮中负责的数据流或组织数量。`work_ownership_owned_units` 的 `work` 标签表示任务类型：`compaction` 与 `partition_manifest` 统计数据流，`index_rebuild` 与 `storage_reconciler` 统计组织。成员稳定时，所有 compactor 节点的数值之和等于数据流或组织的总数。

### Alert 节点之间的分工

可以同时运行多个 `alert` 节点。节点按条目分工，其中一个节点是主节点，负责只能运行一份的任务。`standalone` 节点也算作 alert 节点之一。

| 工作 | 划分单位 | 执行节点 |
| - | - | - |
| 规则评估 | 规则 | 负责该规则的节点 |
| 未处理事件的升级派发 | 事件所属的规则 | 负责该规则的节点，也就是评估这条规则的节点 |
| 定时报告 | 报告 | 负责该报告的节点 |
| 定时流水线 | 流水线 | 负责该流水线的节点 |
| Pull 连接器 | 连接器 | 负责该连接器的节点 |
| 通知投递、值班事件与状态页通知 | 不划分 | 每个 alert 节点。节点发送前先在数据库中认领事件或投递记录，事件 id 稳定，因此重复执行没有影响。 |
| 定时日志巡检 | 不划分 | 每个 alert 节点。节点开始执行前先在数据库中认领该次执行，因此每次执行只由一个节点完成。 |
| 跨集群事件推送与 gossip、服务图重算、APM 汇总、状态页维护、拨测自动化、慢查询分析、事件根因分析 | 不划分 | 仅主节点 |

条目的负责节点由对存活 alert 节点的 id 做 rendezvous 哈希得出，方式与 compactor 节点相同。主节点是一个固定 key 的负责节点。成员变化时沿用上文 compactor 的规则：节点故障或退出后，其余节点按相同的时间接管它的条目；新节点在成员列表中出现满 `[cluster].peer_timeout_secs` 之后才开始承担工作。主节点故障或退出后由另一个节点接任主节点，原来由主节点运行的任务在新主节点的下一轮中继续。新加入的节点也可能接任主节点，在它完成预热之前这些任务会暂停。

* **规则。** 节点在每一轮（`[alert].eval_interval_secs`）评估自己负责的规则。成员变化期间，两个节点可能短暂地同时负责同一条规则。距上一次评估不足 `[alert].eval_interval_secs` 一半的规则会被跳过，因此同一个周期只计一次数，`for_periods` 的去抖不会被提前耗尽。
* **报告、流水线与连接器。** 节点先以租约认领条目再运行，运行结束后释放租约。运行期间崩溃的节点会让租约保留最多 10 分钟，之后由另一个节点重新运行该条目。运行超过 10 分钟的条目可能被另一个节点重复运行。连接器从保存的游标继续拉取，不会丢失数据，但被中断的那个窗口里的事件可能被写入两次。

请保持各节点的时钟同步，因为节点之间会比较各自写入的时间戳。为每个节点设置唯一的 `[node].id`。

`/metrics` 中，`work_ownership_owned_units` 的 `work` 标签取 `rule_evaluation` 与 `escalation_dispatch` 时，分别表示节点在最近一轮中负责的规则数与事件数；取 `primary` 时，主节点为 `1`，其余节点为 `0`，该值每隔 `[cluster].heartbeat_interval_secs` 更新一次。`work_overlap_skipped_total` 统计节点因为另一个节点已在同一周期运行过而跳过的条目数，成员稳定期间该计数保持不变。

## 速率限制

`router` 角色用 `[router.rate_limit]` 按组织和路由类别限流：接入类别覆盖 `/api/v1/intake/*`，查询类别覆盖 `/api/v1/query` 及其下的路径。限流按已认证组织的请求计数，超限时返回 `429` 并附带 `Retry-After` 响应头。每个 router 副本各自计数，因此实际上限随副本数增长。`standalone` 节点不应用这个限制。

## 扩缩容与高可用

* **Router。** 在负载均衡器之后增加副本。
* **Querier。** 增加副本。每个查询在一个 querier 上运行，因此 querier 越多，能同时运行的查询越多。
* **Intake。** 每个节点使用自己的持久化 WAL 卷（Kubernetes 中使用 StatefulSet）。节点数变化时，加入或离开的节点名下的数据流会迁移，因此缩容前要确认 WAL 已刷写，并在把流量路由给重启的节点之前留出 WAL 重放时间。
* **Compactor。** 增加副本。节点之间按数据流与组织分工，某个节点故障后由其余节点接管，可以滚动更新，参见 [Compactor 节点之间的分工](#compactor-节点之间的分工)。
* **Alert。** 增加副本。节点之间按规则、报告、流水线与连接器分工，由一个主节点运行只能运行一份的任务，某个节点故障后由其余节点接管，可以滚动更新，参见 [Alert 节点之间的分工](#alert-节点之间的分工)。
* **多节点共识。** intake 节点之间不做 WAL 复制。丢失某个 intake 节点的卷，会丢失尚未刷写的记录。修改 `[node].roles` 需要重启。

安装与端口参见[部署 MoleSignal](https://docs.molesignal.com/zh-Hans/deployment)，上线前参见[生产检查清单](https://docs.molesignal.com/zh-Hans/deployment/production-checklist.md)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.