为何Nack消息未进入Google Pub/Sub模拟器的死信主题?
问题背景
我希望配置Google Pub/Sub模拟器的死信队列主题,当消息在处理器中触发异常时,消息会转发至死信队列主题,之后我可以订阅该主题拉取死信消息。我使用spring-cloud-gcp-starter-pubsub实现生产者和消费者,通过以下Shell命令启动并配置模拟器:
gcloud beta emulators pubsub start --project=local-test --host-port=0.0.0.0:8080
模拟器启动完成后,执行以下命令创建主题和订阅:
curl -X PUT -v http://localhost:8080/v1/projects/local-test/topics/fam-task curl -X PUT -v http://localhost:8080/v1/projects/local-test/topics/dead-letter curl -X PUT 'http://localhost:8080/v1/projects/local-test/subscriptions/subscription-task-reactor-fam-task' \ --header 'Content-Type: application/json' --data '{"topic": "projects/local-test/topics/fam-task", "enableMessageOrdering": true, "enableExactlyOnceDelivery": true, "deadLetterPolicy": {"deadLetterTopic": "projects/local-test/topics/dead-letter", "maxDeliveryAttempts": 5}}' echo "Create dead letter subscriptions" curl -X PUT 'http://localhost:8080/v1/projects/local-test/subscriptions/subscription-dead-letter' \ --header 'Content-Type: application/json' --data '{"topic": "projects/local-test/topics/dead-letter", "enableMessageOrdering": true, "enableExactlyOnceDelivery": true}'
已验证生产者和消费者连接正常,可正常收发消息。为验证死信队列功能,我让消息接收方法抛出RuntimeException,应用日志显示:
PubSubInboundChannelAdapter - Sending Spring message [4] failed; message nacked automatically.
该日志重复出现5次,与订阅subscription-task-reactor-fam-task设置的maxDeliveryAttempts=5预期一致。但通过以下请求轮询死信订阅时:
POST http://localhost:8080/v1/projects/local-test/subscriptions/subscription-dead-letter:pull
结果超时且无消息返回。已确认订阅、主题均存在,死信主题在订阅创建时已指定,死信主题的订阅可正常轮询,但Nack后的消息并未进入死信主题。看到其他问题提到需确保服务账号拥有订阅者和发布者角色,但不确定这在模拟器中是否适用及如何配置,请问哪里操作有误?
问题原因及解决方案
1. 核心问题:Exactly-Once Delivery与模拟器的兼容性
Google Pub/Sub模拟器不支持Exactly-Once Delivery特性,而你在创建订阅时同时启用了enableExactlyOnceDelivery: true和死信策略。这两个特性组合会导致死信队列功能失效——模拟器无法在Exactly-Once模式下处理消息的死信转发逻辑。
2. 修复步骤
- 先删除已创建的订阅:
curl -X DELETE 'http://localhost:8080/v1/projects/local-test/subscriptions/subscription-task-reactor-fam-task' curl -X DELETE 'http://localhost:8080/v1/projects/local-test/subscriptions/subscription-dead-letter' - 重新创建订阅,移除
enableExactlyOnceDelivery配置项:# 重新创建主订阅,保留消息排序和死信策略 curl -X PUT 'http://localhost:8080/v1/projects/local-test/subscriptions/subscription-task-reactor-fam-task' \ --header 'Content-Type: application/json' --data '{"topic": "projects/local-test/topics/fam-task", "enableMessageOrdering": true, "deadLetterPolicy": {"deadLetterTopic": "projects/local-test/topics/dead-letter", "maxDeliveryAttempts": 5}}' # 重新创建死信订阅 curl -X PUT 'http://localhost:8080/v1/projects/local-test/subscriptions/subscription-dead-letter' \ --header 'Content-Type: application/json' --data '{"topic": "projects/local-test/topics/dead-letter", "enableMessageOrdering": true}'
3. 关于服务账号权限的说明
Pub/Sub模拟器完全在本地运行,不需要配置服务账号权限——所有操作默认拥有最高权限,因此权限问题不是导致此次死信队列失效的原因。
4. 验证方法
重新发送消息并触发异常,待消息被Nack 5次后,使用带参数的Pull请求查询死信订阅:
curl -X POST 'http://localhost:8080/v1/projects/local-test/subscriptions/subscription-dead-letter:pull' \ --header 'Content-Type: application/json' --data '{"maxMessages": 10, "returnImmediately": true}'
此时应该能收到转发到死信队列的消息。
内容的提问来源于stack exchange,提问作者Steve Gazzo

