현재 회사에서 spring-kafka 를 이용해서 특정 서비스들의 푸시 메세지 이벤트를 받아서
전송해주는 서버를 구현하고 있다. (오늘 쿠버네티스에 배포까지 했다!! 모르는게 너무많은...)
로컬에서 내 맘대로 메세지 토픽을 발행해서 쏴도 잘 맞게 역직렬화를 수행을 해주길래 그냥 그런가보다.
하고 잘 넘어갔던 찰나에!!!
Class Not Found Exception
왜? 이 에러가 났을까?
일단 나는 구독하는쪽 그러니까 Kafka에서는 Consumer 쪽 만을 구현해주었다.
내 지식이 부족했던 탓인지는 모르겠지만, 어쨌든 같은 JSON 형태라고 생각해서 클래스가 무엇이던 간에
JSON형식만 같다면 Consume해도 괜찮을 것이라고 처음 생각했었다.
그래서 발행 모델인 Producer쪽에서는 예를 들면 MessageReq
로 Producer가 보내고
받은 부분인 Consumer에서는 PushReq
라고 받는다고 하고 데이터는 둘다 똑같은 형식으로 매칭이 되어있다고 가정한다.
이랬을 때 Consumer 서버를 키면???!!!
바로 Class Not Found Exception이 떠버린다.
Caused by: org.springframework.messaging.converter.MessageConversionException: failed to resolve class name. Class not found [com.github.lsj8367.message.PushReq]; nested exception is java.lang.ClassNotFoundException: com.github.lsj8367.message.MessageReq
at org.springframework.kafka.support.converter.DefaultJackson2JavaTypeMapper.getClassIdType(DefaultJackson2JavaTypeMapper.java:138)
at org.springframework.kafka.support.converter.DefaultJackson2JavaTypeMapper.toJavaType(DefaultJackson2JavaTypeMapper.java:99)
at org.springframework.kafka.support.serializer.JsonDeserializer.deserialize(JsonDeserializer.java:342)
at org.apache.kafka.clients.consumer.internals.Fetcher.parseRecord(Fetcher.java:1030)
at org.apache.kafka.clients.consumer.internals.Fetcher.access$3300(Fetcher.java:110)
at org.apache.kafka.clients.consumer.internals.Fetcher$PartitionRecords.fetchRecords(Fetcher.java:1250)
at org.apache.kafka.clients.consumer.internals.Fetcher$PartitionRecords.access$1400(Fetcher.java:1099)
at org.apache.kafka.clients.consumer.internals.Fetcher.fetchRecords(Fetcher.java:545)
at org.apache.kafka.clients.consumer.internals.Fetcher.fetchedRecords(Fetcher.java:506)
at org.apache.kafka.clients.consumer.KafkaConsumer.pollForFetches(KafkaConsumer.java:1269)
at org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1200)
at org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1176)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:741)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:698)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:748)
예제코드는 깃허브에 있지만, producer와 consumer는 각기 다른 서버로 만들어서 진행시켜주어야 한다!! 안그러면 에러 안나고 받지도 않는다.
왜? Consumer쪽엔 직렬화한 MessageReq
클래스가 없기 때문이다.
그래서 보내는 쪽에서 미리 받는 Consumer쪽의 객체 형식을 Header에 바인딩 해주거나,
받는 입장에서 Header가 아닌 Method로 받게 설정해줄 수 있다.
해결책
그래서 해결책이 무엇이냐!
위에서 잠깐 말했지만 producer에서 header를 설정해주거나 consumer에서 설정을 해주는 방법이 있다고 했다.
물론 서로 정의가 잘 되어있고, 동시에 구현했다면 나는 전자의 방법을 택했을 것이다.
그렇지만, 각자 개발하는 속도가 있었고, 또 다른 작업들을 계속해서 진행해야 하고 이미 돌아가고 있던 배치서버에서 그걸 바꿔서
다시 배포하기엔 쉽지 않았었다. (사실 말씀드리기도 좀 그랬다.)
디버깅
그래서
맨끝부분에 false를 넣어주었는데, 이는 들어가보면...
생성자를 오버로딩하기에 쭉 들어오니 이 생성자가 나온다.
initialize에서 해당 boolean값을 사용하고 있기 때문에
boolean값에 의해서 어떤 타입으로 결정하는지가 나온다.
그 설명은
위에서 앞서 설명했던 것과 똑같은 설명이 자바독으로 쓰여져있다.
공부해야될거 참 많다...
정리
그래도 내가 혼자 힘으로 이 메세지 서버를 만들면서 정말 재밌게 개발했다.
물론 오늘 배포하는데 쿠버네티스 지식이 없어서 애를 먹었다. ~~ㅋㅋㅋㅋㅋㅋ~~
그래도 백엔드 개발자분들이 도와주셔서 꿀 플러그인도 전수받고 기본 개념을 정리해가는 것 같다.
뭐하나 건들면 바로 공부해야되는게 너무 어렵지만 재밌어서 좋다.
조만간 카프카 기본개념과 쿠버네티스도 정리해야겠다 🔥🔥🔥🔥🔥
'Spring > Kafka' 카테고리의 다른 글
Kafka Offset Commit의 중요성 (0) | 2022.10.06 |
---|---|
Spring Kafka 좀 더 공통 설정하기 (0) | 2022.10.06 |