묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨카프카 완벽 가이드 - 코어편
브로커가 추가될 때 파티션 재분배
안녕하세요 선생님! 완강 후에 정리하며 이것저것 테스트를 하는 와중에 궁금한게 생겨 질문드립니다. 이미 특정 토픽의 파티션이 브로커들에게 분배된 상태에서, 새로운 브로커가 추가됐을 때 새로운 브로커는 특정 토픽의 파티션을 가질 수 있는 대상으로 선정되지 않는 것 같습니다.새로운 브로커가 추가 됐을 때 새 브로커에도 기존의 토픽의 파티션 재분배를 하는 방법이 있나요?불가능 하다면, 이런 모델을 가지는 이유가 있을까요? 테스트 과정 공유드립니다.broker #1, #2 총 2개 띄운 상태에서 partition 3개, replication 2개의 토픽 생성 (topic-p3r2)Topic: topic-p3r2 Partition: 0 Leader: 2 Replicas: 2,1 Isr: 2,1 Offline: Topic: topic-p3r2 Partition: 1 Leader: 1 Replicas: 1,2 Isr: 1,2 Offline: Topic: topic-p3r2 Partition: 2 Leader: 2 Replicas: 2,1 Isr: 2,1 Offline:broker #3 추가 후 topic-p3r2 토픽 상태Topic: topic-p3r2 Partition: 0 Leader: 2 Replicas: 2,1 Isr: 2,1 Offline: Topic: topic-p3r2 Partition: 1 Leader: 1 Replicas: 1,2 Isr: 1,2 Offline: Topic: topic-p3r2 Partition: 2 Leader: 2 Replicas: 2,1 Isr: 2,1 Offline:브로커#3은 후보에도 오르지 않았습니다. 제가 예상했던 건 아래와 같이 브로커#3이 추가되었을 때 브로커#3도 토픽의 파티션을 갖는 것이었습니다.(아래 로그는 제가 상상한 것을 임의로 만든 것입니다)Topic: topic-p3r2 Partition: 0 Leader: 2 Replicas: 2,1 Isr: 2,1 Offline: Topic: topic-p3r2 Partition: 1 Leader: 1 Replicas: 1,2 Isr: 1,2 Offline: Topic: topic-p3r2 Partition: 2 Leader: 3 Replicas: 3,1 Isr: 3,1 Offline: 감사합니다!!
-
해결됨카프카 완벽 가이드 - 코어편
토픽에 데이터가 없을 때 offset이 0이 되는 현상 문의
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 선생님 안녕하십니까!강의를 열심히 듣고 있는 수강생입니다. 다름이아니라, 토픽에 데이터가 없을 때 offset이 0이 된다는 로그는 어디서부터 기인하는 것인지 궁금하여 질문 드립니다. 제가 이해하기로 AUTO_COMMIT false가 되어있으면 명시적으로 commit을 호출하지 않으면 commit이 되지 않는 것으로 알고있습니다 (테스트도 해보았습니다.) 하지만, 강의 6:00경 시나리오.즉, 토픽에 아무런 데이터가 없는 상태에서 컨슈머만 켠 케이스에서 저는 커밋(commitSync)을 하지 않았는데 poll을 몇번 돌다보면 offset이 0이 되었다는 로그가 발생합니다.(실제로 __consumer_offsets-* 파일에는 offset이 기록되진 않고, 그래서 컨슈머를 껐다 켜면 offset 0부터 읽습니다) 저는 commit을 한적이 없으므로 실제로 __consumer_offsets에는 0으로 기록되지 않는데, 저 로그에 있는 offset이 0은 어디서부터 오는 것일까요? +) 코드상으로는 poll 시점에 updateAssignmentMetadataIfNeeded (maybeSeekUnvalidated) 에서 해당 로그가 찍히는데요, 이건 무슨 정책일까요? 관련 자료가 있으면 공유주셔도 감사합니다. // offset=0 설정이 되어버림 [main] INFO org.apache.kafka.clients.consumer.internals.Fetcher - [Consumer clientId=consumer-group-pizza-assign-seek-maintenance-6-1, groupId=group-pizza-assign-seek-maintenance-6] Fetch position FetchPosition{offset=10, offsetEpoch=Optional.empty, currentLeader=LeaderAndEpoch{leader=Optional[localhost:9092 (id: 0 rack: null)], epoch=0}} is out of range for partition pizza-topic-0, resetting offset [main] INFO org.apache.kafka.clients.consumer.internals.SubscriptionState - [Consumer clientId=consumer-group-pizza-assign-seek-maintenance-6-1, groupId=group-pizza-assign-seek-maintenance-6] Resetting offset for partition pizza-topic-0 to position FetchPosition{offset=0, offsetEpoch=Optional.empty, currentLeader=LeaderAndEpoch{leader=Optional[localhost:9092 (id: 0 rack: null)], epoch=0}}. 테스트한 코드 일부 공유드립니다.(필요할 설정들을 했으며, 정말 커밋이 안되고 있는지 확인하기 위해 records.count()>100일 때만 명시적으로 커밋을 한 코드입니다)props.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); TopicPartition topicPartition = new TopicPartition(topicName, 0); kafkaConsumer.assign(List.of(topicPartition)); kafkaConsumer.seek(topicPartition, 10L);while(true) { ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(1_000L)); for(ConsumerRecord<String, String> record: records) { log.info("record key: {}, partition: {}, recordOffset: {}", record.key(), record.partition(), record.offset()); } if (records.count() > 100 ){ kafkaConsumer.commitSync(); log.info("commit sync called"); } } 감사합니다 ^^
-
미해결실습으로 배우는 선착순 이벤트 시스템
쿠폰 발급을 api로 제공할 경우, client에서 쿠폰 발급 여부를 확인할 수 있는 방법 문의드립니다.
안녕하세요. 강의 잘 들었습니다.강의 들으면서 궁금증이 생겼는데, 만약에 쿠폰 발급 기능을 API로 제공한다고 가정하면client -> 쿠폰 발급 기능 API 호출이 이루어지고쿠폰 발급 기능 API에서는 쿠폰 발급 여부를 확인하고 kafka로 produce하게 되는데, 이때는 실제로 쿠폰이 발급된 상태는 아닐 수도 있을 것으로 예상됩니다(실제 쿠폰이 발급되는 시점은 consumer에서 작업이 정상적으로 완료되어야하므로 트래픽이 많거나 하는 경우 시간차이가 더 심할 것으로 예상됩니다) 이러면 쿠폰 발급 기능 API에서 응답값은 어떤 값을 줘야할까요?쿠폰 발급 여부에서 발급이 가능하다면 쿠폰 발급되는것은 확정이기 때문에 발급되었다는 정보?쿠폰 발급 여부에서 발급이 가능하지만 추가적으로 polling해서 client 쪽에서 확인하도록 처리?제가 생각했을땐 위의 2가지정도로 가능할 것 같은데 강사님 의견이 궁금합니다..! 감사합니다.
-
미해결실습으로 배우는 선착순 이벤트 시스템
예제 프로젝트 상에서의 Kafka 사용시 궁금한점
강의 잘 듣고 있습니다. 질문사항이 두개 있습니다.1.4강의 [문제점] 영상에서 쿠폰생성 10000개 요청으로 인해 mysql이 1분에 100개의 insert가 가능하다고 가정할 시 '주문생성/회원가입요청이 타임아웃 또는 10분뒤에 실행' 된다고 하셨는데요.예제로 사용하신 Kafka 사용 예제에서는 Consumer 프로젝트도 어차피 API프로젝트와 같은 DB를 바라보고 있으므로, 어차피 Kafka를 사용하여도 '주문생성/회원가입요청이 타임아웃 또는 10분뒤에 실행'되지 않나요? 왜 여쭤보냐면, 강의 내에서 Kafka 미사용시 주문생성/회원가입요청의 타임아웃 및 10분뒤 실행에 대한 해결책을 Kafka로 사용하셔서 문의드립니다.2.5강의 [Consumer 사용하기] 영상을 보면 API 프로젝트 Consumer 프로젝트가 별개로 존재합니다.그러므로 API프로젝트의 테스트 케이스가 종료되어도 Consumer 프로젝트는 이미 Kafka로 100개의 데이터가 스트림으로 들어오는 상태이므로, 테스트케이스가 종료되어도(즉, API프로젝트가 종료되어도) Cunsumer 프로젝트는 종료가 되지 않은 상태이므로 100개의 쿠폰이 DB에 생성이 되어야 하는게 아닌지요?왜 여쭤보냐면, 강의 내에서는 API프로젝트가 종료되면 Consumer 프로젝트도 작업이 멈추는 현상이 있어서 문의드립니다.
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
SpringBoot 3점대 버전 Spring Security 설정
Spring Security 가 3점대 버전으로 오면서 상당한 변화가 있습니다. 강의 내용을 따라 하다보니 순환참조나, 현재는 지원하지 않는 기능이 상당수 존재하였습니다. 현재 작업한 코드가 문제 해결에 많은 도움이 되면 좋겠어서 글을 첨부합니다. SecurityConfig.class 입니다.@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig{ private final CustomAuthenticationManager customAuthenticationManager; private final UserFindPort userFindPort; private final Environment environment; @Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring(). requestMatchers(new AntPathRequestMatcher("/h2-console/**")) .requestMatchers(new AntPathRequestMatcher( "/favicon.ico")) .requestMatchers(new AntPathRequestMatcher( "/css/**")) .requestMatchers(new AntPathRequestMatcher( "/js/**")) .requestMatchers(new AntPathRequestMatcher( "/img/**")) .requestMatchers(new AntPathRequestMatcher( "/lib/**")); } @Bean protected SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception { http.csrf(AbstractHttpConfigurer::disable); http.authorizeHttpRequests(authorize -> authorize.requestMatchers(new MvcRequestMatcher(introspector, "/**")).permitAll() // requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/users/**")).permitAll() // .requestMatchers(new MvcRequestMatcher(introspector, "/greeting")).permitAll() // .requestMatchers(new MvcRequestMatcher(introspector, "/welcome")).permitAll() // .requestMatchers(new MvcRequestMatcher(introspector, "/health-check")).permitAll() // .requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.POST, "/users")).permitAll() .anyRequest() .authenticated()) .addFilter(getAuthenticationFilter()) .httpBasic(Customizer.withDefaults()); return http.build(); } private AuthenticationFilter getAuthenticationFilter() { return new AuthenticationFilter(customAuthenticationManager, userFindPort, environment); } }requestMatcher에서 AntPathRequestMatcher, MvcRequestMatcher에 관한 설명은부족하지만 https://velog.io/@dktlsk6/Spring-Security-RequestMatcher에서 확인 가능하십니다. CustomUserDetailService.class 입니다. 순환참조 문제가 발생하여 강의와 달리 새로 CustomService를 생성하여 implements 하였습니다.@Component @RequiredArgsConstructor public class CustomUserDetailService implements UserDetailsService { private final UserFindPort userFindPort; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDto userByEmail = userFindPort.findUserByEmail(username); if (userByEmail == null) { throw new UsernameNotFoundException("User Not Found"); } return new User(userByEmail.getEmail(), userByEmail.getEncPasswd(), true, true, true, true, new ArrayList<>()); } } CustomAuthenticationManager.class 입니다. AuthenticationFilter의 AuthenticationManager로 사용할 것입니다.@Component @RequiredArgsConstructor @Slf4j public class CustomAuthenticationManager implements AuthenticationManager { private final CustomUserDetailService customUserDetailService; @Bean protected PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { UserDetails userDetails = customUserDetailService.loadUserByUsername(authentication.getName()); if (!passwordEncoder().matches(authentication.getCredentials().toString(), userDetails.getPassword())) { throw new BadCredentialsException("Wrong password"); } return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities()); } } AuthenticationFilter.class 입니다. 해당 부분은 강의와 차이점이 없습니다.@Slf4j public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final UserFindPort userFindPort; private final Environment environment; public AuthenticationFilter(AuthenticationManager authenticationManager, UserFindPort userFindPort, Environment environment) { super.setAuthenticationManager(authenticationManager); this.userFindPort = userFindPort; this.environment = environment; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { LoginRequestDto creds = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(creds.getEmail(), creds.getPassword(), new ArrayList<>()); return getAuthenticationManager().authenticate(token); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { String username = authResult.getName(); UserDto user = userFindPort.findUserByEmail(username); if (user == null) { throw new UsernameNotFoundException(username); } log.debug("user id {}", user.getUserId()); String token = Jwts.builder() .setSubject(user.getUserId()) .setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(environment.getProperty("token.expiration.time")))) .signWith(SignatureAlgorithm.HS512, environment.getProperty("token.secret")) .compact(); response.addHeader("token", token); response.addHeader("userId", user.getUserId()); } } 아래는 실제 결과입니다.404가 뜨는 이유는 login 성공시 redirect url을 설정해주지 않아서 /(루트) 경로로 이동해서입니다. 해당 경로와 매핑되는 resource나 api가 없기 때문에 해당 오류가 발생한것이므로 정상작동으로 생각하시면 됩니다.아래는 잘못된 정보를 기입하여 실패 테스트 입니다. 추후 강의를 들으며 업데이트 하도록 하겠습니다.
-
미해결[아파치 카프카 애플리케이션 프로그래밍] 개념부터 컨슈머, 프로듀서, 커넥트, 스트림즈까지!
컨슈머 랙 모니터링 아키텍처 관련 질문
안녕하세요. 좋은 강의 잘 보고 있습니다. 컨슈머 랙 모니터링 아키텍처 관련 질문이 있습니다.카프카 버로우, 텔레그래프 application에 대해서 각각의 노드에서 구성하는 것이 일반적인지 아니면 카프카 버로우, 텔레그래프를 하나의 노드에서 동작시켜도 무방한 건지에 대한 부분이 궁금합니다.
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
섹션14 Prometheus와 Grafana 설치 강의 내용중 문의 드립니다
강의 내용대로 진행하였는데http://localhost:8000/user-service/actuator/prometheushttp://localhost:8000/order-service/actuator/prometheus둘다 아래 이미지와같은 에러가 발생합니다 apigateway application.yml 설정- id: user-service uri: lb://USER-SERVICE predicates: - Path=/user-service/actuator/** - Method=GET,POST filters: - RemoveRequestHeader=Cookie - RewritePath=/user-service/(?<segment>.*), /$\{segment} - id: order-service uri: lb://ORDER-SERVICE predicates: - Path=/order-service/actuator/** - Method=GET filters: - RemoveRequestHeader=Cookie - RewritePath=/order-service/(?<segment>.*), /$\{segment} 프로메테우스.yml 설정static_configs: - targets: ["localhost:9090"] - job_name: 'user-service' scrape_interval: 15s metrics_path: '/user-service/actuator/prometheus' static_configs: - targets: ['localhost:8000'] - job_name: 'order-service' scrape_interval: 15s metrics_path: '/order-service/actuator/prometheus' static_configs: - targets: ['localhost:8000'] - job_name: 'apigateway-service' scrape_interval: 15s metrics_path: '/actuator/prometheus' static_configs: - targets: ['localhost:8000'] apigateway 의 AuthorizationHeaderFilter.java// 절차 // login -> token반환받음 -> client에서 apigateway로 정보 요청 시 (토큰정보를 가지고 요청함) -> 서버에서는 토큰정보 검증 (header 안에 토큰이 포함됨) 38줄 @Override public GatewayFilter apply(Config config) { return ((exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); if(!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) { return onError(exchange, "no authorization header", HttpStatus.UNAUTHORIZED); } String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0); // 반환값은 list배열이기에 0 String jwt = authorizationHeader.replace("Bearer", ""); if(!isJwtValid(jwt)) { return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED); } return chain.filter(exchange); }); }여기서 지속적으로 아래 오류가 발생중입니다2023-09-10 01:15:35.241 INFO 31372 --- [tor-http-nio-10] c.e.a.filter.GlobalFilter : Global Filter baseMessage: Spring Cloud Gateway Global Filter2023-09-10 01:15:35.241 INFO 31372 --- [tor-http-nio-10] c.e.a.filter.GlobalFilter : Global Filter Start: request id -> a5f590c1-10982023-09-10 01:15:35.270 INFO 31372 --- [tor-http-nio-10] c.e.a.filter.GlobalFilter : Global Filter End: response code -> 404 NOT_FOUND2023-09-10 01:15:35.528 INFO 31372 --- [tor-http-nio-12] c.e.a.filter.GlobalFilter : Global Filter baseMessage: Spring Cloud Gateway Global Filter2023-09-10 01:15:35.528 INFO 31372 --- [tor-http-nio-12] c.e.a.filter.GlobalFilter : Global Filter Start: request id -> d5aec101-10992023-09-10 01:15:35.528 ERROR 31372 --- [tor-http-nio-12] c.e.a.filter.AuthorizationHeaderFilter : no authorization header2023-09-10 01:15:35.528 INFO 31372 --- [tor-http-nio-12] c.e.a.filter.GlobalFilter : Global Filter End: response code -> 401 UNAUTHORIZED 이 필터관련 수정한건 없는것으로 기억하는데 제가 놓친게있을까요답답하네요 ㅠ
-
미해결실습으로 배우는 선착순 이벤트 시스템
안녕하세요 강사님 이전 재고 관리 강의와 차이에 대해 궁금합니다.
쿠폰 생성 발급 로직도컬럼에 쿠폰의 개수를 지정해놓으면이전 강의랑 똑같은 거 같은데왜 이번 강의는 쿠폰 엔티티를 새로 생성해서 그 개수를 체크하는 건지 궁금합니다.이전 컬럼에 개수를 두어 관리하는 거랑지금처럼 엔티티를 생성하는 방식의 차이점이 너무 궁금해요 항상 감사합니다.
-
미해결실습으로 배우는 선착순 이벤트 시스템
쿠폰 카운트를 Redis에 의존하고 있는데요
만약 Redis에 장애가 발생한다면 2차 장치로 DB Count에 의존할 수 밖에 없는걸까요?실무에선 어떻게 대응하시는지 궁금합니다!
-
미해결[아파치 카프카 애플리케이션 프로그래밍] 개념부터 컨슈머, 프로듀서, 커넥트, 스트림즈까지!
KStream, KTable 조인 스트림즈 애플리케이션에서 에러가 발생하고 있습니다.
... [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] INFO org.apache.kafka.streams.processor.internals.StreamThread - stream-thread [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] State transition from PARTITIONS_ASSIGNED to PENDING_SHUTDOWN [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] INFO org.apache.kafka.streams.processor.internals.StreamThread - stream-thread [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] Shutting down [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] INFO org.apache.kafka.clients.consumer.KafkaConsumer - [Consumer clientId=order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1-restore-consumer, groupId=null] Unsubscribed all topics or patterns and assigned partitions [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] INFO org.apache.kafka.clients.producer.KafkaProducer - [Producer clientId=order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1-producer] Closing the Kafka producer with timeoutMillis = 9223372036854775807 ms. [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] INFO org.apache.kafka.streams.processor.internals.StreamThread - stream-thread [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] State transition from PENDING_SHUTDOWN to DEAD [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] INFO org.apache.kafka.streams.KafkaStreams - stream-client [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43] State transition from REBALANCING to ERROR [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] ERROR org.apache.kafka.streams.KafkaStreams - stream-client [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43] All stream threads have died. The instance will be in error state and should be closed. [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] INFO org.apache.kafka.streams.processor.internals.StreamThread - stream-thread [order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1] Shutdown complete Exception in thread "order-join-application-05b24bf4-65d2-4fda-83be-a754a4988a43-StreamThread-1" java.lang.UnsatisfiedLinkError: /private/var/folders/16/xqv9hsq91sn7glvzckc__r100000gn/T/librocksdbjni3612565276450787735.jnilib: dlopen(/private/var/folders/16/xqv9hsq91sn7glvzckc__r100000gn/T/librocksdbjni3612565276450787735.jnilib, 0x0001): tried: '/private/var/folders/16/xqv9hsq91sn7glvzckc__r100000gn/T/librocksdbjni3612565276450787735.jnilib' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e'))샘플 코드 실행 시 해당 에러가 계속 발생하고 있습니다.원인을 알 수 있을까요?
-
미해결[아파치 카프카 애플리케이션 프로그래밍] 개념부터 컨슈머, 프로듀서, 커넥트, 스트림즈까지!
프로듀서를 통해 카프카 클러스터에 레코드를 보낼 때 항상 1개의 배치로만 tcp 통신이 이루어지나요?
카프카 프로듀서 소개 강의를 보다 질문드립니다.프로듀서에서 레코드를 send하면 Accumulator에서 배치로 묶는 과정을 한다고 하셨는데요.실제 Sender가 발생하는 시점에 프로듀서 애플리케이션에서 항상 1개의 배치 단위로 tcp 통신이 발생하나요?아니면 내부적으로 여러개의 배치를 한번의 tcp 통신으로 통신할까요?
-
미해결실습으로 배우는 선착순 이벤트 시스템
동시성제어
안녕하세요 강의를 듣다가 궁금증이 생겼는데요. kafka는 메시지를 하나씩 처리하기 때문에 동시성 제어도 가능할 것이라고 이해했는데 그렇다면 여기서 레디스를 사용하지 않더라도 카프카만 사용해도 동시성과 관련된 데이터 정합성을 보장할 수 있는건가요?
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
2023.08.10 기준 Spring Boot 3버전 대 Security Config 설정 파일 공유합니다.
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class WebSecurity { private final UserService userService; private final BCryptPasswordEncoder bCryptPasswordEncoder; private final Environment environment; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(request ->{ request.requestMatchers(antMatcher("/actuator/**")).permitAll(); request.requestMatchers(antMatcher("/**")).permitAll();}) // .headers(header -> header.frameOptions( // frameOptionsConfig -> frameOptionsConfig.disable())) .apply(new MyCustomSecurity()); return http.build(); } public class MyCustomSecurity extends AbstractHttpConfigurer<MyCustomSecurity, HttpSecurity> { @Override public void configure(HttpSecurity http) throws Exception { AuthenticationManager authenticationManager = http.getSharedObject( AuthenticationManager.class); AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager, userService, environment); http.addFilter(authenticationFilter); } protected void configure2(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder); } } }해당 코드는 앞 내용까지 포함하여 설정이 적용된 버전입니다. 저같은 경우는 처음부터 mysql로 진행하여서 h2 콘솔을 사용하지 않아 frameOptions를 주석처리 하였으나, 혹여나 h2 콘솔 사용하시는 분은 주석 해제 후 사용하시면 되고, 23.08.10 기준으로 hasIpAddress는 사용 불가능합니다.
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
spring boot 3버전대에서 metrics에서 정보 안나오는 문제 해결
@Configuration @EnableAspectJAutoProxy public class MetricsConfig { @Bean public TimedAspect timedAspect(MeterRegistry registry) { return new TimedAspect(registry); } }해당 config 파일을 추가해주세요.
-
미해결실습으로 배우는 선착순 이벤트 시스템
MySql Lock을 사용하지 않는 이유
강의에서 설명해주시기로는 쿠폰 개수를 가져오는 것부터 쿠폰 생성까지 lock을 걸어야 한다고 설명 주셨는데 이전 강의인 재고 관리 이슈와는 다르게 row가 아닌 table에 lock을 걸기 때문에 성능 이슈가 발생한다고 보면 될까요?
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
login 요청하면 404 에러가 발생합니다.
포스트맨으로 API_GW를 경유해서 http://127.0.0.1:8000/user-service/login 요청하면 404 에러가 발생합니다.근데 http://127.0.0.1:54656/login 로 바로 리퀘스트하면 200 상태가 반환됩니다.로그인만 저렇고 http://127.0.0.1:8000/user-service/users 요청하면 회원가입은 또 됩니다..어디 부분부터 잘못되었는지 찾아봐야할까요?필터부분도 오타없이 잘 되었는데.. - id: user-service uri: lb://USER-SERVICE predicates: - Path=/user-service/login - Method=POST filters: - RemoveRequestHeader=Cookie - RewritePath=/user-service/(?<segment>.*), /$\{segment}/ - id: user-service uri: lb://USER-SERVICE predicates: - Path=/user-service/users - Method=POST filters: - RemoveRequestHeader=Cookie - RewritePath=/user-service/(?<segment>.*), /$\{segment}/ - id: user-service uri: lb://USER-SERVICE predicates: - Path=/user-service/** - Method=GET filters: - RemoveRequestHeader=Cookie - RewritePath=/user-service/(?<segment>.*), /$\{segment}/
-
미해결실습으로 배우는 선착순 이벤트 시스템
발급가능 쿠폰개수가 1인당 2개이상일 때
안녕하세요! 강의 재미있고 유익하게 잘 듣고 있습니다 :)확장에 대해 생각해보니, Set을 이용해서는 중복방지 이상 개수 확장에 대해서 처리는 불가능 할 거 같아서요. 혹시, 2개 이상 N개 제한에 대한 확장 방법은 어떤게 있을까요??감사합니다!
-
미해결실습으로 배우는 선착순 이벤트 시스템
왜 쿠폰수가 너무 많을까요?
분명 현재 없는 상태이고결과가 자꾸 이상하게 나와서 sout 처리를 잠시 해보았습니다 package com.example.api.service; import com.example.api.domain.Coupon; import com.example.api.repository.CouponCountRepository; import com.example.api.repository.CouponRepository; import org.springframework.stereotype.Service; @Service public class ApplyService { private final CouponRepository couponRepository; private final CouponCountRepository couponCountRepository; public ApplyService(CouponRepository couponRepository, CouponCountRepository couponCountRepository) { this.couponRepository = couponRepository; this.couponCountRepository = couponCountRepository; } public void applyV1(Long userId) { Long count = couponRepository.count(); if(count > 100) { return; } couponRepository.save(new Coupon(userId)); } public void applyV2(Long userId) { Long count = couponCountRepository.increment(); System.out.println(count); if(count > 100) { return; } couponRepository.save(new Coupon(userId)); } } @SpringBootTest class ApplyServiceTest { @Autowired private ApplyService applyService; @Autowired private CouponRepository couponRepository; @Test public void applyOnce() { applyService.applyV1(1L); long count = couponRepository.count(); Assertions.assertEquals(1L, count); } @Test public void 여러명응모V1() throws InterruptedException { int threadCount = 1000; ExecutorService executorService = Executors.newFixedThreadPool(32); CountDownLatch latch = new CountDownLatch(threadCount); for(int i=0; i<threadCount; i++){ long userId = i; executorService.submit(() -> { try { applyService.applyV1(userId); } catch(Exception e) { System.out.println(e); }finally { latch.countDown(); } }); } latch.await(); long count = couponRepository.count(); assertThat(count).isEqualTo(100); } @Test public void 여러명응모V2() throws InterruptedException { int threadCount = 1000; ExecutorService executorService = Executors.newFixedThreadPool(32); CountDownLatch latch = new CountDownLatch(threadCount); for(int i=0; i<threadCount; i++){ long userId = i; executorService.submit(() -> { try { applyService.applyV2(userId); } catch(Exception e) { System.out.println(e); }finally { latch.countDown(); } }); } latch.await(); long count = couponRepository.count(); org.assertj.core.api.Assertions.assertThat(count).isEqualTo(100); } } 그런데 여러명응모V2 test를 실행시에 count를 출력시다음과 같은 수가 나옵니다. 20003200112001220013200152001620017200182002020022 ??? 한번 할때마다 1000씩 쿠폰의 수가 증가중인데요;;;조회했을때는 empty라 나오는데 이렇게 되는 연유를 잘 모르갰습니다. 테스트 코드라서 rollback이 되야할거 같은데 그렇지 않는것도 잘 모르겟네요;; ㅠㅠ
-
해결됨Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
(필수정보) 레거시 bootstrap 을 사용하지 않는 방법
* bootstrap 라이브러리를 추가하고 아래와 같이 application.yml 파일만으로 설정하면 적용이 안됩니다.dependency에 bootstrap을 의존성 추가하지 않습니다.그 후 application.yml파일에 아래와 같이 설정합니다.spring: cloud: config: name: ecommerce # yml 파일명 앞부분 config: import: optional:configserver:http://localhost:8888 # 구성정보 설정강의에서 나오는 bootstrap.yml 설정 정보를 Spring Boot 2.4버전 이후부터는 application.yml 설정 정보에서 사용할 수 있습니다. (공식홈페이지)위 방법은 공식 홈페이지 목차에서 Spring Cloud Config Client - Spring Boot Config Data Import 부분에 나와있습니다.만약 bootstrap 라이브러리를 사용한 구성설정을 하고 싶다면 공식홈페이지 목차에서 Spring Cloud Config Client - Config First Bootstrap 부분을 살펴보시면 되겠습니다.
-
미해결[아파치 카프카 애플리케이션 프로그래밍] 개념부터 컨슈머, 프로듀서, 커넥트, 스트림즈까지!
카프카 브로커 cpu 사용률 관련입니다.
안녕하세요, 강의 잘 봤습니다.카프카의 실제 운영관점의 질문을 드리고자 합니다. 상황에 따라 다르겠지만, 카프카 브로커의 권장 cpu 스펙이 있을까요? 카프카 브로커의 cpu사용률은 무엇에 크게 좌우될지 궁금합니다.예를들면, 토픽 및 파티션의 수에 비례한다든지, 메시지 사이즈에 비례한다든지 질문드리는 이유는 카프카를 신규 구성 예정인데요, 내부적으로 테스트 해봤을때 업무량에 크게 좌우되지는 않는 것 같았는데, 파티션 수가 많은 경우에 튀어 보이긴 합니다. 참고로 압축기능은 사용하지 않습니다.