背景
公司项目采用微服务异构开发,java开发了一个消费模块,通过消费kafka
进行业务处理。
- java开发的业务系统,能被正常消费。
- go开发的业务系统,发送消息正常,但是消费的时候一直报错。
排查问题
因为排查问题时没有截图,这里就简单描述下。
查资料发现,应该是反序列化失败触发了kafka
的毒丸。 然后看spring-kafka
的代码,找到了问题。在这里总结一下。
- 反序列化消息抛出异常
- 触发毒丸
补充一下,为什么java业务系统可以正常发消息。
因为消费模块
dto
信息拆分出来给业务模块使用,所以spring-kafka
在发消息时会自动把这些信息写进去,并且不会出现dto
类中字段不对应的问题。也就不会反序列化失败了。
序列化问题
消费模块是spring-boot开发的,用了spring-kafka
,消息入口是这样的
@KafkaListener(topics = {"test"})
public void testListener(ConsumerRecord<String, TestDto> msg) {
log.info("testListener 收到新的推送 : {}", msg);
.....
}
复制代码
这里就发现了,spring-kafka
会帮我们把消息内容转换为TestDto
,但是它转换失败了。然后我跟着源码看了一下,发现它会找一个请求头。这就好办了, 把"__TypeId__":"xxxx.xxxx.TestDto"
这样的一个键值对放到请求头里。__TypeId__
是key,xxxx.xxx.TestDao
就是java代码中的那个类的包位置了。
然后看一下go的代码,发kafka消息用的是sarama
库、
// 主要就是这块,在构建消息的时候,将请求头放进去。
message := sarama.ProducerMessage{
Topic: "test",
// marshal 是要发送的内容,是 marshal, err := json.Marshal(map[string]interface{}{"test":1})
Value: sarama.ByteEncoder(marshal),
// __TypeId__ : yh.ci.message.dto.kafka.entity.message.AliSmsDto
// 消费者反序列化时需要指定实体类。
Headers: []sarama.RecordHeader{
{
Key: []byte("__TypeId__"),
Value: []byte("xxx.xxx.TestDto"),
},
},
}
// NewProducer() 用来配置kafka地址啥的,就不展示了
client, err := NewProducer()
复制代码
大概就是这样,要让请求头的包和java代码中实际位置对应上。
毒丸
毒丸的配置我大概是参照这篇文章,感谢作者。 www.jdon.com/54521
简单列一下,
- 修改yml配置,
- 配置
errorHandler
bean
yml
spring:
kafka:
consumer:
bootstrap-servers: xxxx:9092
# 是否自动提交offset
enable-auto-commit: true
# 当kafka中没有初始offset或offset超出范围时将自动重置offset
# earliest:重置为分区中最小的offset;
# latest:重置为分区中最新的offset(消费分区中新产生的数据);
# none:只要有一个分区不存在已提交的offset,就抛出异常;
auto-offset-reset: latest
#毒丸配置
key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
properties:
# 默认的消费组ID
group:
id: defaultConsumerGroup
"spring.json.trusted.packages": "*"
spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
listener:
missing-topics-fatal: false
复制代码
配置errorHandler
@Configuration
@EnableKafka
public class KafkaConfiguration {
/**
* 毒丸
* https://www.jdon.com/54521
*
* Boot will autowire this into the container factory.
*/
@Bean
public LoggingErrorHandler errorHandler() {
return new LoggingErrorHandler();
}
}
复制代码
这样,如果在再遇到反序化之类的异常,会打印异常信息,然后跳过这条消息。当然你也可以考虑私信队列之类的方法。