127, 128 섹션 관련 문의드립니다. 2개의 오더 마이크로서비스 각각에 연결된 데이터베이스로 인한 동기화 문제를 위해 카프카 커넥터를 활용하여 하나의 단일 디비로 문제를 처리한다고 하셨는데, 결국 2개의 오더 마이크로서비스에 카프카 커넥터를 사용하지 않고 동일한 디비 1개를 직접 연결해서 사용하면 동기화 문제가 발생하지 않는건 마찬가지아닌가요? 동일한 오더 마이크로서비스를 스케일아웃 하는 상황에서 카프카 커넥터를 사용하는게 목적에 맞는지 의아해서 질문드립니다.
안녕하십니까 코드빌런님. 이번 추석 연휴동안 레빗 엠큐 강의 잘 들었습니다. 다름이 아니라 강의에서 알려주신 여러 내용을 바탕으로 기존에 구현하였던 redis pub/sub 기반의 알림 기능에 레빗 엠큐를 적용해보고 있습니다. 그리고 구현 중 두가지 질문이 있어 질문을 작성하게 되었습니다. 1. 동적 큐 이름 설정 방식 우선 알림을 전송하는 과정에 대해 말씀드리면 다음과 같습니다. 알림 객체 저장 성공 시 알림 발송 sse 연결들을 ConcurrentMap으로 관리하여 대상 userId의 sse연결을 찾아 해당 연결로 알림 객체 전송 현재 메시지 큐 구조는 다음과 같습니다. 알림 저장 메시지 생성 (direct exchange, saveNotificationQueue) -> 메시지 저장 성공 시 알림 전달 메시지 생성 (fanout exchange, publishNotificationQueue), 메시지 저장 실패 시 데드레터 큐로 전달 현재 서비스는 3개의 인스턴스로 동작하고 있습니다. 이때 알림 저장 큐는 1개라서 복수 저장될 일이 없지만, 그 후에 진행되는 알림 전달의 경우 단일 큐로 작동하면 대상 sse 연결이 없는 인스턴스에서 해당 메시지를 소비하게 되면 전송이 실패합니다. 그래서 각 인스턴스마다 큐를 만들어주고 fanout exchange에 모두 바인딩하여 사용하는 방식으로 만들어야 할 것 같다고 생각하였습니다. 그래서 찾아보니 SpEL 기반 동적 큐 이름 지정 방식이 있다고 하여 해당 방식으로 구현해보았습니다. // RabbitMQConfig.java // 알림 발송 큐 @Bean public String dynamicPublishNotificationQueueName() { String randomString = UUID.randomUUID().toString(); return PUBLISH_NOTIFICATION_QUEUE + " : " + randomString; } @Bean public Queue publishNotificationQueue() { return new Queue(dynamicPublishNotificationQueueName(), false); } @Bean public FanoutExchange publishNotificationExchange() { return new FanoutExchange(PUBLISH_NOTIFICATION_EXCHANGE); } @Bean public Binding publishNotificationBinding() { return BindingBuilder.bind(publishNotificationQueue()).to(publishNotificationExchange()); } // NotificationSubscriber.java @RabbitListener(queues = "#{@dynamicPublishNotificationQueueName}") public void consumePublishNotificationMessage(Notification notification) { notificationService.publishNotification(notification); } 해당 방식으로 정상 작동은 확인하였는데, 혹시 해당 방식 외에 더 나은 방식이 있는지 궁금합니다. 2. SimpleRabbitListenerContainerFactory의 재정의에 따른 Retry 설정 미적용 강의 18강에서 application.yml에 retry 관련 프로퍼티를 설정하는 것만으로 자동으로 retry가 적용된다고 하여 해당 방식을 프로젝트에 적용해보았습니다. spring.rabbitmq.listener.simple.retry.enabled=true spring.rabbitmq.listener.simple.retry.initial-interval=1000 spring.rabbitmq.listener.simple.retry.max-attempts=3 spring.rabbitmq.listener.simple.retry.max-interval=1000 spring.rabbitmq.listener.simple.default-requeue-rejected=false 하지만 어떤 이유인지는 몰라도 retry가 작동하지 않았습니다. 실제로 실행되는 코드에 로그를 찍어봐도 한번만 시도하고 설정한 예외가 발생 후 바로 DLQ로 이동하였습니다. 그래서 원인을 찾던 도중 https://inf.run/bsxxr 에서 @RabbitListener를 사용하면 내부적으로 SimpleMessageListenerContainer 가 자동으로 생성되기 때문에 retry 설정을 읽어서 exception 이 발생할 경우 RetryTemplate을 사용해서 자동으로 설정된 속성에 해당하는 작업을 수행하게 됩니다. 라고 코드빌런님이 말씀하신 것을 보았습니다. 확인해보니 메시지큐에서 객체 자동 역직렬화를 위해 @Bean public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setMessageConverter(messageConverter()); return factory; } 이렇게 SimpleRabbitListenerContainerFactory를 정의하여 빈으로 등록해놓았는데, SimpleRabbitListenerContainerFactory를 살펴보니 public class SimpleRabbitListenerContainerFactory extends AbstractRabbitListenerContainerFactory<SimpleMessageListenerContainer> { ... 말씀하신 SimpleMessageListenerContainer 를 타입파라미터로 받아 상속받고 있는 형태였습니다. 이에 말씀하신 SimpleMessageListenerContainer 가 자동으로 생성되어 retry 설정이 적용안되는것인가? 라고 예상하여 application.properties에 정의하는 대신 @Bean public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setMessageConverter(messageConverter()); factory.setDefaultRequeueRejected(false); factory.setAdviceChain(RetryInterceptorBuilder.stateless() .maxAttempts(3) .backOffOptions(1000, 2.0, 10000) .build()); return factory; } 이렇게 직접 retry 설정을 넣어주니 그제야 재시도가 정상적으로 작동하였습니다. 해당 원인이 제가 생각한 직접 팩토리를 Bean으로 등록하면 application.properties 의 retry 설정이 무시되는 것이 맞는지 궁금합니다. 코드는 아래 url에서 보실 수 있습니다. https://github.com/Dockerel/4th-SC-TEAM1-BE/pull/15/files 강의 정말 잘 들었습니다! 이렇게 프로젝트에 바로 적용해볼 수 있어서 기분이 좋네요. 나중에 코드빌런님의 다른 기술 스택 강의도 들어보고 싶습니다. 감사합니다.
안녕하게요 강의를 보고 있는데 Kafka Connect 이 부분이 기존의 zookeeper 에서 kraft 로 버전이 변경되서 지원을 안 하는데 혹시 다른 방법이 있을까 ? 싶어 질문 올립니다. 참고로 블로그 도 여러개 봤는데 계속 에러가 걸려서 질문 올립니다. 정확히 kafka Connect 설치 를 하고 이거를 했을때 C:\Work\confluent-5.5.2>bin\windows\connect-distributed.bat etc\kafka\ connect-distributed.properties Classpath is empty. Please build the project first e.g. by running 'gradlew jarAll' C:\Work\confluent-5.5.2> 윈도우이고 이런 에러가 계속 뜨는데 해결이 안됩니다.
학습중 궁금한 것은 언제든 문의 하세요. 질문을 최대한 자세히 남겨주시면 반드시 답변 드리도록 하겠습니다. 추가로 알고 싶은 내용도 요청해주시면 강의 자료를 업데이트 해서 제공할 예정입니다. application.yml spring: application: name: HelloMessageQueue rabbitmq: host: localhost port: 5672 username: admin password: admin server: port: 8080 docker-compose.yml services: rabbitmq: image: rabbitmq:3-management container_name: mq ports: - "5672:5672" - "15672:15672" environment: RABBITMQ_ERLANG_COOKIE: rabbitmqCookie RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: admin 이렇게 해서 실행하면 2025-08-08T11:17:15.145+09:00 ERROR 11684 --- [HelloMessageQueue] [ restartedMain] o.s.boot.SpringApplication : Application run failed org.springframework.context.ApplicationContextException: Failed to start bean 'container' at org.springframework.context.support .DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:408) ~[spring-context-6.2.9.jar:6.2.9] at org.springframework.context.support .DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:394) ~[spring-context-6.2.9.jar:6.2.9] at org.springframework.context.support .DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:586) ~[spring-context-6.2.9.jar:6.2.9] at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] at org.springframework.context.support .DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:364) ~[spring-context-6.2.9.jar:6.2.9] at org.springframework.context.support .DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:310) ~[spring-context-6.2.9.jar:6.2.9] at org.springframework.context.support .AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:1006) ~[spring-context-6.2.9.jar:6.2.9] at org.springframework.context.support .AbstractApplicationContext.refresh(AbstractApplicationContext.java:630) ~[spring-context-6.2.9.jar:6.2.9] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.5.4.jar:3.5.4] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.5.4.jar:3.5.4] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.4.jar:3.5.4] at org.springframework.boot.SpringApplication.run (SpringApplication.java:318) ~[spring-boot-3.5.4.jar:3.5.4] at org.springframework.boot.SpringApplication.run (SpringApplication.java:1361) ~[spring-boot-3.5.4.jar:3.5.4] at org.springframework.boot.SpringApplication.run (SpringApplication.java:1350) ~[spring-boot-3.5.4.jar:3.5.4] at com.study .rabbitmq.RabbitmqApplication.main(RabbitmqApplication.java:10) ~[main/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run (RestartLauncher.java:50) ~[spring-boot-devtools-3.5.4.jar:3.5.4] Caused by: org.springframework.amqp.AmqpIllegalStateException: Fatal exception on listener startup at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.waitForConsumersToStart(SimpleMessageListenerContainer.java:643) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doStart(SimpleMessageListenerContainer.java:603) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.start(AbstractMessageListenerContainer.java:1417) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.context.support .DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:405) ~[spring-context-6.2.9.jar:6.2.9] ... 19 common frames omitted Caused by: org.springframework.amqp.rabbit.listener.exception.FatalListenerStartupException: Authentication failure at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:631) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.initialize(SimpleMessageListenerContainer.java:1482) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$ AsyncMessageProcessingConsumer.run (SimpleMessageListenerContainer.java:1322) ~[spring-rabbit-3.2.6.jar:3.2.6] at java.base/ java.lang.Thread.run (Thread.java:842) ~[na:na] Caused by: org.springframework.amqp.AmqpAuthenticationException: com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile. at org.springframework.amqp.rabbit.support .RabbitExceptionTranslator.convertRabbitAccessException(RabbitExceptionTranslator.java:64) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:632) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.createConnection(CachingConnectionFactory.java:726) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.createConnection(ConnectionFactoryUtils.java:257) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils$RabbitResourceFactory.createConnection(ConnectionFactoryUtils.java:345) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.doGetTransactionalResourceHolder(ConnectionFactoryUtils.java:140) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactoryUtils.java:102) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactoryUtils.java:85) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:625) ~[spring-rabbit-3.2.6.jar:3.2.6] ... 3 common frames omitted Caused by: com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile. at com.rabbitmq.client.impl.AMQConnection.start(AMQConnection.java:395) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:1251) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:1198) ~[amqp-client-5.25.0.jar:5.25.0] at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.connectAddresses(AbstractConnectionFactory.java:678) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.connect(AbstractConnectionFactory.java:647) ~[spring-rabbit-3.2.6.jar:3.2.6] at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:593) ~[spring-rabbit-3.2.6.jar:3.2.6] ... 10 common frames omitted 이 에러가 발생합니다 계정도 있는데 인증이 안되는 이유를 모르겠습니다
안녕하세요. 트랜잭션 메시징에 대해 궁금한 것이 생겨 질문드립니다. 트랜잭션 메시징은 데이터베이스 작업과 메시지 발행 작업을 원자적으로 수행하여 데이터의 일관성을 보장하는 것으로 알고 있습니다. 강의 내용 중 '트랜잭션 메시징의 한계'에서 분산 트랜잭션을 완벽하게 보장하지는 않는다고 하셔서 이를 DB의 트랜잭션과 메시지 전송의 트랜잭션이 서로 독립적으로 작동한다고 이해했습니다. 그럼 RabbitMQ에서 트랜잭션 메시징을 지원한다고 할 수 있나요?
인사말 안녕하세요. 질 높은 강의를 제공해주셔서 너무 감사하다는 말씀드립니다. 아직 강의를 모두 본 것은 아니어서, 제가 질문드리고자 하는 내용이 강의에 포함이 되어있을지 모르겠습니다만.. 궁금한 내용을 질문드립니다. 질문 많은 모놀리식 프로젝트에서는 Spring Web + Spring Data JPA를 의존성을 주입하고 개발을 하고 저 또한 이러한 방식의 개발을 경험해왔습니다. Spring Data JPA와 Spring Web은 동기방식으로 동작하게 되는데, MSA의 독립 모듈을 만들 때 해당 의존성들을 사용하는 것이 바람직한지 모르겠습니다. 강의의 내용처럼 하나의 서비스는 독립적으로 구성된 수십개에서 수백개의 독립된 애플리케이션으로 동작을 하게 될 것인데요. 상황에 따라 다르기는 하겠지만 Spring Web과 Spring Data JPA를 사용하는 독립된 애플리케이션이 있게 된다면 이쪽에서 병목이 일어나 엄청난 성능 이슈가 발생할 것으로 생각이듭니다. 이와 같은 고민을 갖고 GPT한테 물어보니 WebFlux와 R2DBC를 사용하여 모두 비동기적으로 만드는 것이 권장되는 사항이라고 하는데 이 답변이 맞을까요? MSA를 구성한다고 한들, 모놀리식보다 성능상 좋지가 않으면 MSA로 구축할 필요성이 없을 것인데, 제가 우려하는 성능상의 심각한 문제 / 병목현상을 예방하기 위한 설계를 어떻게 해야하는지 너무 궁금합니다. 추가적으로는 Speing web이나 Spring Data Jpa가 MSA에거 어느 점에 필요하게 됭까요? 성능 관점에서 도무지 필요성을 느끼지 못하고있습니다. 질문이 너무 두서가 없는데... 질문 내용이 강사님께 잘 전달되었길 바랍니다..!
spring_cloud_gateway_requests_seconds_count{ httpMethod ="GET", httpStatusCode ="401", instance ="localhost:8000", job ="apigateway-service", outcome ="CLIENT_ERROR", routeId ="user-service", routeUri ="lb://USER-SERVICE", status ="UNAUTHORIZED"} 22 spring_cloud_gateway_requests_seconds_count{ httpMethod ="GET", httpStatusCode ="404", instance ="localhost:8000", job ="apigateway-service", outcome ="CLIENT_ERROR", routeId ="order-service", routeUri ="lb://ORDER-SERVICE", status ="NOT_FOUND"} 회원가입과 로그인만 하더라도 이러한 것들의 숫자가 올라가 총 요청 수 중 성공한 숫자가 매우 적습니다. chat gpt에서는 자동으로 health-check를 해서 그렇다라고는 하는데 정확히 어떤 것이 문제인지 잘 모르겠습니다.
java.sql.SQLException: No suitable driver found for jdbc:h2:mem:testdb java.sql.SQLException: Access denied for user 'root'@' localhost ' (using password: YES) 현재 kafak connect 연결시 이런 오류가 뜹니다. order-service를 mariadb Driver로 설정했는데 왜 h2메모리방식의 드라이버를 찾는지 이유를 잘 모르겠으며 해결방법이 절실합니다. Kafka의 zookeeper, Kafka server는 현재 작동이 잘됩니다. 윈도우 사용자입니다. 도움이 절실합니다.
현재 zookeeper, kafka server는 다 실행되고 topic list도 잘 나오는 상황입니다. 하지만 현재 Caused by: java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/mydb 이 에러 때문에 진도를 못나가고 있는 상황입니다. 강의 그대로 했음에도 불구하고 어떤것이 문제인지 모르겠습니다. 제발 도와주세요
기존에는 http://localhost:8081 과같이 보내줬는데, 이걸 lb:/MY-FIRST-SERVICE 로 바꿨습니다. 그렇다면, 원래 localhost:8081로 직접 http 프로토콜로 보내줬던건, 유레카를 거치지 않았고, 하드코딩으로 직접 보내줬던 것이고, 그리고 lb:/를 이용하는 것이 유레카에 저장되어 있는, 정보들을 통해 읽어와서 동적으로 보내주는 것으로 이해하였는데, 제가 이해한게 맞을까요?
학습중 궁금한 것은 언제든 문의 하세요. 질문을 최대한 자세히 남겨주시면 반드시 답변 드리도록 하겠습니다. 추가로 알고 싶은 내용도 요청해주시면 강의 자료를 업데이트 해서 제공할 예정입니다. 저만 궁금한 걸 수 있는데, 예를들어 Producer가 두개일 때 이 경우는 스프링 애플리케이션의 publish하는 두개의 스레드를 의미하는 건지... 애플리케이션 두 개를 의미하는 건지... 아니면 커넥션 두개를 의미하는 건지... 무엇을 기준으로 Producer/Consumer 단위를 세는건지 아시나요 ?