跳转到主要内容
MoleSignal 是一个单一二进制(single binary):同一份可执行文件(同一个镜像)服务所有角色,你来选择某个进程运行哪些角色。这让它既能当作一条命令启动的沙箱,也能横向扩展成多角色集群。 本篇面向运维 / SRE,系统说明角色划分、后台 worker 归属、数据流、集群发现、外部依赖与端口,以及单机 / Docker Compose / Kubernetes 三种部署拓扑。

概述:设计哲学

单一二进制

所有角色编译进同一个二进制、打进同一个镜像。进程之间只靠配置([node].roles)区分职责,不需要不同的构建产物。

角色由配置选择

[node].roles 决定本进程对外暴露什么、承载哪些前台职责。默认 ["standalone"],即单进程跑全部职责。

状态尽量外置

元数据落 Postgres,数据落对象存储。除 Ingester 的 WAL/缓冲外,大多数角色是无状态的,可水平扩。

发现走 Postgres

没有独立的 gossip / 共识组件。节点通过心跳把自己写进 cluster_nodes 表,其他节点直接查表发现存活对等节点。
何时单机、何时分角色:
  • 评估、开发、PoC、小流量生产。
  • 一个进程暴露 HTTP + gRPC,并在进程内运行全部内部职责(写入、查询、压实、告警评估等)。
  • 依赖仍然是外置的 Postgres + 对象存储(或本地文件系统后端)。
角色枚举值在 TOML / 环境变量里使用 snake_casestandalonealert_manager),与内部实现的命名约定一致。下文所有配置示例均使用 snake_case。

节点角色总表

[node].roles 是一个数组(Vec<Role>),合法取值:standalonerouteringesterqueriercompactoralert_manager。默认 ["standalone"]
多角色可在一个进程内组合。 形如 [node].roles = ["ingester", "querier"] 会启动各角色所需的去重后前台 server(此例为同时承载 ingest + 扫描的 gRPC server),按角色 gate 对应后台循环,并把节点的全部角色登记进 cluster_nodes——于是对等节点能按它承担的每个角色发现它。standalone 是”全部角色合一进程”的简写。
角色前台暴露有/无状态扩缩特性
standaloneHTTP + gRPC,并在进程内运行全部内部职责含状态(内含 Ingester)评估用单实例;不适合作为横向扩展单元
routerHTTP 反向代理 + 限流;不直接暴露业务 HTTP listener 之外的内部服务无状态(限流器为进程内、临时)可水平扩,前置一个 L4/L7 LB 即可
ingester承载 WAL 重放 + 缓冲 + 周期 flush(flush 循环在配置了该角色时于启动期 spawn)有状态(WAL、内存缓冲、file_meta 缓存)一致性哈希分片;扩缩需考虑 WAL 持久卷与再平衡
queriergRPC 上的分布式扫描端(Arrow Flight do_get):读列式文件、跑 shard SQL、流式返回结果批无状态可水平扩
compactor运行压实 + retention tick 循环(仅当配置了该角色才 spawn)无状态(逐 tick 处理)建议单实例(见高可用一节)
alert_manager运行评估 + 派发 tick 循环(仅当配置了该角色才 spawn)有状态(评估状态、事件状态在进程内)建议单实例
独立 querier(或任何含 querier / ingester 的角色集)会启动 gRPC server,其中承载 Arrow Flight 扫描服务。协调端经 list_role(querier) 发现各 querier 并向其散播分片。单进程 standalone 仍如常工作;分布式散播需 ≥2 个 querier 对等节点。

后台 worker 由哪个角色承载

角色相关的循环(ingester flush、compaction + file_meta_dumper、告警评估/派发)只有在配置了其归属角色时才 spawnstandalone 视为全部角色)。少数常驻 worker(心跳、sweeper、对象存储探针、MMDB 刷新、search_jobs、scheduled_reports)在每个节点都运行。下表给出归属角色与周期。
后台 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_managereval_interval_secs,默认 30s[alert_manager].eval_interval_secs已接线
告警派发(dispatcher)alert_managerdispatch_interval_secs,默认 10s[alert_manager].dispatch_interval_secs已接线
压实(compaction)compactorinterval_secs,默认 300s(5min)[compactor].interval_secs已接线
file_meta_dumper(冷分区落盘)compactor(与 compaction 同进程)interval_secs,默认 3600s(1h)[storage.file_meta_dump].interval_secsenabled=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 签发 / 续期承担 TLS 终结的 HTTP server(router / standalone签发 issue_poll_secs(默认 60s);续期 renewal_retry_secs(默认 21600s/6h)[http.tls].issue_poll_secs / renewal_retry_secs已接线([http.tls].enabled = true 时生效)
ACME 签发 / 续期已实现:签发循环扫 pending 域名、续期循环对进入 30 天窗口的 active 证书续签,二者都走单次签发路径并带每域名冷却。runner 由承担 TLS 终结的 HTTP server 在 [http.tls].enabled = true 时拉起。TLS/ACME 编入所有构建(无 feature 门),纯由 [http.tls].enabled 运行期开关。

如何选择角色

角色通过 [node].roles 配置;可用环境变量覆盖。环境变量统一前缀 MS_section 与 field 之间用 .(点)分隔(去掉 MS_ 前缀后,其余按 . 切分)。例如 MS_NODE.ROLESMS_STORE.META.DSNMS_HTTP.PORT
[node]
# 节点唯一标识,留空则由进程生成
id = "ingester-a1"
# 角色数组;多角色可在一个进程内组合
roles = ["ingester"]

[cluster]
# 对等节点用于互联的 gRPC 地址(host:port)
advertise_addr = "10.0.1.21:5082"
heartbeat_interval_secs = 5
peer_timeout_secs = 15
少数 bootstrap / 密钥类变量是扁平单下划线且不进 Settings,直接从环境读取:MS_CIPHER_KEYMS_AUTH_JWT_SECRET_OVERRIDEMS_LICENSE_FILEMS_COPILOT_<PROVIDER>_API_KEY 等。其余结构化字段一律走 MS_<SECTION>.<FIELD> 点分形式,docker-compose 与 k8s 清单中即如此(如 MS_NODE.ROLESMS_STORE.META.DSNMS_CLUSTER.ADVERTISE_ADDR)。

数据流

写入路径

入口经 Router(限流 + 一致性哈希)落到某个 Ingester;Ingester 先写 WAL(落盘持久),再写内存缓冲;后台 flush 循环按时间窗或大小阈值把缓冲编码成列式文件 + 检索索引上传对象存储,最后把 FileMeta 落 Postgres,并截断已 flush 的 WAL 段。
写入路径
关键配置:[wal].dir[wal].segment_size_mb[wal].flush_strategybatch/none/every_write)、[wal].sync_leveldata/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-Afterorg_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。
联邦 / 多集群查询
联邦查询受 license 门控: 只要 clusters 含非 local 目标且当前 license 不含 federated_search 特性,HTTP 层直接返回 403。OSS / 社区版会回退到单集群。远程集群定义存于 Postgres remote_clusters 表(advertise_addrtoken_secret_reftls_verifyenabled),enabled=false 的集群在 fan-out 时跳过。当前远程鉴权仅支持 Bearer token;tls_verify=false 映射为 http://(而非”https 跳过校验”)。

集群成员与发现

1

节点注册

心跳任务周期性 upsert (node_id, roles, advertise_addr, last_heartbeat_at_micros) 到 Postgres cluster_nodes 表(主键 node_idON CONFLICT DO UPDATE)。节点的完整角色集以逗号拼接存一行,故多角色节点是一行、可按它承担的每个角色被发现。首次心跳立即发出,之后按间隔。
2

心跳间隔

[cluster].heartbeat_interval_secs,默认 5s。advertise_addr 默认 127.0.0.1:5082,即对等节点用来互联的 gRPC 地址(host:port)。
3

存活窗口与 stale 清理

存活窗口由 [cluster].peer_timeout_secs 控制,默认 15s:注册表只返回 last_heartbeat_at >= now - peer_timeout 的节点。另有 sweeper 每 60s 删除超过 5 分钟未心跳的 cluster_nodes 行。
4

发现对等节点

没有 gossip / 共识;分布式模式下各角色直接查 cluster_nodes 表,按角色成员筛选存活对等节点(节点的角色集包含所求角色即命中)。standalone 模式跳过整套发现,只返回自身。
5

选址算法

Router 选 Ingester:对 org_id|stream_name 做一致性哈希后取模,确定性落到某个 Ingester。Router 选 Querier:朴素轮询(now_ns % peer_count),不是完整一致性哈希。分布式查询分片:对 object_key 取哈希取模散到 querier 对等节点。
6

分布式扫描 RPC

协调端把扫描请求(含 org/stream/sql/file_metas/time_range)编码成 ticket,经内部扫描 RPC 发到对等节点;对等节点读列式文件、注册内存表、跑分片 SQL、回传结果流。该 RPC 与 gRPC 节点服务、数据接入服务共用同一端口(默认 5082)。
集群内部的本地扫描 RPC 调用不带鉴权;只有联邦 / 远程集群调用使用可选的 Bearer token。
cluster_nodes 表结构:node_id VARCHAR(64) PKrole VARCHAR(128)(逗号拼接的角色集)、advertise_addr VARCHAR(255)started_at_micros BIGINTlast_heartbeat_at_micros BIGINTlist_role 扫活跃行后在代码里按成员匹配,故角色查找不依赖该列建索引。

外部依赖与端口

PostgreSQL

元数据库:FileMeta、streams、rules、incidents、users、orgs、audit、quotas、证书、cluster_nodessearch_jobsremote_clusters 等。[store.meta]backend(默认 sqlite,生产请置 postgres)、dsnmax_connections(默认 16)。迁移在编译期内嵌。

对象存储

列式文件 + 检索索引侧车。[store.object].backendlocal(默认,root=./data/objects)/ s3(含 MinIO、R2、阿里云 OSS,用 endpoint 覆盖)/ azure / gcs。凭据优先级:环境变量 > 凭据文件 > 内联 TOML。

无外部缓存 / 共识

仅进程内 LRU+TTL 缓存、限流器、异步运行时。无 Redis / Memcached,无外部共识组件。

渲染依赖(可选)

定时报表的 PNG/PDF 渲染需 headless Chromium,为 feature 门控;OSS 构建一律回退 SVG 占位。镜像运行层已带 chromium。

监听端口

端口协议 / 服务配置键说明
5080HTTP[http].bind(默认 0.0.0.0)、[http].port/api/v1/*/metrics/healthz/readyz/.well-known/acme-challenge/
5082gRPC[grpc].bind[grpc].portmax_message_size_mb(默认 32MB)数据接入 + 扫描 RPC + 集群心跳,三服务共用
80TLS 模式下的明文 HTTP[http.tls].plain_port健康检查 + ACME HTTP-01 挑战 + 301 跳转 HTTPS
443TLS 模式下的 HTTPS(SNI)[http.tls].port完整路由;由 [http.tls].enabled 运行期开启(编入所有构建,无 feature 门)
/metrics(GET,Prometheus 文本 0.0.4)由 [telemetry].metrics_enabled(默认 true)控制,暴露缓存命中 / 失效、object_store_*wal_*file_meta_dump_*tantivy_pruned_files_totalalert_rule_eval_timeout_total 等指标。

健康 / 就绪探针语义

探针路径200 条件503 条件用途
LivenessGET /healthzWAL 重放完成(仅 Ingester 相关;其他角色绕过)对象存储未降级重放进行中 对象存储连接丢失进程存活与依赖健康
ReadinessGET /readyzreplay_done = true(即使对象存储已降级)重放仍在进行中允许 K8s 在写路径降级时仍放读流量进来
1

启动时对象存储探测(阻塞)

startup_ping() 同步对 _health/{uuid}.probe 做 PUT→GET→DELETE(128 字节)。失败则进程不启动。
2

后台周期探针

[store.object].health_probe_interval_secs(默认 30s)做一次同样的往返;连续 3 次失败才置 object_store_degraded=true,成功则计数归零。
3

Ingester WAL 重放

Ingester 启动时扫描 [wal].dir 下的段文件,按 (org, stream_type, stream) 重放进内存缓冲,全部载入后强制 flush 一次,再置 replay_done=true。重放未完成前 /readyz 返回 503。

认证与配置覆盖

  • JWT 密钥在首次启动时由数据库自动 bootstrap;旧的 jwt_secret TOML 字段已废弃(仅为兼容旧配置而保留解析)。需要固定密钥时用环境变量 MS_AUTH_JWT_SECRET_OVERRIDE
  • API token 形如 ms_<prefix>_<secret>,secret 经 argon2id 哈希存储。
  • 启动时 store.meta.dsnwal.dirhttp.portgrpc.portnode.id 等被视为不可变字段,运行中变更会告警。

部署拓扑

所有拓扑共用同一镜像(如 molesignal:dev),靠 MS_NODE.ROLES 区分。Web 前端是独立的 nginx 镜像(如 molesignal-web:dev)。
单进程,全部角色合一,最快上手。
[node]
roles = ["standalone"]

[store.meta]
backend = "postgres"
dsn = "postgres://molesignal:molesignal@localhost:5432/molesignal"

[store.object]
backend = "local"
root = "./data/objects"

[wal]
dir = "./data/wal"
molesignal --config ./conf/config.toml
# HTTP :5080  gRPC :5082
本地后端(store.object.backend=local)适合开发;生产请用对象存储后端。

扩缩与高可用

Router — 可水平扩

无状态,副本数随入口流量扩。限流器是进程内、临时的:多副本下每副本各自计数,实际 org QPS 上限约为 配置值 × 副本数,需要时把限流前置到统一网关或下调单副本阈值。

Querier — 可水平扩

无状态。querier 进程经 gRPC 提供扫描 RPC;≥2 个 querier 对等节点才触发分布式扫描,否则协调端回退到进程内引擎、无网络跳。瓶颈在列式文件读取 + 查询引擎内存。

Ingester — 有状态

用 StatefulSet + 每副本 PVC 持久 WAL。Router 按 org|stream 一致性哈希落点,扩缩会改变取模结果导致再平衡;缩容前应确保 WAL 已 flush。就绪探针留足重放窗口(清单设 10s 初始延迟)。

Compactor / AlertManager — 单实例

两者均建议单副本:Compactor 多实例会产生合并冲突(lease 表加锁为未来计划),AlertManager 评估 / 事件状态为进程内。可靠性靠快速重启而非多副本。
监控建议指标:写路径看 wal_append_lock_wait_secondswal_append_inflightwal_fsync_errors_totalfile_meta_dump_*;对象存储看 object_store_operations_totalobject_store_errors_totalobject_store_op_duration_secondsobject_store_probe_*;查询看缓存命中率 cache_*tantivy_pruned_files_total;告警看 alert_rule_eval_timeout_total。配合 /healthz/readyzcluster_nodes 表观察成员存活。

最小生产清单 / 校验清单

1

外部依赖就绪

Postgres 可达、[store.meta].backend = "postgres"dsn 正确;对象存储后端选 s3/azure/gcs(非 local),凭据按”环境变量 > 凭据文件 > 内联”优先级注入。
2

角色与互联地址

每个进程 MS_NODE.ROLES 明确;MS_CLUSTER.ADVERTISE_ADDR 设为对等节点可达的 host:5082(K8s 用 $(POD_IP):5082)。一个进程可同时承担多个角色(如 ["ingester","querier"]),会起去重后的 server 集并以全部角色登记。
3

Ingester 持久化

Ingester 用 StatefulSet + PVC 挂 WAL 目录;[wal].flush_strategy / sync_level 按持久性要求设置;就绪探针留足重放延迟。
4

单实例角色

Compactor、AlertManager 各保持 1 副本。确认 [compactor].interval_secsretention_days[storage.file_meta_dump].enabled 符合预期。
5

入口与限流

Router 前置 LB;按 org 设 [router.rate_limit].ingest_qps / query_qps;注意多副本下限流为近似值。Ingress 对 /api/v1/query/stream 关闭缓冲。
6

可观测与探针

/metrics 接入 Prometheus;K8s 探针指向 /api/v1/healthz(liveness)与 /readyz(readiness)。确认对象存储启动探测能通过(否则进程不启动)。
7

安全与 license

需要固定 JWT 密钥时设 MS_AUTH_JWT_SECRET_OVERRIDE,否则由 DB 自动 bootstrap。注入 cipher key(生产勿用 dev 全零值)。联邦查询需 federated_search license 特性。TLS + 证书自动签发/续期编入所有构建(无 feature 门),由 [http.tls].enabled 运行期开关。
落地前请明确以下”现状”边界:分布式共识 WAL term source 仍为静态占位(多节点共识未实现);联邦查询与 SSO(仅 OIDC,SAML 未实现)等为 license/feature 门控;以及更改 [node].roles 需重启进程(无热重载)。