5000 台 IoT 设备的异常检测,全 Serverless 架构月成本几十美元------Lambda + DynamoDB + Step Functions 实战
管几千台 IoT 设备,传统方案要自己搭服务器跑定时脚本?亚马逊云科技官博最近分享了一个纯 Serverless 方案,分钟级检测、告警去重、批量故障识别,5000 台设备月成本可能就几十美元。
亚马逊云科技官博最近(3/27)发了一篇实战文章:用纯 Serverless 架构搭了一套分钟级的 IoT 设备异常检测系统。不用管服务器,按量付费,几千台设备的规模成本还很低。
拆解一下核心设计思路和代码实现。
业务痛点
智能建筑的 IoT 设备管理,几个头疼的问题:
- 设备多、类型杂:一栋楼几百台设备,温控、门禁、电梯、消防,每种心跳周期不一样
- 误报太多:设备实际在线但平台显示离线,维保人员收到一堆无效告警,干脆关了邮件通知
- 批量故障定位慢:网络断了导致一层楼的设备全掉线,告警风暴来了才发现是网络问题不是设备问题
- 告警重复:同一台设备已经报过异常了,下一次检测又报一遍
这些问题用传统方案(轮询数据库、定时脚本)都能解决,但维护成本高,扩展性差。
架构设计
整套系统全部 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,人只处理需要决策的问题。
落地建议
- 先从心跳监控开始:最简单的异常检测,设备没心跳就告警
- 加数据异常检测:温度突然跳变、湿度持续为 0 这类数值异常
- 用 Step Functions 编排:方便后续加新的检测逻辑,不用改主流程
- 告警分级:单设备离线 → 邮件通知;批量故障 → 短信 + 电话
亚马逊云科技官博原文: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...