概述:设计哲学
单一二进制
所有角色编译进同一个二进制、打进同一个镜像。进程之间只靠配置(
[node].roles)区分职责,不需要不同的构建产物。角色由配置选择
[node].roles 决定本进程对外暴露什么、承载哪些前台职责。默认 ["standalone"],即单进程跑全部职责。状态尽量外置
元数据落 Postgres,数据落对象存储。除 Ingester 的 WAL/缓冲外,大多数角色是无状态的,可水平扩。
发现走 Postgres
没有独立的 gossip / 共识组件。节点通过心跳把自己写进
cluster_nodes 表,其他节点直接查表发现存活对等节点。- 单机 standalone
- 分角色集群
- 评估、开发、PoC、小流量生产。
- 一个进程暴露 HTTP + gRPC,并在进程内运行全部内部职责(写入、查询、压实、告警评估等)。
- 依赖仍然是外置的 Postgres + 对象存储(或本地文件系统后端)。
角色枚举值在 TOML / 环境变量里使用 snake_case(
standalone、alert_manager),与内部实现的命名约定一致。下文所有配置示例均使用 snake_case。节点角色总表
[node].roles 是一个数组(Vec<Role>),合法取值:standalone、router、ingester、querier、compactor、alert_manager。默认 ["standalone"]。
| 角色 | 前台暴露 | 有/无状态 | 扩缩特性 |
|---|---|---|---|
standalone | HTTP + gRPC,并在进程内运行全部内部职责 | 含状态(内含 Ingester) | 评估用单实例;不适合作为横向扩展单元 |
router | HTTP 反向代理 + 限流;不直接暴露业务 HTTP listener 之外的内部服务 | 无状态(限流器为进程内、临时) | 可水平扩,前置一个 L4/L7 LB 即可 |
ingester | 承载 WAL 重放 + 缓冲 + 周期 flush(实际工作在进程启动时预拉起) | 有状态(WAL、内存缓冲、file_meta 缓存) | 一致性哈希分片;扩缩需考虑 WAL 持久卷与再平衡 |
querier | 设计上为分布式扫描端 | 无状态 | 可水平扩 |
compactor | 后台 tick 循环(预拉起),角色本体空转 | 无状态(逐 tick 处理) | 建议单实例(见高可用一节) |
alert_manager | 后台评估 + 派发两条循环(预拉起),角色本体空转 | 有状态(评估状态、事件状态在进程内) | 建议单实例 |
后台 worker 由哪个角色承载
一个关键事实:几乎所有后台 worker 都在进程启动的统一装配阶段被无条件拉起,而不是在各角色的分发函数里。 它们通过 feature flag 或配置自我门控(gating)。这意味着:在standalone 进程里,它们全部运行;在分角色镜像里,是否真正”干活”取决于该角色是否被注入了对应依赖与配置。下表给出建议的承载角色与周期。
| 后台 worker | 建议承载角色 | 周期 | 配置键 | 现状 |
|---|---|---|---|---|
| 心跳(heartbeat) | 所有角色 | heartbeat_interval_secs,默认 5s(首次立即) | [cluster].heartbeat_interval_secs | 已接线 |
| stale 节点清理(sweeper) | 所有角色 | 固定 60s | 无(硬编码) | 已接线 |
| 对象存储健康探针 | 所有角色(启动时阻塞探测一次) | health_probe_interval_secs,默认 30s | [store.object].health_probe_interval_secs | 已接线 |
| 告警评估(evaluator) | alert_manager | eval_interval_secs,默认 30s | [alert_manager].eval_interval_secs | 已接线 |
| 告警派发(dispatcher) | alert_manager | dispatch_interval_secs,默认 10s | [alert_manager].dispatch_interval_secs | 已接线 |
| 压实(compaction) | compactor | interval_secs,默认 300s(5min) | [compactor].interval_secs | 已接线 |
| file_meta_dumper(冷分区落盘) | compactor(与 compaction 同进程) | interval_secs,默认 3600s(1h) | [storage.file_meta_dump].interval_secs,enabled=false 可关闭 | 已接线 |
| scheduled_reports(定时报表) | alert_manager(需渲染依赖见下) | 固定 60s tick;report 自身 cron 决定是否到期 | report 的 cron(tick 间隔不可配) | 已接线 |
| search_jobs(异步搜索池) | 承载 AppState 的角色(通常 standalone / 暴露查询的节点) | 空闲轮询 idle_poll_secs,默认 2s;清理 cleanup_interval_secs,默认 3600s | [search_jobs].workers(默认 2)等 | 已接线 |
| ACME 签发 / 续期 | router(TLS 入口) | 签发 issue_poll_secs(默认 60s);续期 renewal_retry_secs(默认 21600s/6h) | [http.tls].issue_poll_secs / renewal_retry_secs | 当前为占位 / 未启用 |
compactor 与 alert_manager 的循环属于”预拉起”模式: 它们在统一装配阶段就以独立后台任务运行,而
compactor::run() / alert_manager::run() 这两个角色本体函数只是空转占位。因此把进程的 [node].roles 设为 compactor 或 alert_manager,效果是”该进程的角色本体空转 + 后台循环照常跑”。如何选择角色
角色通过[node].roles 配置;可用环境变量覆盖。环境变量统一前缀 MS_,section 与 field 之间用 .(点)分隔(去掉 MS_ 前缀后,其余按 . 切分)。例如 MS_NODE.ROLES、MS_STORE.META.DSN、MS_HTTP.PORT。
- TOML
- 环境变量覆盖
- 一组分角色进程
少数 bootstrap / 密钥类变量是扁平单下划线且不进
Settings,直接从环境读取:MS_CIPHER_KEY、MS_AUTH_JWT_SECRET_OVERRIDE、MS_LICENSE_FILE、MS_COPILOT_<PROVIDER>_API_KEY 等。其余结构化字段一律走 MS_<SECTION>.<FIELD> 点分形式,docker-compose 与 k8s 清单中即如此(如 MS_NODE.ROLES、MS_STORE.META.DSN、MS_CLUSTER.ADVERTISE_ADDR)。数据流
写入路径
入口经 Router(限流 + 一致性哈希)落到某个 Ingester;Ingester 先写 WAL(落盘持久),再写内存缓冲;后台 flush 循环按时间窗或大小阈值把缓冲编码成列式文件 + 检索索引上传对象存储,最后把 FileMeta 落 Postgres,并截断已 flush 的 WAL 段。[wal].dir、[wal].segment_size_mb、[wal].flush_strategy(batch/none/every_write)、[wal].sync_level(data/all);[ingester].buffer_max_mb(默认 256)、flush_interval_secs(默认 30)、flush_parallelism(默认 4);[router.rate_limit].ingest_qps(默认每 org 1000,0=不限)。
Router 限流粒度为
(org_id, route_class),超限返回 429 并带 Retry-After。org_id 来自请求头 X-Org-Id,缺省为 default。查询路径
查询入口在暴露 HTTP 的节点上。引擎按集群规模逐层包装:本地查询引擎 →(≥2 个 querier 对等节点时)分布式引擎 →(指定了远程集群时)联邦引擎。分布式查询的分片对
object_key 取哈希散到对等节点;分片 SQL 仅做扫描(SELECT * FROM <stream>),完整聚合在协调端执行以避免部分/最终聚合不一致。仅当集群中有 ≥2 个 querier 对等节点时才走分布式;否则回退到本地引擎,无网络跳。异步搜索作业管线
[search_jobs].workers(默认 2)、idle_poll_secs(默认 2)、cleanup_interval_secs(默认 3600);自动异步阈值 [querier].auto_async_threshold_rows(默认 5000 万行)。
FOR UPDATE SKIP LOCKED 的领取语义对多 worker / 多节点安全:多个承载 search_jobs 的进程可以共享同一张 search_jobs 表并发领取,不会重复执行同一作业。联邦 / 多集群查询
通过?clusters=local,sf,nyc 指定目标集群。协调端本地扫描后,对每个启用的远程集群发起一次内部扫描 RPC(带 Bearer token),把各集群回传的批次与本地 UNION ALL 后执行完整 SQL。
集群成员与发现
节点注册
心跳任务周期性 upsert
(node_id, role, advertise_addr, last_heartbeat_at_micros) 到 Postgres cluster_nodes 表(主键 node_id,ON CONFLICT DO UPDATE)。首次心跳立即发出,之后按间隔。心跳间隔
[cluster].heartbeat_interval_secs,默认 5s。advertise_addr 默认 127.0.0.1:5082,即对等节点用来互联的 gRPC 地址(host:port)。存活窗口与 stale 清理
存活窗口由
[cluster].peer_timeout_secs 控制,默认 15s:注册表只返回 last_heartbeat_at >= now - peer_timeout 的节点。另有 sweeper 每 60s 删除超过 5 分钟未心跳的 cluster_nodes 行。发现对等节点
没有 gossip / 共识;分布式模式下各角色直接查
cluster_nodes 表按角色筛选存活对等节点。standalone 模式跳过整套发现,只返回自身、永不触达 cluster_nodes。选址算法
Router 选 Ingester:对
org_id|stream_name 做一致性哈希后取模,确定性落到某个 Ingester。Router 选 Querier:朴素轮询(now_ns % peer_count),不是完整一致性哈希。分布式查询分片:对 object_key 取哈希取模散到 querier 对等节点。集群内部的本地扫描 RPC 调用不带鉴权;只有联邦 / 远程集群调用使用可选的 Bearer token。
cluster_nodes 表结构:node_id VARCHAR(64) PK、role VARCHAR(32)、advertise_addr VARCHAR(255)、started_at_micros BIGINT、last_heartbeat_at_micros BIGINT,在 role 与 last_heartbeat_at_micros 上建索引。
外部依赖与端口
PostgreSQL
元数据库:FileMeta、streams、rules、incidents、users、orgs、audit、quotas、证书、
cluster_nodes、search_jobs、remote_clusters 等。[store.meta]:backend(默认 sqlite,生产请置 postgres)、dsn、max_connections(默认 16)。迁移在编译期内嵌。对象存储
列式文件 + 检索索引侧车。
[store.object].backend:local(默认,root=./data/objects)/ s3(含 MinIO、R2、阿里云 OSS,用 endpoint 覆盖)/ azure / gcs。凭据优先级:环境变量 > 凭据文件 > 内联 TOML。无外部缓存 / 共识
仅进程内 LRU+TTL 缓存、限流器、异步运行时。无 Redis / Memcached,无外部共识组件。
渲染依赖(可选)
定时报表的 PNG/PDF 渲染需 headless Chromium,为 feature 门控;OSS 构建一律回退 SVG 占位。镜像运行层已带 chromium。
监听端口
| 端口 | 协议 / 服务 | 配置键 | 说明 |
|---|---|---|---|
| 5080 | HTTP | [http].bind(默认 0.0.0.0)、[http].port | /api/v1/*、/metrics、/healthz、/readyz、/.well-known/acme-challenge/ 等 |
| 5082 | gRPC | [grpc].bind、[grpc].port、max_message_size_mb(默认 32MB) | 数据接入 + 扫描 RPC + 集群心跳,三服务共用 |
| 80 | TLS 模式下的明文 HTTP | [http.tls].plain_port | 健康检查 + ACME HTTP-01 挑战 + 301 跳转 HTTPS |
| 443 | TLS 模式下的 HTTPS(SNI) | [http.tls].port | 完整路由;需 domain-acme-tls feature |
/metrics(GET,Prometheus 文本 0.0.4)由 [telemetry].metrics_enabled(默认 true)控制,暴露缓存命中 / 失效、object_store_*、wal_*、file_meta_dump_*、tantivy_pruned_files_total、alert_rule_eval_timeout_total 等指标。健康 / 就绪探针语义
| 探针 | 路径 | 200 条件 | 503 条件 | 用途 |
|---|---|---|---|---|
| Liveness | GET /healthz | WAL 重放完成(仅 Ingester 相关;其他角色绕过)且对象存储未降级 | 重放进行中 或 对象存储连接丢失 | 进程存活与依赖健康 |
| Readiness | GET /readyz | replay_done = true(即使对象存储已降级) | 重放仍在进行中 | 允许 K8s 在写路径降级时仍放读流量进来 |
后台周期探针
每
[store.object].health_probe_interval_secs(默认 30s)做一次同样的往返;连续 3 次失败才置 object_store_degraded=true,成功则计数归零。认证与配置覆盖
- JWT 密钥在首次启动时由数据库自动 bootstrap;旧的
jwt_secretTOML 字段已废弃(仅为兼容旧配置而保留解析)。需要固定密钥时用环境变量MS_AUTH_JWT_SECRET_OVERRIDE。 - API token 形如
ms_<prefix>_<secret>,secret 经 argon2id 哈希存储。 - 启动时
store.meta.dsn、wal.dir、http.port、grpc.port、node.id等被视为不可变字段,运行中变更会告警。
部署拓扑
所有拓扑共用同一镜像(如molesignal:dev),靠 MS_NODE.ROLES 区分。Web 前端是独立的 nginx 镜像(如 molesignal-web:dev)。
- 单机沙箱
- Docker Compose
- Kubernetes
单进程,全部角色合一,最快上手。
本地后端(
store.object.backend=local)适合开发;生产请用对象存储后端。扩缩与高可用
Router — 可水平扩
无状态,副本数随入口流量扩。限流器是进程内、临时的:多副本下每副本各自计数,实际 org QPS 上限约为
配置值 × 副本数,需要时把限流前置到统一网关或下调单副本阈值。Querier — 可水平扩
无状态。≥2 个对等节点才触发分布式扫描。注意:独立 querier 的扫描 RPC 服务端当前为占位,分布式查询的完整落地形态以 standalone 内引擎为准。瓶颈在列式文件读取 + 查询引擎内存。
Ingester — 有状态
用 StatefulSet + 每副本 PVC 持久 WAL。Router 按
org|stream 一致性哈希落点,扩缩会改变取模结果导致再平衡;缩容前应确保 WAL 已 flush。就绪探针留足重放窗口(清单设 10s 初始延迟)。Compactor / AlertManager — 单实例
两者均建议单副本:Compactor 多实例会产生合并冲突(lease 表加锁为未来计划),AlertManager 评估 / 事件状态为进程内。可靠性靠快速重启而非多副本。
wal_append_lock_wait_seconds、wal_append_inflight、wal_fsync_errors_total、file_meta_dump_*;对象存储看 object_store_operations_total、object_store_errors_total、object_store_op_duration_seconds、object_store_probe_*;查询看缓存命中率 cache_*、tantivy_pruned_files_total;告警看 alert_rule_eval_timeout_total。配合 /healthz、/readyz 与 cluster_nodes 表观察成员存活。
最小生产清单 / 校验清单
外部依赖就绪
Postgres 可达、
[store.meta].backend = "postgres" 且 dsn 正确;对象存储后端选 s3/azure/gcs(非 local),凭据按”环境变量 > 凭据文件 > 内联”优先级注入。角色与互联地址
每个进程
MS_NODE.ROLES 明确;MS_CLUSTER.ADVERTISE_ADDR 设为对等节点可达的 host:5082(K8s 用 $(POD_IP):5082)。确认仅设单一前台角色(多角色数组只取首元素)。Ingester 持久化
Ingester 用 StatefulSet + PVC 挂 WAL 目录;
[wal].flush_strategy / sync_level 按持久性要求设置;就绪探针留足重放延迟。单实例角色
Compactor、AlertManager 各保持 1 副本。确认
[compactor].interval_secs、retention_days、[storage.file_meta_dump].enabled 符合预期。入口与限流
Router 前置 LB;按 org 设
[router.rate_limit].ingest_qps / query_qps;注意多副本下限流为近似值。Ingress 对 /api/v1/query/stream 关闭缓冲。可观测与探针
/metrics 接入 Prometheus;K8s 探针指向 /api/v1/healthz(liveness)与 /readyz(readiness)。确认对象存储启动探测能通过(否则进程不启动)。