ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

实战|腾讯云助手处理 SCF 死信队列故障(一)

实战|腾讯云助手处理 SCF 死信队列故障(一) 实战腾讯云助手处理 SCF 死信队列故障一死信日志解析与投递轨迹还原系列导航第一篇本篇事件驱动架构下的死信全景、死信原始日志结构解析、投递轨迹还原第二篇从死信日志定位业务缺陷——三类典型缺陷的归因实战第三篇安全消息重放脚本——幂等去重、限速灰度、断点续放一、死信队列事件驱动架构的急诊室我们的订单履约链路跑在 SCF云函数 消息队列上下单 → 订单服务 → 履约函数 → 通知函数。这套架构跑顺了很优雅出问题了很要命——一条消息在链路上某处死了没有任何界面告诉你它为什么死、死在哪。死信产生的三大来源消费失败函数抛异常超过最大重试次数消息进死信队列DLQ消费超时函数执行超过配置的超时时间被强制终止消息进了 DLQ 但函数可能已执行一半最危险的一类第三篇重放的大坑投递失败消息本身格式非法schema 不符、payload 损坏永远不可能消费成功。麻烦在于三点死信队列里躺着的是压缩base64 的原始消息体 元数据人眼直接看基本是天书一条死信的病历分散在多处队列的投递记录、函数日志、DLQ 消息体本身没有现成的视图把它们串起来死信堆积是慢性病——每天死 20 条不痛不痒两个月后突然发现积压 8,000 条其中一半已经过了业务时效。本篇解决看得懂怎么把死信的原始日志解析成人类可读、AI 可分析的结构。第二篇解决查得准第三篇解决放得安全。二、死信日志到底长什么样以 CKafka / CMQ 触发 SCF 的场景为例一条死信消息的原始结构简化{Topic:order-fulfillment,Partition:3,Message:eyJvcmRlcklkIjogIk9SRC0yMDI2MDkyMS0wMDAxIiwgInNrdUlkIjogIlNLVS05O...,MsgKey:ORD-20260921-0001,TryCount:3,ErrorCode:SCF_TIMEOUT,ErrorMsg:Task timed out after 30 seconds,FirstFailsTime:1758421200000,DeadLetterTime:1758421560000,ClientToken:cl-7f3a2b}逐字段解读这些字段是后续 AI 分析的关键特征字段含义分析价值Messagebase64(压缩) 的业务消息体还原业务上下文的核心TryCount已重试次数3 次失败 vs 1 次就死指向不同的缺陷类型ErrorCode死信原因码TIMEOUT / INVOKE_ERROR / PAYLOAD_INVALID 三分类FirstFailsTime首次失败时间与DeadLetterTime的差值 重试总耗时窗口MsgKey业务消息键重放去重的关键第三篇ClientToken投递幂等令牌判断消息是否可能被重复投递三个第一眼容易看错的点Message里的内容不一定和业务方日志里的请求体一致——中间可能经过队列端的序列化/压缩封装直接 base64 解码出来还是乱码的情况很常见要先识别压缩算法gzip/snappy/zstd 的 magic number 不同TryCount是队列侧的重试函数内部的业务级重试比如代码里自己写了 for retry不在此列——排查时两本账要分开对ErrorCode: SCF_TIMEOUT的消息可能已经执行了一半业务逻辑写库写了一半才超时这是重放时重复污染的高危来源。三、让腾讯云助手解析从天书到结构化病历我们让腾讯云助手写的第一个工具就是死信日志解析器输入死信消息输出病历卡importbase64,gzip,json,iodefsniff_compression(raw:bytes)-str:ifraw[:2]b\x1f\x8b:returngzipifraw[:1]b\x00:returnsnappyifraw[:4]b\x28\xb5\x2f\xfd:returnzstdreturnplaindefparse_dead_letter(dlq_msg:dict)-dict:rawbase64.b64decode(dlq_msg[Message])algosniff_compression(raw)ifalgogzip:payloadgzip.decompress(raw)elifalgoin(snappy,zstd):raiseNeedExternalLib(algo)# 让 AI 提示需要哪个解压库别静默猜else:payloadraw bodyjson.loads(payload)return{msg_key:dlq_msg[MsgKey],try_count:dlq_msg[TryCount],error_class:classify(dlq_msg[ErrorCode]),retry_window_s:(dlq_msg[DeadLetterTime]-dlq_msg[FirstFailsTime])/1000,business_context:{order_id:body.get(orderId),sku_id:body.get(skuId),event_type:body.get(eventType),occurred_at:body.get(ts),},age_hours:hours_since(dlq_msg[FirstFailsTime]),}defclassify(code:str)-str:ifcode.startswith(SCF_TIMEOUT):returntimeout# 高危可能半执行ifcode.startswith(PAYLOAD):returninvalid# 永久性重放无用returninvoke_error# 业务异常可修可放两个设计决策值得强调决策 1压缩算法用 magic number 嗅探不靠试错。AI 初版写的是try: gzip / except: try snappy / except: ...的连环 try——能跑但出错时你不知道是格式不支持还是数据损坏。嗅探 显式报错让解析失败时的信息量完全不同。决策 2error_class三分类直接决定第三篇重放策略。timeout 类要按可能半执行处理先补偿再重放invalid 类重放一万次也不会成功进人工分析流程invoke_error 类修完 bug 后是重放的主力。四、投递轨迹还原把三个数据源串成一条时间线单条死信的解析只是验尸定位问题需要还原它生前走过的路。三个数据源DLQ 消息本身上文结构——死亡现场SCF 函数日志CLS——按MsgKey检索该消息每次触发的执行日志业务库的操作流水——消息处理到哪一步。让腾讯云助手做时间线拼接提示词要点你是分布式系统故障分析助手。给定三个数据源 A. 死信消息病历卡结构化 B. 该 MsgKey 在 CLS 中的全部函数日志含每次重试 C. 业务库中该订单的操作流水脱敏 任务还原这条消息的完整生命周期时间线 1. 消息产生 → 每次投递 → 每次失败 → 进入 DLQ逐条带时间戳 2. 标注每次失败发生在函数执行的第几毫秒、卡在哪个业务步骤以日志为准 3. 明确指出最后一次执行中已完成的写操作对重放至关重要 4. 输出结论三选一永久性失败schema/数据问题/ 瞬时性失败下游抖动/ 代码缺陷可复现。 硬约束 - 时间线只基于给定日志禁止推测未出现的时间段发生了什么 - 已完成的写操作必须引用具体日志行不确定就标 UNKNOWN。一条真实死信还原出来的时间线脱敏简化14:20:00.128 消息产生order-fulfillment topic, ORD-...-0001 14:20:00.512 第 1 次投递 → 函数启动 14:20:12.903 日志库存预占成功DB 写入 #1 14:20:30.001 函数超时被杀30s未走到发货单创建步骤 14:23:00.311 第 2 次投递重试 14:23:12.500 日志库存预占再次执行DB 写入 #2 ← 重复 14:23:30.002 再次超时 14:26:00.xxx 第 3 次投递 → 同样模式 → 进入 DLQ这条时间线一眼暴露两个问题库存预占逻辑不幂等重试导致重复预占下游发货接口偶发慢每次都卡在 12 秒后。前者是第二篇的主题业务缺陷定位后者是容量问题。而已完成的写操作 3 次库存预占这个事实直接决定了第三篇重放脚本必须先做补偿再重放。五、本篇小结死信三大来源里TIMEOUT 类最危险——函数可能半执行重放前必须搞清它写过什么死信消息体要经过 base64 压缩双层解码压缩算法用 magic number 嗅探而非连环 tryerror_class三分类timeout / invalid / invoke_error直接决定重放策略解析时就要分好投递轨迹还原要把 DLQ、函数日志、业务流水三个源串成时间线AI 提示词的硬约束是禁止推测 不确定的写操作标 UNKNOWN。下一篇用 4 个真实死信案例讲业务缺陷定位非幂等写、异常吞噬、消息 schema 漂移、下游限流雪崩——每个案例都从死信日志的特征字段讲起。做事件驱动架构的同学欢迎评论区交流你们的 DLQ 积压现状。
返回列表