首页/新闻资讯/正文详情

EMQX 规则引擎指标计数竞态条件修复:从晦涩的 badkey 崩溃日志到可读的结构化告警

发布时间:2026/9/23 19:59:42 来源:云帆数科 栏目:资讯中心
EMQX 规则引擎指标计数竞态条件修复:从晦涩的 badkey 崩溃日志到可读的结构化告警
后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载导读本文基于 EMQX 开源仓库的变更记录 changes/ee/fix-16699.en.md 展开聚焦规则引擎Rule Engine中actions.success等指标计数在竞态条件下触发进程崩溃、并打印超长晦涩日志的经典问题以及官方随后引入的可观测性改进。读完本文你将理解emqx_metrics_worker的指标索引查找机制、崩溃根因以及修复后如何输出结构化、带上下文、可限流的告警日志便于在真实集群中快速定位同类问题。问题现象一条长达数百字符的晦涩崩溃日志在特定的竞态条件下EMQX 节点可能会打印出如下形式的错误日志原文摘录2026-02-03T13:53:54.57632600:00 [error] Generic server 0.11323236.0 terminating. Reason: {{badkey,actions.success}, [{erlang,map_get,[actions.success,#{}], [{error_info,#{module erl_erts_errors}}]}, {emqx_metrics_worker,idx_metric,4, [{file,emqx_metrics_worker.erl},{line,683}]}, {emqx_metrics_worker,inc,4, [{file,emqx_metrics_worker.erl},{line,322}]}, {emqx_rule_runtime,do_eval_action_reply_t...这条日志的核心信息隐藏在巨大的错误项与堆栈中{{badkey,actions.success},[{erlang,map_get,[actions.success,#{}]},...]}说明在调用map_get(actions.success, #{})时目标 Map 是空 map找不到actions.success这个 key抛出badkey异常堆栈依次经过emqx_metrics_worker:idx_metric/4→emqx_metrics_worker:inc/4→emqx_rule_runtime:do_eval_action_reply_to/2即规则运行时在异步 action 回复回调里递增actions.success指标时崩溃最终导致承载该逻辑的gen_server进程以异常原因终止Generic server ... terminating。由于异常未被妥善捕获Reason中混合了内部元数据与完整调用栈普通运维人员很难一眼看出是哪条规则、哪个指标、因为什么原因出了问题——这正是本次变更要解决的痛点。根因分析指标索引查找在竞态下抛出 badkey指标计数调用链在修复前规则执行动作并递增指标的核心链路如下规则运行时在动作成功时递增actions.success相关逻辑位于 emqx_rule_runtime.erl 的do_inc_action_metrics/2成功分支apps/emqx_rule_engine/src/emqx_rule_runtime.erl#L874-L897true - trace_action( ActId, action_success, maps:merge(#{result FormatterRes}, TraceContext1) ), metric_inc(RuleResId, actions.success), ?tp(rule_runtime_action_success, #{})metric_inc/2调用emqx_metrics_worker:inc(rule_metrics, RuleResId, Metric)见 emqx_rule_runtime.erl#L1015-L1037。emqx_metrics_worker:inc/4需要先根据RuleResIdmetric id与指标名找到原子计数器counters中的索引旧实现直接使用maps:get硬取修复前idx_metric/4对应崩溃日志中的line 683idx_metric(Name, Id, Type, Metric) - maps:get(Metric, get_indexes(Name, Type, Id)).get_indexes/3在persistent_term中按Id查找该规则的指标索引表get_indexes(Name, Type, Id) - case maps:get(Id, get_pterm(Name), #{}) of #{Type : Indexes} - Indexes; #{} - #{} end.竞态从何而来规则引擎在emqx_rule_engine.erl中负责指标生命周期的管理规则创建时通过maybe_add_metrics_for_rule/1初始化指标若已存在则reset_metrics_for_rule否则emqx_metrics_worker:create_metrics(rule_metrics, Id, ?METRICS, ?RATE_METRICS)见 emqx_rule_engine.erl#L428-L434规则删除时通过clear_metrics_for_rule/1清理指标见 emqx_rule_engine.erl#L436-L437。从源码结构可以推断出以下竞态场景异步桥接bridgeaction 的消息仍在飞行途中如emqx_bridge_v2:send_message的异步回复而规则已被删除或正在重建此时do_eval_action_reply_to回调里的metric_inc(RuleResId, actions.success)会面对一个尚未创建、或已被clear_metrics_for_rule清空的指标表——maps:get直接返回badkey异常进而击穿外层try捕获范围do_handle_action/2只捕获了discard、unhealthy_target等特定异常最终导致gen_server进程异常终止。修复方案安全索引查找 结构化异常上下文1. 新增idx_metric_safe/4返回可读错误原因修复的核心是在 emqx_metrics_worker.erl 中新增安全查找函数idx_metric_safe/4apps/emqx_utils/src/emqx_metrics_worker.erl#L732-L745把找不到从badkey异常细化为结构化错误原因idx_metric_safe(Name, Id, Type, Metric) - Metrics get_pterm(Name), case maps:find(Id, Metrics) of {ok, #{Type : #{Metric : Idx}}} - {ok, Idx}; {ok, #{Type : _}} - {error, metric_not_found}; {ok, _} - {error, type_not_found}; error when map_size(Metrics) 0 - {error, empty_metrics_in_pt}; error - {error, id_not_found} end.依据缺失层次的不同返回原因包括错误原因含义metric_not_found该规则的指标表中不存在该指标名type_not_found该规则未注册对应指标类型counter/slide/histempty_metrics_in_ptpersistent_term中整体指标表为空id_not_found找不到该规则metric id的指标记录no_ref找不到该规则的原子计数器引用由get_ref/2返回随后get_counter_ref_and_idx/3apps/emqx_utils/src/emqx_metrics_worker.erl#L987-L998先取计数器引用、再安全取索引get_counter_ref_and_idx(Name, Id, Metric) - case get_ref(Name, Id) of not_found - {error, no_ref}; CounterRef - case idx_metric_safe(Name, Id, counter, Metric) of {ok, Idx} - {ok, CounterRef, Idx}; {error, _} Error - Error end end.2.inc/4、set/4抛出携带完整上下文的异常inc/4apps/emqx_utils/src/emqx_metrics_worker.erl#L335-L351与set/4apps/emqx_utils/src/emqx_metrics_worker.erl#L355-L370在查找失败时不再让badkey裸奔而是抛出结构化的failed_to_update_counter异常inc(Name, Id, Metric, Val) - case get_counter_ref_and_idx(Name, Id, Metric) of {ok, CounterRef, Idx} - counters:add(CounterRef, Idx, Val); {error, Reason} - throw( {failed_to_update_counter, #{ action inc, val Val, reason Reason, name Name, id Id, metric Metric }} ) end.该异常 map 中包含了动作类型action、增量值val、失败原因reason、指标 worker 名name、规则资源 IDid与指标名metric——任何一层信息都能直接帮助定位是哪条规则、哪个指标、在何种缺失状态下触发。调用方改进捕获异常并输出可读的限流告警在调用侧emqx_rule_runtime.erl 的metric_inc/2apps/emqx_rule_engine/src/emqx_rule_runtime.erl#L1015-L1037显式捕获上述结构化异常并用?SLOG_THROTTLE宏输出语义化的 warning 日志metric_inc(RuleResId, Metric) - try emqx_metrics_worker:inc(rule_metrics, RuleResId, Metric) catch throw:{failed_to_update_counter, #{ reason : Reason, name : Name, id : Id, metric : Metric }} - ?SLOG_THROTTLE( warning, #{ msg failed_to_update_metric_counter, action inc, reason Reason, name Name, id Id, metric Metric } ), ok end.相比修复前现在的日志不再导致进程崩溃异常被try ... catch捕获并降级为ok规则执行路径得以继续信息可读直接输出msg failed_to_update_metric_counter及id规则 ID、metric如actions.success、reason如id_not_found、name自动限流?SLOG_THROTTLE宏定义于 apps/emqx/include/logger.hrl#L35-L58在写日志前会调用emqx_log_throttler:allow(__Msg, UniqueKey)进行节流避免高并发竞态下告警刷屏。日志限流的配置项failed_to_update_metric_counter已纳入log.throttling.msgs的默认消息列表见 apps/emqx_conf/src/emqx_conf_schema.erl#L139。对应的限流配置位于log.throttling段apps/emqx_conf/src/emqx_conf_schema.erl#L1393-L1414log { throttling { ## 限流时间窗口默认 1m time_window 1m ## 受限流保护的消息列表failed_to_update_metric_counter 已默认包含其中 # msgs [...] } }time_window默认值为1m即同一种消息在同一分钟窗口内会被合并输出防止竞态风暴期间产生海量日志。修复效果的验证依据在仓库测试中规则引擎各类桥接HTTP、Kafka、MQTT、MySQL、Pulsar 等的测试套件均对actions.success等规则指标做了断言例如 emqx_bridge_mysql_SUITE.erl#L845 直接校验?assertEqual(1, emqx_metrics_worker:get(rule_metrics, RuleId, actions.success)),这证明rule_metricsworker 与actions.success指标是规则/桥接链路对外暴露的标准可观测接口本次修复保证的是即使出现指标表缺失的竞态也只输出一条可读告警而不再以进程崩溃 超长badkey堆栈的形式破坏运行稳定性。小结维度修复前修复后索引查找maps:get硬取直接抛badkeyidx_metric_safe/4返回metric_not_found/id_not_found等结构化原因异常形态崩溃日志Generic server ... terminating 超长堆栈捕获后输出限流 warningfailed_to_update_metric_counter上下文信息几乎不可读含规则 ID、指标名、失败原因、worker 名运行影响gen_server 进程异常终止降级为ok执行路径不受影响核心改动集中在两个文件emqx_metrics_worker.erl安全查找与结构化异常与 emqx_rule_runtime.erl捕获与可读告警。对运行 EMQX 的用户而言若在日志中看到failed_to_update_metric_counter且reason为id_not_found即可快速联想到规则删除/重建与在途消息之间存在时序竞争从而精准定位无需再逐层拆解晦涩的badkey堆栈。赞分享后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载相关推荐EMQX 告警系统启动竞态修复解析runq 过载告警与 emqx_alarm_handler 虚假崩溃日志EMQX 告警系统启动竞态修复解析runq 过载告警与 emqx_alarm_handler 虚假崩溃日志 本篇技术指南围绕 EMQX 开源仓库中 chang后端物联网消息队列通信如何使用 RunApiDiff.ps1 生成两个 .NET 版本之间的 API 差异报告如何使用 RunApiDiff.ps1 生成两个 .NET 版本之间的 API 差异报告 如果你需要在 dotnet/core 仓库中发布某个 .NET 版本的后端物联网消息队列通信MongoDB 的 $expr: {$in: [常量, $字段]} 重写优化原理、query knob 与 Golden Test 全解析MongoDB 的 $expr: {$in: 常量, $字段 } 重写优化原理、query knob 与 Golden Test 全解析 导读 本文以 M后端物联网消息队列通信创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

Java实现论文查重:SimHash算法与文本相似度计算实战
Java实现论文查重:SimHash算法与文本相似度计算实战

简介:基于Java的论文查重系统源码,采用SimHash算法计算原文与抄袭版论文之间的相似度并输出重复率,适合计算机相关专业学生进行课程设计、毕业设计,或Java文本处理方向的学习。项目支持通过命令行参数指定原文、抄袭论文与答案文件… · 2026/9/23 19:59:42

Bloc 官方 Zed 扩展使用指南:代码片段与 Bloc 语言服务器完整解析
Bloc 官方 Zed 扩展使用指南:代码片段与 Bloc 语言服务器完整解析

前端 【免费下载链接】bloc A predictable state management library that helps implement the BLoC design pattern 项目地址: https://gitcode.com/gh_mirrors/bl/bloc 点击查看 免费下载 Zed 是当前广受关注的现代代码编辑器,而 Bloc 是 Dart / Flu… · 2026/9/23 19:59:29

苹果数据迁移避坑指南:3步搞定iOS换机不丢数据
苹果数据迁移避坑指南:3步搞定iOS换机不丢数据

苹果数据迁移避坑指南:3步搞定iOS换机不丢数据 官方文档翻了三遍还是头大?别慌,我整理了一份实战避坑指南。咱们直接上干货,拒绝废话。… · 2026/9/23 19:59:22

搞懂下一张1070瓶颈,3个实战项目教你性能翻倍
搞懂下一张1070瓶颈,3个实战项目教你性能翻倍

搞懂下一张1070瓶颈,3个实战项目教你性能翻倍 刚拿到下一张1070显卡的朋友,是不是也跟我一样,装完驱动跑分挺高,但一到实际写代码、跑模型或者渲染项目,风扇就狂转,帧数或编译速度却掉得厉害?… · 2026/9/23 20:42:26

插队拼单性能优化:从入门到精通的实战避坑指南
插队拼单性能优化:从入门到精通的实战避坑指南

插队拼单性能优化:从入门到精通的实战避坑指南 看了一堆教程还是不会写项目?别急,今天咱们不聊虚的,直接上手。 很多后端开发者在接到“插队拼单”这类高并发业务需求时,第一反应往往是堆代码、加锁。结果上线后,系统卡死,响应时间从毫秒级飙秒级。这… · 2026/9/23 20:42:19

2026最新安全加密软件性能调优:告别配置卡半天
2026最新安全加密软件性能调优:告别配置卡半天

2026最新安全加密软件性能调优:告别配置卡半天 你是不是也遇到过这种情况:为了搞个安全加密功能,环境配置折腾了半天,代码写了一堆,结果一跑就卡死?别急,这不是你的错。2026年最新的安全加密软件生态变了,老旧的加密算法和冗余的配置流程成了… · 2026/9/23 20:42:19

3个细节一文搞懂大气的字渲染底层
3个细节一文搞懂大气的字渲染底层

3个细节一文搞懂大气的字渲染底层 报错堆满屏幕,StackTrace 像天书一样滚动,盯着 NullPointerException 或 OutOfMemoryError… · 2026/9/23 20:42:19

ELB负载均衡源码深扒:3个核心机制看懂最佳实践
ELB负载均衡源码深扒:3个核心机制看懂最佳实践

ELB负载均衡源码深扒:3个核心机制看懂最佳实践 官方文档几百页,翻到头晕还是抓不住重点?别慌。今天咱们不背概念,直接钻进 ELB… · 2026/9/23 20:42:05

3步搭建公司文件管理系统,实战项目避坑指南
3步搭建公司文件管理系统,实战项目避坑指南

3步搭建公司文件管理系统,实战项目避坑指南 官方文档翻了三遍还是懵?别急,这不是你的问题,是文档太“高冷”了。咱们做市政工程的,项目现场文件堆成山,Excel 台账乱得没法看,这时候你需要的不是一个理论家,而是一个能直接落地的 实战项目… · 2026/9/23 20:41:59

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码