5000 台 IoT 设备的异常检测,全 Serverless 架构月成本几十美元——Lambda + DynamoDB + Step Functions 实战

5000 台 IoT 设备的异常检测,全 Serverless 架构月成本几十美元------Lambda + DynamoDB + Step Functions 实战

管几千台 IoT 设备,传统方案要自己搭服务器跑定时脚本?亚马逊云科技官博最近分享了一个纯 Serverless 方案,分钟级检测、告警去重、批量故障识别,5000 台设备月成本可能就几十美元。

亚马逊云科技官博最近(3/27)发了一篇实战文章:用纯 Serverless 架构搭了一套分钟级的 IoT 设备异常检测系统。不用管服务器,按量付费,几千台设备的规模成本还很低。

拆解一下核心设计思路和代码实现。

业务痛点

智能建筑的 IoT 设备管理,几个头疼的问题:

  1. 设备多、类型杂:一栋楼几百台设备,温控、门禁、电梯、消防,每种心跳周期不一样
  2. 误报太多:设备实际在线但平台显示离线,维保人员收到一堆无效告警,干脆关了邮件通知
  3. 批量故障定位慢:网络断了导致一层楼的设备全掉线,告警风暴来了才发现是网络问题不是设备问题
  4. 告警重复:同一台设备已经报过异常了,下一次检测又报一遍

这些问题用传统方案(轮询数据库、定时脚本)都能解决,但维护成本高,扩展性差。

架构设计

整套系统全部 Serverless,核心组件:

arduino 复制代码
IoT 设备 → MQTT → Amazon IoT Core → IoT Rules → Lambda(数据预处理)
                                                        ↓
                                                  DynamoDB(设备状态表)
                                                        ↓
                                    EventBridge Scheduler(每分钟触发)
                                                        ↓
                                              Step Functions(检测流程编排)
                                                        ↓
                                    Lambda(异常判定) → SNS(告警通知)

核心表设计

Amazon DynamoDB 里维护一张设备状态表:

python 复制代码
# DynamoDB 表结构
{
    "deviceId": "00284L4XHV5Y8T0601001142U",  # PK
    "projectId": "002",                         # GSI
    "lastHeartbeat": 1766743982151,             # 最后心跳时间戳
    "lastDataReport": 1766743982151,            # 最后数据上报时间戳
    "status": "online",                          # 当前状态
    "alertSent": False,                          # 是否已发告警(防重复)
    "deviceType": "temperature_sensor",
    "heartbeatInterval": 300,                    # 预期心跳间隔(秒)
    "floor": "3F",
    "room": "0301"
}

关键字段是 alertSent------设备已经报过异常后置为 True,直到收到新心跳才重置。解决告警重复问题。

数据预处理 Lambda

设备上报数据时,Lambda 更新 DynamoDB:

python 复制代码
import boto3
import time

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('DeviceStatus')

def handler(event, context):
    """处理 IoT Core 转发的设备消息"""
    for record in event.get('records', [event]):
        device_id = record['deviceId']
        message_type = record.get('messageType', 'DATA')
        
        update_expr = "SET lastDataReport = :ts, #s = :online, alertSent = :false"
        expr_values = {
            ':ts': int(time.time() * 1000),
            ':online': 'online',
            ':false': False
        }
        
        if message_type == 'HEARTBEAT':
            update_expr += ", lastHeartbeat = :ts"
        
        table.update_item(
            Key={'deviceId': device_id},
            UpdateExpression=update_expr,
            ExpressionAttributeNames={'#s': 'status'},
            ExpressionAttributeValues=expr_values
        )
    
    return {'statusCode': 200}

异常检测逻辑

每分钟 Amazon EventBridge Scheduler 触发 AWS Step Functions,遍历设备表检查异常:

python 复制代码
import boto3
import time

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('DeviceStatus')
sns = boto3.client('sns')

ALERT_TOPIC = 'arn:aws:sns:ap-northeast-1:123456789:device-alerts'

def detect_anomalies(event, context):
    """检测设备异常"""
    now = int(time.time() * 1000)
    anomalies = []
    
    # 扫描所有在线设备
    response = table.scan(
        FilterExpression='#s = :online AND alertSent = :false',
        ExpressionAttributeNames={'#s': 'status'},
        ExpressionAttributeValues={':online': 'online', ':false': False}
    )
    
    for device in response['Items']:
        heartbeat_interval = device.get('heartbeatInterval', 300) * 1000
        # 超过 2 倍心跳间隔未收到消息,判定异常
        threshold = heartbeat_interval * 2
        
        last_contact = max(
            device.get('lastHeartbeat', 0),
            device.get('lastDataReport', 0)
        )
        
        if now - last_contact > threshold:
            anomalies.append({
                'deviceId': device['deviceId'],
                'projectId': device['projectId'],
                'floor': device.get('floor'),
                'lastContact': last_contact,
                'silentMinutes': (now - last_contact) // 60000
            })
            
            # 标记已告警,防止重复
            table.update_item(
                Key={'deviceId': device['deviceId']},
                UpdateExpression='SET alertSent = :true, #s = :offline',
                ExpressionAttributeNames={'#s': 'status'},
                ExpressionAttributeValues={':true': True, ':offline': 'offline'}
            )
    
    # 批量故障检测:同一楼层超过 3 台设备同时离线
    floor_counts = {}
    for a in anomalies:
        key = f"{a['projectId']}-{a.get('floor', 'unknown')}"
        floor_counts[key] = floor_counts.get(key, 0) + 1
    
    batch_failures = {k: v for k, v in floor_counts.items() if v >= 3}
    
    if anomalies:
        alert_message = format_alert(anomalies, batch_failures)
        sns.publish(TopicArn=ALERT_TOPIC, Message=alert_message)
    
    return {
        'totalChecked': response['Count'],
        'anomaliesFound': len(anomalies),
        'batchFailures': batch_failures
    }

def format_alert(anomalies, batch_failures):
    """格式化告警消息"""
    lines = [f"检测到 {len(anomalies)} 台设备异常\n"]
    
    if batch_failures:
        lines.append("⚠ 疑似批量故障:")
        for location, count in batch_failures.items():
            lines.append(f"  {location}: {count} 台设备同时离线(可能是网络问题)")
        lines.append("")
    
    for a in anomalies[:10]:  # 最多显示 10 台
        lines.append(
            f"- {a['deviceId']} | {a.get('floor','?')} | "
            f"已静默 {a['silentMinutes']} 分钟"
        )
    
    if len(anomalies) > 10:
        lines.append(f"... 还有 {len(anomalies) - 10} 台")
    
    return '\n'.join(lines)

Step Functions 编排

用 AWS Step Functions 编排整个检测流程,好处是可以加重试、超时、并行处理:

json 复制代码
{
  "StartAt": "DetectAnomalies",
  "States": {
    "DetectAnomalies": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789:function:detect-anomalies",
      "Next": "CheckResults"
    },
    "CheckResults": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.anomaliesFound",
          "NumericGreaterThan": 0,
          "Next": "LogToCloudWatch"
        }
      ],
      "Default": "Done"
    },
    "LogToCloudWatch": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789:function:log-anomalies",
      "Next": "Done"
    },
    "Done": {
      "Type": "Succeed"
    }
  }
}

几个设计亮点

1. 分钟级而不是秒级

选择分钟级检测而不是秒级,是成本和实时性的权衡。IoT 设备的心跳周期通常是 5 分钟,1 分钟检测一次已经够用。秒级检测的 Lambda 调用量会高 60 倍,成本完全不值。

2. 告警去重

alertSent 字段 + DynamoDB 条件更新,确保同一台设备在恢复前只告警一次。收到新心跳后重置标志,进入下一轮检测。

3. 批量故障识别

不只是逐台设备告警,还聚合分析------同一楼层 3 台以上设备同时离线,大概率是网络问题不是设备问题。告警消息里直接说明"疑似网络故障",维保人员不用逐台排查。

4. 全 Serverless 成本

组件 月估算(5000 台设备)
IoT Core MQTT 消息量 × 单价
Lambda(数据预处理) ~150 万次调用/月
Lambda(异常检测) ~43200 次调用/月(每分钟一次)
DynamoDB 5000 条记录,按需模式
Step Functions ~43200 次执行/月
SNS 告警消息,量很小

5000 台设备的规模,全 Serverless 按需付费,成本比自建服务器跑定时脚本低很多,运维量也小得多。

和 OpenClaw 的结合

OpenClaw Agent 可以订阅 SNS 告警,自动处理:

bash 复制代码
# OpenClaw Skill 示例:IoT 告警自动处理
# 1. 收到告警 → 判断严重程度
# 2. 批量故障 → 自动查网络设备状态
# 3. 单设备离线 → 创建工单到维保系统
# 4. 误报 → 调整检测阈值

运维自动化的核心思路:把重复的判断和操作交给 Agent,人只处理需要决策的问题。

落地建议

  1. 先从心跳监控开始:最简单的异常检测,设备没心跳就告警
  2. 加数据异常检测:温度突然跳变、湿度持续为 0 这类数值异常
  3. 用 Step Functions 编排:方便后续加新的检测逻辑,不用改主流程
  4. 告警分级:单设备离线 → 邮件通知;批量故障 → 短信 + 电话

亚马逊云科技官博原文:aws.amazon.com/cn/blogs/ch... Amazon IoT Core:aws.amazon.com/cn/iot-core... AWS Step Functions:aws.amazon.com/cn/step-fun... Amazon DynamoDB:aws.amazon.com/cn/dynamodb... OpenClaw:github.com/openclaw/op...

相关推荐
亚马逊云开发者2 小时前
WAF 误杀了正常请求怎么补数据?CloudFront + Lambda@Edge 双函数架构实战
aws
亚林瓜子5 小时前
AWS EB使用自定义镜像
云计算·bug·aws·ami·fix·eb·al2023
长征coder15 小时前
AWS-S2上传提示证书错误
aws
亚马逊云开发者1 天前
SDLC 过时了?从 2x 到 20x 效能的 AIDLC 转型实践 | Kiro + OpenClaw 实战
aws
亚马逊云开发者1 天前
从本地到云端 | OpenClaw Agent + Bedrock AgentCore SDK 部署全攻略
aws
Blue summer2 天前
使用AWS SSO + Terraform 管理AWS资源
云计算·aws·terraform
亚马逊云开发者2 天前
不用 Docker 也能跑 AI Agent | OpenClaw + Podman + EC2 Graviton + ECR 容器化部署全攻略
aws
海水冷却5 天前
RTC成语音AI基础设施:AWS和ElevenLabs相继跟进,ZEGO已跑三年
人工智能·实时音视频·aws
亚马逊云开发者5 天前
开了 GuardDuty 一周,发现 EC2 被人暴力破解 SSH 了
aws