Quarkus对接Azure Event Hubs运行数天停服问题咨询
该故障属于典型的云托管消息服务与开源客户端默认参数不匹配+客户端版本已知bug组合导致的静默连接失效问题,和原生Kafka集群的运行环境差异是核心诱因,以下按优先级给出排查和修复路径:
1. 核心根因匹配验证
你遇到的现象完全符合SmallRye Reactive Messaging Kafka Connector 3.8.0之前版本的已知缺陷:
当生产者侧连接被Azure Event Hubs网关、K8s conntrack链路、SLB等中间节点*静默断开(无FIN/RST包返回)*时,若断开瞬间没有正在处理的消息发送请求,客户端网络线程会卡在Socket等待状态,不会触发连接状态更新和自动重连逻辑,Channel会被永久标记为KO状态,仅重启应用可恢复。
这也能解释为什么你手动禁用/删除Event Hubs资源无法复现:主动操作服务端资源时,连接处于活跃状态,客户端能收到服务端返回的错误帧,会打印明确错误日志并触发重连,和空闲超时导致的静默断连场景完全不同。
Azure Event Hubs默认会强制断开空闲超过240分钟的连接,多数企业K8s集群的conntrack表项空闲超时为2-5天,两者的超时差刚好匹配你观察到的4-5天故障周期。
2. 第一优先级修复配置
直接在application.properties中添加以下配置,无需改业务代码:
# 生产者/消费者开启TCP Keepalive,自动刷新中间链路conntrack表项,适配Azure SLB、K8s网络的空闲超时 kafka.producer.tcp.keepalive.enable=true kafka.consumer.tcp.keepalive.enable=true # 客户端主动回收空闲连接,周期低于Azure侧4小时空闲阈值,避免被服务端静默断开 kafka.producer.connections.max.idle.ms=180000 kafka.consumer.connections.max.idle.ms=180000 # 开启生产者连接定期健康检测,每分钟校验一次连接状态,断连时自动触发重连 mp.messaging.connector.smallrye-kafka.producer.connection.check.interval.ms=60000 # 配置发送侧重试策略,避免单次网络波动导致Channel标记为永久失败 mp.messaging.outgoing.event-hub-2.retries=5 mp.messaging.outgoing.event-hub-2.retry.initial-interval=1000 mp.messaging.outgoing.event-hub-2.retry.max-interval=10000 # 开启连接错误自动重连开关 mp.messaging.connector.smallrye-kafka.producer.reconnect-on-error=true
3. 版本修复
如果当前使用的Quarkus版本低于3.2.0(对应SmallRye Kafka Connector版本低于3.8.0),直接升级Quarkus到3.2.LTS及以上版本,该版本已经合入了静默断连场景下的Channel自动重连修复,不会再出现连接卡死不恢复的问题。
4. 故障现场验证方法
不需要等测试环境复现,下次生产触发故障时,直接进故障Pod执行以下操作确认根因:
- 执行
netstat -anp | grep 9093查看Event Hubs对应连接状态,如果状态显示为ESTABLISHED但实际无法发送消息,即可确认是静默断连问题 - 执行
jstack <应用PID>打印线程栈,若看到Kafka生产者网络线程卡在java.net.SocketInputStream.socketRead0方法上,即可确认是客户端线程卡死的已知bug - 提前配置以下日志参数,故障发生时可直接看到连接断开的完整链路:
quarkus.log.category."org.apache.kafka.common.network".level=DEBUG quarkus.log.category."io.smallrye.reactive.messaging.kafka".level=INFO
5. 临时兜底方案
在完成配置修改和版本升级前,可调整K8s就绪探针的失败阈值,连续3次健康检查失败时自动重启Pod,避免业务长时间中断:
readinessProbe: httpGet: path: /q/health/ready port: 8080 initialDelaySeconds: 20 periodSeconds: 10 failureThreshold: 3
内容的提问来源于stack exchange,提问作者Logan

