묻고 답해요
150만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
순위 정보를
불러오고 있어요
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
grafana 그래프 nodata
https://www.inflearn.com/community/questions/267420/%EB%8C%80%EC%89%AC%EB%B3%B4%EB%93%9C%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EB%AA%BB%EA%B0%80%EC%A0%B8%EC%98%A4%EB%8A%94%EA%B2%83-%EA%B0%99%EC%8A%B5%EB%8B%88%EB%8B%A4를 참고하여 Legend: {{instance}}Instant 체크 + Fields 입력을 통해 위에 처리량은 잘나오는 모습입니다 하지만밑에 그래프들은 Fields 입력하는 부분이없어서 처리량때 처럼 설정을 할 수 없고 역시나no data로 나오네요설정은 이대로 했습니다.
-
미해결Kafka & Spark 활용한 Realtime Datalake
강의안 문제
강의안이 다 안 올라와 있는 것 같습니다
-
미해결스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
각 사용자는 게시글 1개당 1번 조회수 증가'일 경우 설계 질문 드립니다.
안녕하세요 쿠케님!강의에 나온 10분의 TTL이 없을시 조회수 증가 관련되서 질문드립니다!' 현 상황에서 게시글 접근시 마다 조회수 증가 API를 호출하고 있습니다.테이블 정보는 다음과 같습니다. Board 테이블 id(auto-increment) readCountBoardRead 테이블 id(auto-increment) boardId userId 게시글 상세 접근시 마다 호출하는 '조회수 증가 API'의 흐름은 다음과 같습니다.1. 해당 게시글을 유저가 조회 했는지 검증 (BoardRead 테이블에서 검증)1-1. 조회 한 이력이 있으면 return;2. Board 비관적 락 조회3. Board 테이블 readCount 업데이트4. BoardRead 테이블 insert Board의 readCount는 게시글 조회수를 나타내고, BoardRead는'각 사용자는 게시글 1개당 1번 조회수 증가'를 검증하기 위한 용도 입니다.'각 사용자는 게시글 1개당 1번 조회수 증가' 정책을 반드시 가져가야 한다면BoardRead 테이블에 있는 데이터도 레디스로 옮겨야 할까요?그런데 조회수 데이터는 계속해서 쌓일테고 비즈니스에 중요하지 않은 데이터가 레디스 메모리만차지하는 느낌이 들어서 꺼려지더 라구욤.. 강의 내용대로 TTL을 걸수 밖에 없는건가 고민도 듭니당..(레디스는 클러스터 환경으로 사용하고 있습니다.) 기능은 그대로 유지하되 비관적 락을 뺄 수 있는 방법이 있을까요? ※ 번외로 트래픽 바로 몰리니까 비관적 락 로직 때문인지 잠금 이슈 나서 디비 바로 터졌버렸네요 하하하 ㅠ비관적 락을 선호하지 않는 이유를 체감해버렸다.. ※ 뇌 + GPT 갈구니까 아래와 같은 여러 결론이 나왔습니다.해결책11. Redis SET 자료구조로 중복 체크, 최초 조회면 TTL 걸어줌2. 분산락(Redis)을 걸고, Board 테이블 readCount 업데이트 및 BoardRead Insert 수행3. 락 해제해결책21. Redis SET 자료구조로 중복 체크, 최초 조회면 TTL 걸어줌2. 비동기로 Board 테이블 readCount 업데이트(낙관적 락 적용) 및 BoardRead Insert 수행작성하면서 문득 'readCount를 정규화 할까?' 했는데 스케이링 넘 클것 같네요..이유는 테이블 설계를 JPA의 상속을 활용하는 방안으로 했기 때문에 readCount 필드가 '게시판'이란 추상 클래스에 위치해 있습니다.주저리 주저리 적어봤는데 머릿속에 혼란이 오네요 ㅠㅠ자기전 마지막 생각레디스의 incr를 이용해 조회수 관리, 조회수 데이터는 mysql에 주기적으로 백업
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
route 설정이 동작을 안하는거 같아요.
RouteLocator gatewayRoutes(RouteLocatorBuilder builder)이 메소드를 만들기 위해 implementation 'org.springframework.cloud:spring-cloud-starter-gateway' 이걸로 수정했을 했는데 이후 저 메소드를 주석 처리하고 applicatio.yml을로 설정을 대체 했을 때는 동작을 안하고 implementation 'org.springframework.cloud:spring-cloud-starter-gateway-mvc'mvc로 다시 변경해줘야 올바르게 동작하는데무슨 차이가 있을까요?
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
댓글 수 구현에서 동시성 문제 해결 질문드립니다
학습 관련 질문을 최대한 상세히 남겨주세요!고민 과정도 같이 나열해주셔도 좋습니다.먼저 유사한 질문이 있었는지 검색해보세요.인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.안녕하세요 댓글 수 구현 강의를 해보다가 동시성 문제를 해결해보고 싶어서 비관적 락 for update를 사용하는 방법으로 한번 코드를 짜보고 테스트를 해보고 있습니다.코드는 아래처럼 짜보았습니다@Table(name = "article_comment_count") @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @ToString public class ArticleCommentCount { @Id private Long articleId; private Long commentCount; public static ArticleCommentCount init(Long articleId, Long commentCount) { ArticleCommentCount articleCommentCount = new ArticleCommentCount(); articleCommentCount.articleId = articleId; articleCommentCount.commentCount = commentCount; return articleCommentCount; } public void increase() { this.commentCount++; } public void decrease() { this.commentCount--; } }public interface ArticleCommentCountRepository extends JpaRepository<ArticleCommentCount, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) Optional<ArticleCommentCount> findLockedByArticleId(Long articleId); }@Service @RequiredArgsConstructor public class CommentService { private final CommentRepository commentRepository; private final Snowflake snowflake = new Snowflake(); private final ArticleCommentCountRepository articleCommentCountRepository; @Transactional public CommentResponse create(CommentCreateRequest request) { Comment parent = findParent(request); Comment comment = commentRepository.save( Comment.create( snowflake.nextId(), request.getContent(), parent == null ? null : parent.getCommentId(), request.getArticleId(), request.getWriterId() ) ); ArticleCommentCount articleCommentCount = articleCommentCountRepository.findLockedByArticleId(request.getArticleId()) .orElseGet(() -> { ArticleCommentCount newCount = ArticleCommentCount.init(request.getArticleId(), 0L); articleCommentCountRepository.save(newCount); return newCount; }); articleCommentCount.increase(); articleCommentCountRepository.save(articleCommentCount); return CommentResponse.from(comment); } private Comment findParent(CommentCreateRequest request) { Long parentCommentId = request.getParentCommentId(); if (parentCommentId == null) { return null; } return commentRepository.findById(parentCommentId) .filter(not(Comment::getDeleted)) .filter(Comment::isRoot) .orElseThrow(); } public CommentResponse read(Long commentId) { return CommentResponse.from(commentRepository.findById(commentId).orElseThrow()); } @Transactional public void delete(Long commentId) { commentRepository.findById(commentId) .filter(not(Comment::getDeleted)) .ifPresent(comment -> { if (hasChildren(comment)) { comment.delete(); } else { delete(comment); } }); } private boolean hasChildren(Comment comment) { return commentRepository.countBy(comment.getArticleId(), comment.getCommentId(), 2L) == 2; } private void delete(Comment comment) { commentRepository.delete(comment); articleCommentCountRepository.findLockedByArticleId(comment.getArticleId()) .ifPresent(articleCommentCount -> { articleCommentCount.decrease(); articleCommentCountRepository.save(articleCommentCount); }); if(!comment.isRoot()) { commentRepository.findById(comment.getParentCommentId()) .filter(Comment::getDeleted) .filter(not(this::hasChildren)) .ifPresent(this::delete); } } public CommentPageResponse readAll(Long articleId, Long page, Long pageSize) { return CommentPageResponse.of( commentRepository.findAll(articleId, (page - 1) * pageSize, pageSize).stream() .map(CommentResponse::from) .toList(), commentRepository.count(articleId, PageLimitCalculator.calculatePageLimit(page, pageSize, 10L)) ); } // 무한 스크롤 public List<CommentResponse> readAll(Long articleId, Long lastParentCommentId, Long lastCommentId, Long limit) { List<Comment> comments = lastParentCommentId == null || lastCommentId == null ? commentRepository.findAllInfiniteScroll(articleId, limit) : commentRepository.findAllInfiniteScroll(articleId, lastParentCommentId, lastCommentId, limit); return comments.stream() .map(CommentResponse::from) .toList(); } public Long count(Long boardId) { return articleCommentCountRepository.findById(boardId) .map(ArticleCommentCount::getCommentCount) .orElse(0L); } }@Test void concurrencyCountTest() throws InterruptedException { Long articleId = 24L; int threadCount = 10; ExecutorService executorService = Executors.newFixedThreadPool(threadCount); CountDownLatch latch = new CountDownLatch(threadCount); for(int i = 0; i < threadCount; i++) { final Long writerId = (long) (1000 + i); executorService.execute(() -> { try { createComment(new CommentCreateRequest(articleId, "concurrency test", null, writerId)); } catch (Exception e) { System.err.println("Exception in thread: " + Thread.currentThread().getName() + " -> " + e.getMessage()); } finally { latch.countDown(); } }); } latch.await(); Long commentCount = restClient.get() .uri("/v1/comments/articles/{articleId}/count", articleId) .retrieve() .body(Long.class); System.out.println("최종 commentCount = " + commentCount); assertThat(commentCount).isEqualTo(threadCount); }그런데 이렇게 했을때맨 처음 실행을 하면 1개의 데이터만 삽입되고 나머지 9개는 소실이됩니다그리고 한번더 실행하면 11개의 데이터가 저장되는데 맨처음 저장된 1개의 데이터 + 10개의 스레드가 저장한 10개의 데이터가 되어 11개가 됩니다.여기서 문제가 article_comment_count 테이블에 데이터가 아예 없을때 10개의 스레드가 동시에 insert문을 날리려고해서 Duplicate entry '24' for key 'article_comment_count.PRIMARY' 이런 문제가 나오지 않나 생각이 듭니다만.. create 메서드에 @Transactional(isolation = Isolation.READ_COMMITTED)로 격리 수준을 높여봤지만 여전히 문제가 해결되지 않습니다.혹시 제가 잘못 이해한 부분이 있을까요? 그리고 동시성 문제를 해결하려면 어떻게 해야할까요? gpt에 물어보거나 구글링해서 찾아봐도 해결이 되지 않아서 질문드립니다!
-
미해결카프카 완벽 가이드 - 코어편
KafkaTimeoutError:
안녕하세요 개발자님 KafkaTimeoutError: Failed to update metadata after 60.0 secs. 2025-03-21 21:28:43.328 | ERROR | kafka_communication:send_message:52 - KafkaTimeoutError: Failed to update metadata after 60.0 secs.파이썬으로 작성된 코드를 받게 되어 개발을 진행 중인데 window 환경에서 ai프로그램을 돌려 린눅스 서버에 cnotainer 컨프런트 kafka 로 결과 값을 전송 하게 되어씁니다.window 에서 ai프로그램이 결과 값을 리눅스 docker contianer로 전송 하는데 오래 걸리지만, kafka 로 전송이 되지만.,window에 있던 ai 프고르램을 다른 리눅스 서버 2 번째 환경에서 실행시키고, 첫번째 kafka가 동작 중인 kafka에 결과 값을 전송 하면 KafkaTimeoutError: Failed to update metadata after 60.0 secs.2025-03-21 21:28:43.328 | ERROR | kafka_communication:send_message:52 - KafkaTimeoutError: Failed to update metadata after 60.0 secs. error 가 발생 합니다.window에서는 안발생 했는데, 말이죠.,ai 프로그램을 강제 종료 하고 error를 보면 ^CTraceback (most recent call last): File "/home/gpu-1/projects/argos/source/ai/ai-main/message_event_handler.py", line 174, in <module> message_event_handler.handle_messages() File "/home/gpu-1/projects/argos/source/ai/ai-main/message_event_handler.py", line 80, in handle_messages msg = self.consumer.receive_message() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/gpu-1/projects/argos/source/ai/ai-main/kafka_communication.py", line 83, in receive_message message = next(self.consumer).value ^^^^^^^^^^^^^^^^^^^ File "/opt/miniconda3/envs/argos-ai-volov8/lib/python3.11/site-packages/kafka/consumer/group.py", line 1168, in next return next(self._iterator) ^^^^^^^^^^^^^^^^^^^^ File "/opt/miniconda3/envs/argos-ai-volov8/lib/python3.11/site-packages/kafka/consumer/group.py", line 1140, in messagegenerator_v2 record_map = self.poll(timeout_ms=timeout_ms, update_offsets=False) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/miniconda3/envs/argos-ai-volov8/lib/python3.11/site-packages/kafka/consumer/group.py", line 679, in poll records = self._poll_once(inner_timeout_ms(), max_records, update_offsets=update_offsets) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/miniconda3/envs/argos-ai-volov8/lib/python3.11/site-packages/kafka/consumer/group.py", line 722, in pollonce self._client.poll(timeout_ms=inner_timeout_ms(self._coordinator.time_to_next_poll() * 1000)) File "/opt/miniconda3/envs/argos-ai-volov8/lib/python3.11/site-packages/kafka/client_async.py", line 683, in poll self._poll(timeout / 1000) File "/opt/miniconda3/envs/argos-ai-volov8/lib/python3.11/site-packages/kafka/client_async.py", line 742, in _poll if conn.send_pending_requests_v2(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/miniconda3/envs/argos-ai-volov8/lib/python3.11/site-packages/kafka/conn.py", line 1039, in send_pending_requests_v2 total_bytes = self._send_bytes(self._send_buffer) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/miniconda3/envs/argos-ai-volov8/lib/python3.11/site-packages/kafka/conn.py", line 683, in sendbytes sent_bytes = self._sock.send(data[total_sent:]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^이렇게 나오고 producer 설정은class MessageProducer: def init(self, broker): self.broker = broker # self.topic = topic self.producer = self.init() def init(self): while True: try: producer = KafkaProducer( bootstrap_servers=self.broker, value_serializer=lambda x: json.dumps(x).encode('utf-8'), acks=0, request_timeout_ms=1200000, retry_backoff_ms=3000, batch_size=16384, # metadata_max_age_ms=3000000, api_version=(2, 5, 0), retries=3 ) break except Exception as e: print(e) logger.error(e) logger.error("Retry connecting to broker...") sleep(2) logger.info("Connected to kafka broker !") return producer` def send_message(self, topic, msg): try: print(f'topic: {topic}') future = self.producer.send(topic, value=msg) self.producer.flush() future.get(timeout=None) return {'status_code': 200, 'error': None} except Exception as e: print(e) logger.error(e) return e입니다. window에서 리눅스로 옴겼더니 왜 그런걸까요?설정 부분 추가 및 변경 해야 하는 부분이 어디 일까요?client에서 설정을 수정 해야 하나여ㅛ, kafka server.properties 에서 어디를 수정 해야 하나요?producer로 kafak로 부토ㅓ matadata를 기다려야 하나요?
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
조회수를 RDB에만 저장하고 있는 서비스에서 Redis 도입 관련해서 질문입니다.
안녕하세요. 쿠케님강의 너무나 잘 보고 있습니다. 쿠케님 질의응답 게시글 정독하고 있는데 1:1 멘토링 부럽지 않을만큼의 고퀄리티 답변 언제나 감사드립니다. (갬동갬동) 현재 회사에서 조회수를 RDB에만 저장하고 있습니다.이를 강의 내용처럼 'Redis 이전 & MySql 백업'을 적용 하고 싶은데 아래 흐름대로 적용 하면 될까요?1⃣ (개발) redis 저장 및 조회, mysql 백업 코드 작성2⃣ (개발) 기존 MySQL 데이터를 Redis에 저장하는 마이그레이션 코드 작성=> 이렇게 일회성 마이그레이션 같은 경우 테스트 코드로 한번만 돌리는데 쿠케님은 어떤 방식으로 하시나요?3⃣ (배포 전) 1번 코드 배포 직전에 2번 로직 실행4⃣ (배포) 1번 코드 배포 위 방식에서 발생할 수 있는 문제점1. 3번(마이그레이션)과 4번(배포) 사이에 조회수가 누락될 가능성- 마이그레이션 실행 후 MySQL에는 새로운 조회수가 계속 업데이트되지만, Redis는 아직 트래픽을 받지 않음.- 즉, 마이그레이션 실행 이후 MySQL에 새로 기록된 조회수는 Redis에 반영되지 않음 → 데이터 불일치 발생 가능.2. 처음 Redis로 전환할 때, Redis에 캐싱되지 않은 일부 조회수가 MySQL에 계속 쿼리될 가능성이 있음.- 처음 Redis로 전환할 때, Redis에 캐싱되지 않은 일부 조회수가 MySQL에 계속 쿼리될 가능성이 있음.- 특정 조회수가 빠르게 증가하면 Hot Key 이슈 발생 가능. 조회수 누락을 최소화 하는 방법1⃣ (개발) redis 저장 및 조회, mysql 백업 코드 작성2⃣ (개발) 기존 MySQL 데이터를 Redis에 저장하는 마이그레이션 코드 작성 및 실행3⃣ (1번 코드 배포 전) 더블 라이트(Double Write) 모드 개발하여 운영에 적용4⃣ (배포) 기존 MySQL 기반 조회수 코드 제거, Redis 기반으로 전환5⃣ (배포 후) MySQL 백업 로직 실행 및 기존 MySQL 조회 로직 완전히 제거제가 혹여나 놓친게 있거나 더 좋은 방법이 있으면 천천히 답변 부탁드립니닷! 출처: 내 뇌 + GPT 센세
-
해결됨커머스 서비스로 배우는 NestJS 실전 개발 (w. Prisma, Docker, Redis, Kafka)
강의 코드를 확인할 수 있는 깃허브 URL이 있을지 궁금합니다!
안녕하세요 좋은 강의 감사합니다! 혹시 강의 코드를 확인할 수 있는 깃허브 URL을 따로 제공받을 수 있는지 궁금합니다!!
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
FeignErrorDecoder 질문
FeignErrorDecoder 에 대해서order_is_empty를 수정한뒤에127.0.0.1:8000/actuator/busrefresh 를 호출하면Keys refreshed [spring.cloud.bootstrap.enabled, order_service.exception.order_is_empty]로그는 정상적으로 뜨며디버깅 모드로 코드를 추가해서 실행한결과List<ResponseOrder> orders= orderServiceClient.getOrders(userId);코드전에env.getProperty("order_service.exception.order_is_empty") 를 확인해본 결과는정상적으로 리프레시되지만 이후에 오류를 터트리고 FeignErrorDecoder 처리 로직에서switch(response.status()){ case 400: break; case 404: if(methodKey.contains("getOrders")){ return new ResponseStatusException(HttpStatus.valueOf(response.status()),env.getProperty("order_service.exception.order_is_empty")); } break; default: return new Exception(response.reason()); }getProperty("order_service.exception.order_is_empty")이부분이 새로운 환경 변수가ErrorDecoder 에서는 반영 안되는것같아서 해결이 안되서 질문남깁니다 ! UserServiceImpl 에서는 정상적으로 바뀐값이 나오는거보면 ErrorDecoder 문제 같은데 정확히 모르겠습니다 @RefreshScope 이것도 해결책은 아닌것같아요 답변 부탁드립니다 !
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
id관련
안녕하세요. 강의 잘 듣고 있습니다.제가 원래 질문이 많은데 개념을 이해가 잘 되도록 쉽게 설명해 주셔서 질문 드릴게 별로 없네요. 보통 api path에 id를 추가하시는거 같은데요.현업에서도 auto_increment나, snowflake id같은 db에서 쓰는 id를 그대로 넣고 사용하시나요?아니면 prefix등을 추가하여 조금 더 가공을 한다든지 하시나요?숫자만 들어가니 좀 밋밋해 보이기도 하고 알아보기도 힘들거 같기도 해서요.
-
해결됨커머스 서비스로 배우는 NestJS 실전 개발 (w. Prisma, Docker, Redis, Kafka)
섹션3과 섹션4 같은 영상으로 확인됩니다
확인부탁드립니다
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
테스트 작성 관련 간단한 질문입니다.
안녕하세요. 강의 잘 듣고 있습니다.테스트 작성하실때 request와 response를 import 안하고 따로 inner class로 만드시는 특별한 이유가 있을까요?궁금하네요.
-
미해결Kafka & Spark 활용한 Realtime Datalake
데이터레이크 구성요소
영상 화면이 안나오는것 같습니다!!
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
아웃박스 패턴과 로그 테일링 기법의 선택 기준에 대해서
안녕하세요 쿠케님 강의 잘 듣고 있습니다.아웃박스 패턴과 로그 테일링 기법은 물론 애플리케이션의 특징마다 다르겠지만로그 테일링 기법이 선호되는 경우는 어떤게 있을까요? LLM 챗봇들과 열심히 논의해본 결과 둘은 실시간성과 구현의 단순성에서 가장 큰 차이로 느꼈는데요. 실제로도 두 가지가 핵심적인 기준 역할을 하는지가 궁금하고 이외에 제가 찾지 못한 다른 것이 있다면 무엇이 있을까요?
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
로그인 에러
안녕하세요 강사님! 질문이 있어서 작성합니다.현재 섹션 7까지 수강한 상태입니다.회원가입을 하고 로그인을 시도했는데 회원가입은 정상, 로그인은 에러가 반환됩니다. 1) 섹션 19(165강)에서 WebSecurity 코드로 실행package com.example.user_service.sercurity; import com.example.user_service.service.UserService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.Authentication; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; import org.springframework.core.env.Environment; import org.springframework.security.web.access.intercept.RequestAuthorizationContext; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.IpAddressMatcher; import java.util.function.Supplier; @Configuration @EnableMethodSecurity public class WebSecurity { private final UserService userService; private final BCryptPasswordEncoder bCryptPasswordEncoder; private final Environment env; public static final String ALLOWED_IP_ADDRESS = "127.0.0.1"; public static final String SUBNET = "/32"; public static final IpAddressMatcher ALLOWED_IP_ADDRESS_MATCHER = new IpAddressMatcher(ALLOWED_IP_ADDRESS + SUBNET); public WebSecurity(Environment env, UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder){ this.env = env; this.userService = userService; this.bCryptPasswordEncoder = bCryptPasswordEncoder; } @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); authenticationManagerBuilder.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder); return authenticationManagerBuilder.build(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf((csrf) -> csrf.disable()) .authorizeHttpRequests((authz) -> authz .requestMatchers(new AntPathRequestMatcher("/actuator/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/h2-console/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/users", "POST")).permitAll() .requestMatchers(new AntPathRequestMatcher("/welcome")).permitAll() .requestMatchers(new AntPathRequestMatcher("/health_check")).permitAll() .anyRequest().access( new WebExpressionAuthorizationManager( "hasIpAddress('127.0.0.1') or hasIpAddress('192.168.219.119')")) ) .sessionManagement((session) -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .headers((headers) -> headers.frameOptions((frameOptions) -> frameOptions.disable())); return http.build(); } private AuthorizationDecision hasIpAddress(Supplier<Authentication> authentication, RequestAuthorizationContext object){ return new AuthorizationDecision(ALLOWED_IP_ADDRESS_MATCHER.matches(object.getRequest())); } }2) https://github.com/joneconsulting/toy-msa/tree/springboot3.2 에서 'springboot3.2' 브런치의 'WebSecurityNew', 'IpAddressLoggingFilter' 코드로 실행 그리고 IpAddressLoggingFilter 코드에 대한 강의도 있나요?
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
jwt 검증 로직 질문
강의 예제 코드에서는 user-service 라는 msa 에서 jwt발급 과정에서 user id를 Subject에 설정해서 발급하고이후에 gateway 에서 검증할 때 그냥 subject 만 추출하고 null 이 아니면 그냥 검증되는 식으로작성되어있습니다 그리고 강의에서 bearer 토큰으로 받은 값에서 subject 를 추출해서 그걸 user_id 랑 비교해서 검증하면 된다는 언급이 있습니다 여기서 의문인 건 비교할 실제 db 가 user-service 에있다는 점입니다추후 강의에서 msa 간 통신하여 user-service에 있는 db 에서 userId 가져와서 비교하는 식으로 처리가 되는 걸까요 ?궁금해서 질문 남깁니다.
-
미해결카프카 완벽 가이드 - 커넥트(Connect) 편
debezium source connector에서 `poll.interval.ms` 파라미터
강의를 들으면서 헷갈리는 부분이 있는데요. debezium source connector등록시 설정하는 poll.interval.ms 파라미터는 debezium source connector가 source db로부터 데이터를 읽어들이는 데 대기하는 시간을 의미하는 걸까요? 아니면 debezium source connector가 kafka로 토픽을 전송하는데 대기하는 시간을 의미하는 걸까요?
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
CommentApiTest에서 readAll() 메서드에서의 SQLException
안녕하세요 쿠케님 ! CommentApiTest에서 readAll() 메서드의 body(CommentPageResponse.class); 부분에서 SQLException이 발생하는 것을 확인했습니다. 하지만 테이블 컬럼을 확인해보니, Comment 테이블에 content 컬럼이 분명히 존재하는걸 확인할 수 있었습니다.그리고 실제로 readAll() 메서드가 사용되는 쿼리에서는 content 컬럼이 사용되지도 않는데, 어째서 content 컬럼을 찾을 수 없다고 하는지 모르겠습니다. findAll()의 쿼리가 잘못됐나 해서 쿼리문을 직접 실행을 해도 잘 돌아가는걸 확인할 수 있었습니다. 코드도 첨부하겠습니다.
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
comment에서 findParent 질문드립니다.
학습 관련 질문을 최대한 상세히 남겨주세요!고민 과정도 같이 나열해주셔도 좋습니다.먼저 유사한 질문이 있었는지 검색해보세요.인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. findParent 메서드에서 return문을 보면 getDeleted로 부모 댓글이 삭제됐는지 여부를 확인하는 코드가 있는데, 부모가 삭제됐을때를 체크하는 이유가 무엇인가요? 부모 댓글이 삭제됐을 때, 더이상 하위 댓글을 달 수 없도록 하기 위함인가요? 하위 댓글이 남아있는 이상 부모 댓글은 "삭제됐습니다"와 같이 표시만 할 뿐이고 db에서 삭제되지는 않더라도 deleted=true로 바뀌어 있을텐데 만약 부모 댓글이 삭제됐을때 하위 댓글을 추가로 달 수 없는게 아니라, 계속해서 하위 댓글을 달 수 있다면 부모 댓글이 삭제된 상태라 하더라도 create 메서드를 실행할때 부모 댓글의 id를 넣어도 문제 없지 않은가라는 의문이 들어서요 create 메서드를 보면 parent가 null이라면 부모댓글이니까 null을 넣고 하위 댓글이라면 findparent에서 찾은 부모 댓글의 id를 넣어주는 코드인데, 부모 댓글이 deleted=true로 바꼈다 할지라도 DB 자체에서 완전히 삭제되지 않은 이상 즉, 하위 댓글이 여전히 남아있는 상황이라면 deleted=true로 바뀐 부모 댓글의 id를 넣어줘도 되지 않나?라는 생각이 들어서 질문드립니다!
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
feign + resilience4j 적용 시, fallback exception 처리 질문
Resilience4j - circuitbreaker를 보며 공부 중에 feign 에 Resilience4j의 cb나 bulkhead를 적용하게 되면 feignfallbackfactory 동작 시, throwable이 wrapping 되는 현상이 나타납니다.(ex. ExecutionException) 예를 들어, 4xx대의 에러, 즉 FeignException.BadRequest에 대한 분기 처리를 하려면 fallbackfactory 에서 throwable에 대한 원본 cause 를 추출하는 방법 밖에는 없을까요? (ex, throwable.getcause().getcause())
주간 인기글
순위 정보를
불러오고 있어요