可靠消息后端与积压观测
# 可靠消息后端与积压观测
# 目的
worker 现在可以显式选择 Event 或 Redis Streams,通知与定时任务共用业务库里的持久恢复记录。Redis 登录缓存和消息连接分别配置;业务正文留在业务数据库,broker 只接收事件引用。本轮验证覆盖本地 MySQL/PostgreSQL 与 Redis 的组合,不表示其他数据库或消息后端已认证。
# 前置条件
使用当前schema的专用业务库、可访问的登录Redis,以及已配置的API/worker。Redis消息模式另需下述持久化能力;工作流还需显式登记精确运行时并部署兼容API/worker池。指标口只绑定回环地址,由部署方在本机抓取;不接收租户查询参数。
# 步骤与规则
# 选择消息后端
新部署默认使用 Event,沿用同进程消费确认和同库 outbox/inbox 恢复。它不提供跨进程广播、全局顺序或独立持久 broker。只启动 API 可以受理并排队,实际执行须启动 worker。
要启用 Redis,先由操作者准备支持 AOF、noeviction 与 WAITAOF 的专用 Redis,并配置访问凭证和网络隔离。worker 只检查能力,不修改服务端持久策略;检查失败直接退出,不降级 Event。当前确认只表示本机 AOF fsync,不表示异地灾备或副本同步完成。
在相同部署的 API 与 worker 配置当前环境 YAML,或用环境覆盖:
export APP_MESSAGING_DRIVER=redis
export APP_MESSAGING_REDIS_ADDR=127.0.0.1:6380
# 每个独立业务数据库/环境选择独立前缀;同库所有worker保持一致。
export APP_MESSAGING_PREFIX=fago_prod_a
# 密码由部署环境/Secret注入APP_MESSAGING_REDIS_PASSWORD,不提交配置或复用示例密码。
bash scripts/fago.sh worker
API 仍通过业务数据库受理任务,不直接发布到 broker。只有 worker 使用消息专用客户端;它不复用登录缓存的连接池、自动重试或截止设置。原 APP_REDIS_* 仍负责登录限流和内容加密请求号去重。即使两组配置指向同一地址,也须满足消息持久前提;推荐分开管理。
配置项与覆盖关系见本页参数。
不同业务数据库或环境不能共用同一前缀下的 stream 与消费组,否则一个数据库的 worker 可能取得另一个数据库的引用并拒绝它。前缀属于部署配置,需要随备份恢复记录保存。改 driver、地址或前缀不迁移已确认但未消费的消息;当前不提供在线自动切换,既有部署应先完成专项停机、排空与对账演练。
多 worker 可以分摊同组消息。接管可能重投,可靠去重仍由原事件 ID、inbox 和同库业务事务负责;不能把 Redis PEL 所有权替代数据库租约。worker 信号退出会取消并等待所有循环,再关闭订阅、专用客户端和共享数据库。
# 查看积压
指标继续使用 API 的独立 loopback /metrics,不注册到业务 API,也不接受租户、事件或用户选择器。采样器每 10 秒在最多 3 秒的只读一致快照中读取固定聚合;抓取只读内存,不因每次抓取增加 SQL。采样与 listener 一起取消、join 后才关闭数据库。
| 指标 | 含义 |
|---|---|
fago_background_backlog | 固定队列/状态的待办总量,包含未到重试时间的待办;文件 uploading 只统计过期预约、deleted 只统计到期复查 |
fago_background_due | 调度时间及租约截止均已到达的待办数;执行器仍须复核当前租户/套餐准入 |
fago_background_retry_waiting | 已尝试、目前等待重试的 pending 数量;正常 deleted 复查不算失败重试 |
fago_background_expired_leases | 正租约已经到期的在途数量;零租约不算过期 |
fago_background_oldest_due_age_seconds | 最早可接管时间至采样时刻的秒数,不冒充事件创建年龄 |
fago_background_oldest_accepted_age_seconds | 通知 queued 或工作流命令的首次业务受理年龄,与下一次重试时间独立 |
fago_background_snapshot_success | 最新采样是否完整成功;启动尚未取得快照也为 0 |
fago_background_snapshot_timestamp_seconds | 最近成功快照的 Unix 秒时间;可据此判断陈旧,启动未成功为 0 |
标签只有固定 queue 与 state,不包含租户、账号、事件、处理器、正文或租约 token。队列为 outbox、inbox、job_plans、job_runs、notice_tasks 、files 和下列四类工作流队列。未使用的数值为零;notice_tasks/queued 是业务等待,只有总量和受理年龄,不虚构调度 due 或消费租约。job_plans/due 只包含已启用、未删除且到期的计划。
工作流标签同样固定,不加入实例、租户、engineVersion或job标识;不用消息outbox/inbox的结算量代替业务队列积压。
| queue | state | 统计边界 |
|---|---|---|
workflow_commands | pending、leased、dead | 发起及审批受理命令;受理年龄从首次created_at计算,不因退避重置 |
workflow_ready | pending、leased、blocked、dead | blocked归accepted命令独占,计总量但due/retry为0,不与command简单相加 |
workflow_timers | pending、leased、dead | 可领取时间按ready_at与lease_until较大值;原due_at不是失败退避后的可重试点 |
workflow_external | pending、leased、dead | 包含执行/补偿待办;dead不会自动重试,不能从计数推断远端效果 |
工作流done/idle/cancelled/compensated历史不算积压。有效租约不计due,过期租约可见,但实际执行仍要通过当前能力与fence。retry_waiting是已尝试而当前pending的数量,包含已经到重试时刻的pending,不等同于“未来才到期”的数量。人工等待也不等于ready积压。
在配置的本机指标端口抓取;例如仍使用默认127.0.0.1:9091时,请求与响应节选如下:
GET /metrics HTTP/1.1
Host: 127.0.0.1:9091
HTTP/1.1 200 OK
fago_background_snapshot_success 1
fago_background_backlog{queue="workflow_commands",state="pending"} 1
fago_background_backlog{queue="workflow_ready",state="blocked"} 1
即使后段工作流SQL失败,也保留上一份完整数值,不能把它当成“现在仍有一个待办”。失败标志与timestamp共同提示旧快照:
HTTP/1.1 200 OK
fago_background_snapshot_success 0
fago_background_backlog{queue="workflow_commands",state="pending"} 1
首次 inbox 认领可能保留 due_at=0,有效时间按调度点与租约截止的较大值计算,避免零值吞掉其他旧待办。年龄固定在采样时刻;失败保留上一份完整快照并标记失败,不发布部分或零值快照。多 API 副本观测同一个数据库,聚合应取 max,不能把各副本直接相加。
可先用以下表达式建立本地观察,再按部署基线设置持续时间和阈值:
max(fago_background_backlog{state="dead"}) > 0
max(fago_background_oldest_due_age_seconds) > 300
max(fago_background_oldest_accepted_age_seconds{queue="notice_tasks"}) > 300
max(fago_background_oldest_accepted_age_seconds{queue="workflow_commands"}) > 300
max(fago_background_expired_leases{queue=~"workflow_.*"}) > 0
min(fago_background_snapshot_success) == 0
max(time() - fago_background_snapshot_timestamp_seconds) > 60
这些是告警表达式示例。当前没有连接外部告警平台或通知渠道;部署方须登记目标并实际试发。采样不是消息 broker 的 XLEN/PEL/lag 全量监控,Redis 存储容量与 AOF 状态仍须由 Redis 运维监控补齐。既有 HTTP、连接池指标见 发布验证与恢复演练。
# 保留与满载处理
当前数据库采用持续保留策略:业务快照、完成/死信记录、去重身份与执行历史不自动到期删除。文件 deleted tombstone 持续复查是对迟到 Put 的保护,不等同于清理失败。自动归档与保留期清理尚未提供;不能单删 inbox、outbox、运行或历史表后继续重放。
Redis stream 不使用 MAXLEN 无条件裁剪。正式 worker 每 30 秒执行可选有界回收,只扫描本进程登记的主题;每个 stream 每轮最多 32 条,轮巡条目及主题,保留窗口外且所有现有组已读取、PEL 已无未确认记录的引用才允许删除。Redis 服务端时间决定窗口,组状态核对和删除在同一 Lua 脚本内完成,领取、确认、建组与删除不会穿过检查竞态。零组、元数据异常或超过 64 个组时保留并拒绝回收,不忽略闲置组。SDK 的 Options.RetainFor=0 保持原先不回收的合同;非零须为整数毫秒,正式环境配置按整数小时。
新建组从 0 追赶的是当前仍保留的历史,不能承诺已回收旧引用的全量回放。不能在运行中以 NOACK、SETID、DELCONSUMER 或手工删键替代正式订阅确认协议。安全回收不删除 SQL 原事件、inbox done 或业务快照;Redis 崩溃后引用重新出现时,仍使用原 SQL 身份去重。
维护日志只记录 scanned/deleted/blocked 和固定错误码。跨主题或应答丢失时,失败可能已有部分安全回收,不能据错误推断一条也没有删除。达到 stream_limit 时仍拒绝新发布,SQL outbox 保留可观察的失败/重试或死信;窗口内或未确认记录不会为腾容量而被强删。不要通过扩大上限、删 stream 或切换前缀来猜测恢复成功。应先处理容量原因、核对业务未完成记录及各组 PEL,再按已授权恢复原事件;超过重试窗口进入 dead 后需人工双版本恢复。
当前没有 SQL 历史自动清理或消息后端在线迁移命令。需要缩短保留期时,必须先确定归档、禁止重放截止及去重 tombstone 的协同方案,并执行专项恢复验证。定时任务和通知的取消、幂等及死信操作分别见 定时任务 与 站内通知。M5 和 R2.3 整体状态以 实施记录 为准。
# 工作流提示与恢复
工作流发起的实际服务组合会在原业务事务写workflow.command.ready提示。完成当前schema迁移
并通过真实管理服务登记运行时后,按工作流配置启用worker,消息循环才同时订阅该主题。
平台/审批HTTP与首批真实Vben页面已接入,操作见工作流API;空运行时目录不能仅开配置完成上线。
下面片段沿用原Event确认与SQL恢复,不改其他worker配置。环境变量优先,版本名单整体覆盖,须事先登记精确清单:
messaging:
driver: event
workflow:
worker_enabled: true
pool_enabled: false
runtime_versions: [fago-form-approval-v2]
poll_millis: 1000
lease_millis: 30000
retry_millis: 1000
消费者/组名固定为workflow.command.wakeup。多进程分摊同组,不能把进程ID或池ID加到组名制造广播与重复历史。
它只验证本库元数据并提交inbox,不在租户锁内执行工作流;提交后发出可合并的本机提示,实际命令循环仍重新领取SQL job。
关闭工作流时该类型不派发,原outbox保留pending;不会因为通知循环仍在运行就误认为工作流已启动。
outbox sent只表示传输确认,inbox done只表示提示已结算,两者都不代表流程结束。应以原命令/实例/业务结果为准。 非法提示可进入inbox dead并确认传输,依赖故障按原窗口退避;租户停用仍受当前能力门禁,不借旧会话恢复。 不要通过删inbox、清fence或补造job来消除提示积压。
重复、乱序、取消后或退役后的旧提示可安全结算,不重新执行业务。早期无后台job的档案仍只保留原在线恢复合同, 新后台档案缺job则拒绝为持久化损坏。提示过期或丢失时,原SQL轮询仍能恢复,不能把消息作为唯一调度来源。 即使原outbox超过一天进入dead,也不自动重置期限;如需人工重放,仍使用原可靠消息授权与版本检查。
消息回调不执行HTTP或其他远程副作用。可靠外部动作、固定输入、独立收据、受控重试/补偿及实际worker循环已接入;完整组合与容量继续验收。 实际双库/Event/Redis与正式子进程范围见M6实施记录。
# 参数
配置统一从当前环境YAML加载,环境变量优先。名单及消息前缀按完整值覆盖,不隐式合并;工作流参数见配置手册。
| YAML 键 | 环境变量 | 默认与限制 |
|---|---|---|
messaging.driver | APP_MESSAGING_DRIVER | event;只接受 event、redis |
messaging.redis_address | APP_MESSAGING_REDIS_ADDR | Redis 模式显式提供 host:port |
messaging.redis_password | APP_MESSAGING_REDIS_PASSWORD | 仅交专用客户端,不进日志或租户参数 |
messaging.prefix | APP_MESSAGING_PREFIX | Redis 模式显式提供;小写字母开头,随后小写字母、数字、下划线或短横线,最长 32 位 |
messaging.capacity | APP_MESSAGING_CAPACITY | 默认 32,1–256 个在途发布额度 |
messaging.stream_limit | APP_MESSAGING_STREAM_LIMIT | 默认 100000,1–1000000 条 stream 保留上限 |
messaging.min_idle_millis | APP_MESSAGING_MIN_IDLE_MILLIS | 默认 60000,30000–300000 毫秒,过期 PEL 接管闲置期 |
messaging.retention_hours | APP_MESSAGING_RETENTION_HOURS | 默认 168,0–8760 小时;0 禁用引用回收,不删除 SQL 业务或去重 |
# 失败
非法driver、缺失Redis消息配置或服务端能力不足时,worker非零退出;先修正配置或依赖,不把降级Event当作恢复成功。采样失败只记录稳定业务码,保持上一份完整快照;先检查snapshot_success/timestamp,再检查库连接、schema及索引。MySQL缺领取索引时采样失败,不静默改为扫描全部历史。抓取失败与SQL业务失败不同,不能通过清队列/租约消除告警。
# 边界
指标只覆盖上述固定SQL队列及当前快照,不是任务正文、远端结果或自动执行许可,也不是生产容量承诺。工作流采样已有四队列状态、长历史访问路径及真实API指标端口专项;端到端工作流并发吞吐、锁等待/恢复容量和生产拓扑仍待完整验收。达梦等其他数据库未由本专项获得认证。