-
Notifications
You must be signed in to change notification settings - Fork 56
[김경린] Sprint12 #504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: part3-김경린
Are you sure you want to change the base?
[김경린] Sprint12 #504
The head ref may contain hidden characters: "part3-\uAE40\uACBD\uB9B0"
Conversation
…e, orElseGet, ifPresentë¥등으로 수정
…개변수로 받지 않고 내내부에서 변경하도록 변경
…ê´ë메서드를 구현한 구현체 수정, 단위 테스트 코드 수정
…s.get().toString() 으로 변êã경, ìFileUserRepository와 FileChannelRepository 생성자ì� ì수정
- JPA Repository 네이밍 규칙 준수
- XorCsrfTokenRequestAttributeHandler 대신 CsrfTokenRequestAttributeHandler (XOR 기능이 없는 기본 핸들러)로 교체
- 비동기 설정및 재시도 설정 - 재시도 횟수 초과 후 복구 로직 추가 - 실패 정보 AsyncTaskFailure 추가
- 메서드 시그니처 변경 : 반환값 UUID -> CompletableFuture<UUID>
- TransactionTemplate 을 통해 트랜잭션 관리
- 알림 관련 API 구현 - ReadStatus 엔티티 리팩토링 - 비동기 방식 + 이벤트 리스너로 알림 생성
- Spring Cache, Caffeine Cache 이용을 위한 셋업 - 사용자별 채널 목록 조회, 사용자별 알림 목록 조회, 사용자 목록 조회에 로컬 캐시 적용
- @AuthenticationPrincipal 어노테이션을 통해 받아오는 객체 변경
- 웹소켓 + STOMP 설정 - ChatController 구현, 발행자의 송신, 저장, 구독자에게 수신
- 1000 -> 100
|
안녕하세요 경린님, 제가 리뷰가 조금 늦어지고 있는데요, 6/26(목) 멘토링 전까지 코드 한번 살펴보고 리뷰 남겨볼게요. |
yeongunheo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
경린님, 안녕하세요.
웹소켓 & SSE 설정 잘 해주셔서 몇가지 참고할만한 코멘트만 남겨두었습니다.
그리고 질문 주신 내용 중에 아래 내용은 제가 로컬에서 테스트해봤는데 원인을 잘 모르겠네요ㅠ
비공개 채널을 생성한 당사자에게는 뜨지 않는 오류가 있습니다 ...!
경린님 코드 단에서는 별다른 특이한 점을 발견하지 못했습니다.
프론트엔드 문제일 수 있으니 우선 넘어가도 좋을 것 같아요.
감사합니다.
| // Subscribe 엔드 포인트 : /sub/channels.{channelId}.messages | ||
| String destination = "/sub/channels." + request.channelId() + ".messages"; | ||
| log.info("구독자에게 메세지 수신 시도 : destination={}, content={}", destination, messageDto.content()); | ||
| template.convertAndSend(destination, messageDto); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
웹소켓을 이용한 메시지 송신 잘 구현해주셨네요 👍
| # 통신할 백엔드 서버 그룹 정의 | ||
| upstream backend_servers{ | ||
| server backend:8080; | ||
| } | ||
|
|
||
| # 실제 웹 서버의 동작을 정의 | ||
|
|
||
| server{ | ||
| listen 80; | ||
|
|
||
| # 1. API 요청 처리 (/api/*) | ||
| location /api/ { | ||
| proxy_pass http://backend_servers; | ||
| proxy_set_header Host $host; | ||
| proxy_set_header X-Real-IP $remote_addr; | ||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
| proxy_set_header X-Forwarded-Proto $scheme; | ||
| } | ||
|
|
||
| # 2. 웹소켓 요청 처리 (/ws/*) | ||
| location /ws/ { | ||
| proxy_pass http://backend_servers; | ||
| proxy_http_version 1.1; | ||
| proxy_set_header Upgrade $http_upgrade; | ||
| proxy_set_header Connection "Upgrade"; | ||
| proxy_set_header Host $host; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로컬에서 SSE와 WebSocket 이 정상 작동하는데, 리버스 프록시를 통해 3000번 포트로 들어가니 SSE와 WebSocket 연결이 되지 않고 있습니다.
웹브라우저 -> 리버스프록시 -> 웹서버
리버스프록시 -> 웹서버 이 구간에서 리버스프록시가 웹서버로 보내는 요청을 일정시간이 지나면 종료하는게 원인이라고 생각해요.
그래서 SSE/웹소켓 요청에서 타임아웃을 늘려줘야합니다!
# 통신할 백엔드 서버 그룹 정의
upstream backend_servers {
server backend:8080;
keepalive 32; # 연결 풀링으로 성능 향상
}
# 실제 웹 서버의 동작을 정의
server {
listen 80;
# 3. SSE 요청 처리 (/api/sse) - 먼저 처리해야 함
location /api/sse {
proxy_pass http://backend_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE 전용 설정 (WebSocket 설정과 다름!)
proxy_set_header Connection ''; # 빈 문자열로 설정
proxy_set_header Cache-Control 'no-cache';
proxy_set_header Content-Type 'text/event-stream';
proxy_set_header X-Accel-Buffering 'no';
# 버퍼링 완전 비활성화
proxy_buffering off;
proxy_cache off;
# 타임아웃 설정 (SSE는 장시간 연결)
proxy_read_timeout 86400s; // 추가
proxy_send_timeout 86400s; // 추가
proxy_connect_timeout 60s; // 추가
# 청크 인코딩 활성화
chunked_transfer_encoding on;
}
# 1. API 요청 처리 (/api/*)
location /api/ {
proxy_pass http://backend_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection ''; # Keep-alive 연결 유지
}
# 2. 웹소켓 요청 처리 (/ws/*)
location /ws/ {
proxy_pass http://backend_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 전용 설정
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# 버퍼링 비활성화
proxy_buffering off;
proxy_cache off;
# WebSocket 타임아웃 설정
proxy_read_timeout 86400s; // 추가
proxy_send_timeout 86400s; // 추가
proxy_connect_timeout 60s; // 추가
}
# 4. 그 외 모든 요청 처리 (프론트엔드)
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
# 정적 파일 캐싱
expires 1h;
add_header Cache-Control "public, immutable";
}
}
| /** | ||
| * SseEmitter 객체를 Thread-safe한 메모리 구조에서 안전하게 관리 | ||
| * <p> | ||
| * Thread-safe한 메모리 구조 = ConcurrentHashMap | ||
| **/ | ||
| private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>(); | ||
| private final Map<String, Object> eventCache = new ConcurrentHashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConcurrentHashMap 사용해서 Thread-safe하게 잘 만들어주셨네요 💯
요구사항
기본 요구사항
웹소켓 구현하기
웹소켓 환경 구성
spring-boot-starter-websocket의존성을 추가하세요.implementation 'org.springframework.boot:spring-boot-starter-websocket'웹소켓 메시지 브로커 설정
SimpleBroker를 사용하세요./ws로 설정하고,SockJS연결을 지원해야 합니다.메시지 수신
/sub/channels.{channelId}.messagesMessageDto를 사용하세요.메시지 송신
/pub/messagesMessageCreateRequestSSE 구현하기
SSE 환경을 구성하세요.
GET /api/sseSseEmitter객체를 스레드 세이프한 메모리 구조에서 안전하게 관리해야 합니다.onCompletion,onTimeout,onError이벤트 핸들러에서 emitter를 제거합니다.Last-Event-ID를 전송해 이벤트 유실 복원이 가능하도록 해야 합니다.- 고유한 ID는 userId + "_" + 날짜 = 이벤트id = emitterId 로 했다.
기존에 클라이언트에서 폴링 방식으로 주기적으로 요청하던 데이터를 SSE를 이용해 서버에서 실시간으로 전달하는 방식으로 리팩토링하세요.
새로운 알림 이벤트 전송
- 새 알림이 생성되었을 때 클라이언트에 이벤트를 전송하세요.
- 클라이언트는 이 이벤트를 수신하면 알림 목록에 알림을 추가합니다.
- 이벤트 명세
파일 업로드 상태 변경 이벤트 전송
- 파일 업로드 상태가 변경될 때 이벤트를 발송하세요.
- 유저 프로필 업로드/수정, 메세지 이미지 업로드 시 당사자에게 발송한다.
- 클라이언트는 해당 상태를 수신하여 UI를 다시 렌더링합니다.
- 이벤트 명세
채널 목록 갱신 이벤트 전송
- 채널 목록을 업데이트해야 할 경우, 이벤트를 발송하세요.
- createPublicChannel(), createPrivateChannel(), updateChannel(), deleteChannelById() 메서드가 사용되면 목록이 바뀌어야 한다.
- 클라이언트는 해당 이벤트를 수신하면 채널 목록을 재조회합니다.
- 이벤트 명세
사용자 목록 갱신 이벤트 전송
ㄴ 이 부분 조건을 이해하지 못했습니다...
- 사용자 목록을 업데이트해야 할 경우, 이벤트를 발송하세요.
- 클라이언트는 해당 이벤트를 수신하면 사용자 목록을 재조회합니다.
- 이벤트 명세
배포 아키텍처 구성하기
Reverse Proxy/api/*,/ws/*요청은 Backend 컨테이너로 프록시 처리합니다./usr/share/nginx/html등)에 복사하세요.3000번 포트를 통해 접근할 수 있어야 합니다.BackendReverse Proxy를 통해/api/*,/ws/*요청이 이 서버로 전달됩니다.DB,Memory DB,Message BrokerBackend컨테이너가 접근 가능한 다음의 인프라 컨테이너들을 구성하세요제 프로젝트에는 Redis와 Kafka 가 적용되어 있지 않지만 도커를 통해 띄우는 건 해두었습니다!
심화 요구사항
주요 변경사항
스크린샷
멘토에게
비공개 채널을 생성한 당사자에게는 뜨지 않는 오류가 있습니다 ...!
로컬에서 SSE와 WebSocket 이 정상 작동하는데, 리버스 프록시를 통해 3000번 포트로 들어가니 SSE와 WebSocket 연결이 되지 않고 있습니다.
리버스 프록시 연결(=포트 번호 변경)이후 SSE와 WebSocket의 설정을 다룰 때 변경해야 하는 게 있을까요?