首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >记一次工厂生产线告警工作流“随机失效”的诡异 Bug

记一次工厂生产线告警工作流“随机失效”的诡异 Bug

原创
作者头像
七条猫
发布2025-09-24 16:25:29
发布2025-09-24 16:25:29
1260
举报

下午,最怕的就是在准备进入“摸鱼”状态时,工作群里弹出急促的 @全体成员。这次也不例外,消息来自我们合作的一家现代化工厂的生产线主管:“紧急求助!我们产线的温度告警工作流又失效了!后台数据显示温度连续几次都超标了,但告警灯没亮,钉钉群里也没收到通知!这已经是本周第三次了!”

看到“又”和“第三次”,我的头皮瞬间发麻。这种“随机发生”的 Bug,是每个程序员的噩梦。它不像必现 Bug 那样可以稳定复现,而是像一个幽灵,在你最意想不到的时候出现,又在你准备好工具要抓它时消失得无影无踪。

一、技术背景:低代码驱动的智能工厂 IOT 系统

在深入排查之前,有必要先介绍一下我们这套系统的架构。它是一个典型的IOT 系统,旨在通过传感器和自动化工作流,提升工厂生产线的智能化水平。

组件/层面

技术实现

职责

数据采集层

产线温度传感器

通过 MQTT 协议,每 10 秒上报一次产线实时温度数据。

数据接入层

EMQ X (MQTT Broker)

接收并分发所有 IoT 设备的遥测数据。

核心处理层

自研低代码工作流引擎

允许产线主管通过拖拽节点的方式,自定义业务规则。本次出问题的就是其中一个告警工作流。

数据存储层

InfluxDB + MySQL

InfluxDB 存储海量的时序传感器数据;MySQL 存储工作流定义、执行日志和告警记录。

告警触达层

硬件控制模块 + 钉钉机器人

接收工作流指令,点亮物理告警灯,并向指定钉钉群发送消息。

出问题的低代码工作流逻辑非常简单,由产线主管自己配置:

当A产线的温度传感器,连续 3 次检测到的温度都高于 85℃ 时,立即触发“一级告警”动作(亮灯 + 发钉钉)。

这个逻辑在低代码平台里,看起来就像下面这样一目了然的流程图:

二、诡异的 Bug 现象:幽灵般的失效

根据主管的反馈和我们后台的日志,Bug 现象被精确地描绘了出来:

  1. 数据无误:我们从 InfluxDB 中调取了故障时间点前后一分钟的原始数据,确认传感器上报的数据是准确的。例如,在 14:30:1014:30:2014:30:30 这三个时间点,温度分别是 86.1℃87.5℃86.8℃,确实满足了“连续3次高于85℃”的条件。
  2. 工作流“部分”执行:查看 MySQL 中的工作流执行日志,我们发现这三个时间点,工作流确实都被触发了。但是,它只执行了“数据接收”和“条件判断(>85℃)”这两个节点,并没有进入“计数”和“触发告警”的环节。
  3. 无规律性:这种失效并非每次都发生。在绝大多数情况下,告警都是正常的。一天之内,可能只会出现一两次这样的“哑火”。

数据流是通的,但逻辑流在某个环节被“神秘力量”中断了。

三、排查之旅:在迷雾中寻找线索
第一站:怀疑低代码逻辑配置

最直接的怀疑对象,自然是产线主管自己配置的低代码工作流。会不会是哪个参数配错了?比如把“连续3次”配成了“连续30次”?

我立刻登录平台,仔细检查了工作流的每一个节点配置。结果是:配置完全正确。主管并没有犯这种低级错误。这条线索断了。

第二站:怀疑时区或时间戳问题

接下来,我开始怀疑时间。MQTT 消息里带有时间戳,我们的工作流引擎在判断“连续”时,也依赖于时间。会不会是传感器、MQTT Broker、工作流服务器之间的时区不统一,导致时间戳解析错误,使得系统认为这三条消息并非“连续”?

这是一个非常常见的分布式系统 Bug。我立刻拉取了三方服务器的系统时间,并检查了日志中记录的各个环节的时间戳。

  • 传感器上报时间戳 (UTC)
  • EMQ X 接收时间戳 (UTC)
  • 工作流引擎处理时间戳 (CST, UTC+8)

虽然时区不同,但我们的代码中做了规范的转换处理。经过一番详细计算,确认时间戳的连续性判断逻辑没有问题。这条路也走不通了。

第三站:真相的曙光 —— 分布式状态的“陷阱”

两次碰壁后,我不得不回到最底层的架构去思考。我们的低代码工作流引擎为了保证高可用,是集群化部署的,一共有 4 个节点(Node)在运行。

这时,一个念头如同闪电般划过我的脑海:状态!计数的“状态”存储在哪里?

我冲到代码前,找到了负责处理这个工作流的核心逻辑。不看不知道,一看吓一跳。为了性能,当时的开发人员将“连续次数”这个计数器,直接存放在了工作流引擎节点的内存中!

问题瞬间清晰了!

  1. MQTT Broker 在分发消息时,采用的是轮询(Round-Robin)策略,它会把消息依次发送给后端的不同工作流节点,以实现负载均衡。
  2. 假设我们有 Node A, B, C, D 四个节点。
  3. 14:30:10,第一条超温消息 (86.1℃) 被发送到了 Node A。Node A 在自己的内存里记录:“传感器X,超温次数:1”。
  4. 14:30:20,第二条超温消息 (87.5℃) 被发送到了 Node B。Node B 在自己的内存里记录:“传感器X,超温次数:1”。
  5. 14:30:30,第三条超温消息 (86.8℃) 被发送到了 Node C。Node C 在自己的内存里记录:“传感器X,超温次数:1”。

灾难发生了! 在任何一个节点看来,超温次数都从未达到过 3 次!它们各自为战,信息完全孤立。那个作为触发条件的计数器,被无情地分散在了集群的各个角落,永远无法累加到我们期望的阈值。

这就是 Bug“随机”发生的原因:只有当 MQTT Broker 碰巧将连续 3 条消息都发给了同一个工作流节点时,告警才能成功触发。而大部分情况下,消息会被分发到不同节点,导致告警失效。

四、解决方案:引入分布式共享状态

定位了根源,解决起来就简单了。问题的核心在于本地状态无法在分布式环境下共享。我们需要一个所有节点都能访问的、统一的、原子的状态存储。

最佳选择:Redis。

我们对工作流引擎的“计数器”节点进行了改造:不再使用节点内存,而是引入 Redis 来进行分布式计数。

改造后的伪代码逻辑:

代码语言:java
复制
// 当工作流执行到“计数器”节点时
public class CounterNodeHandler {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public void process(SensorData data) {
        if (data.getTemperature() > 85.0) {
            String counterKey = "workflow:counter:" + data.getSensorId();
            
            // 使用 Redis 的 INCR 命令,这是一个原子操作
            Long count = redisTemplate.opsForValue().increment(counterKey);

            // 每次计数后,都设置一个合理的过期时间(例如60秒)
            // 防止因消息中断导致计数器永远存在
            redisTemplate.expire(counterKey, 60, TimeUnit.SECONDS);

            if (count >= 3) {
                // 触发告警
                triggerAlarm(data.getSensorId());
                
                // 告警后立即清除计数器,避免重复告警
                redisTemplate.delete(counterKey);
            }
        } else {
            // 如果温度正常,则立即清除计数器
            String counterKey = "workflow:counter:" + data.getSensorId();
            redisTemplate.delete(counterKey);
        }
    }
}

通过这个改造,无论 MQTT 消息被分发到哪个工作流节点,它们操作的都是 Redis 中同一个 keyINCR 的原子性保证了计数的准确性,EXPIRE 机制则保证了系统的健壮性。

部署上线后,我们观察了两天,那个“幽灵”般的 Bug 再也没有出现过。

五、总结与反思:低代码不是“无代码”

这次惊心动魄的 Debug 经历,给我带来了深刻的教训和反思:

  1. 分布式系统的“常识”至关重要:在设计任何集群化应用时,必须对“状态”保持高度警惕。本地内存是性能最快的,但也是分布式环境下最危险的“陷阱”。任何需要在多个请求、多个节点间共享的信息,都必须存储在外部共享存储中(如 Redis, Zookeeper)。
  2. 低代码平台隐藏了复杂性,但并未消除它:对于使用者(如产线主管)来说,低代码平台非常友好。但对于开发者而言,我们必须意识到,这些简单的拖拽节点背后,是复杂的分布式计算和状态管理。设计低代码平台的内置功能(如计数器、定时器)时,必须默认它们会运行在分布式

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、技术背景:低代码驱动的智能工厂 IOT 系统
  • 二、诡异的 Bug 现象:幽灵般的失效
  • 三、排查之旅:在迷雾中寻找线索
    • 第一站:怀疑低代码逻辑配置
    • 第二站:怀疑时区或时间戳问题
    • 第三站:真相的曙光 —— 分布式状态的“陷阱”
  • 四、解决方案:引入分布式共享状态
  • 五、总结与反思:低代码不是“无代码”
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档