Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication(scanBasePackages = "com.momogo")
@EntityScan(basePackages = "com.momogo.core.domain")
@EnableJpaRepositories(basePackages = "com.momogo.core.domain")
@EnableScheduling
public class MomogoApiApplication {
public static void main(String[] args) {
SpringApplication.run(MomogoApiApplication.class, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ public List<SseEmitter> findAllByUserId(UUID userId) {
return emitters.getOrDefault(userId, List.of());
}

// 이 인스턴스에 연결된 모든 emitter 조회 (하트비트 브로드캐스트용)
public List<SseEmitter> findAll() {
return emitters.values().stream()
.flatMap(List::stream)
.toList();
}

// 연결이 정상종료되거나, 타임아웃되거나, 에러가 났을 때 목록에서 제거
// 유저의 emitter가 다 없어지면 메모리 누수 방지를 위해 그 유저의 key 자체도 지움
public void remove(UUID userId, SseEmitter emitter) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.momogo.api.notification.scheduler;

import com.momogo.api.notification.registry.NotificationEmitterRegistry;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

/*
* SSE 연결이 오래 idle 상태면 중간 프록시/로드밸런서가 죽은 연결로 오해해 끊어버릴 수 있음
* 일정 주기로 빈 코멘트를 보내 연결이 살아있음을 알림
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class NotificationSseHeartbeatScheduler {

private static final long HEARTBEAT_INTERVAL = 30_000L; // 30초

private final NotificationEmitterRegistry emitterRegistry;

@Scheduled(fixedRate = HEARTBEAT_INTERVAL)
public void sendHeartbeat() {
for (SseEmitter emitter : emitterRegistry.findAll()) {
try {
emitter.send(SseEmitter.event().comment("heartbeat"));
Comment thread
idktomorrow marked this conversation as resolved.
} catch (IOException | IllegalStateException e) {
log.warn("[NotificationSseHeartbeatScheduler] 하트비트 전송 실패, 연결 정리", e);
emitter.completeWithError(e);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
}