Skip to content

Commit 2c19aa9

Browse files
committed
#90 chatforyou 서버 scaleout 대응 :: 쿠키 체크 실패 시 파드 재시작 로직 추가
- electron 대응을 위한 cookie 세팅 로직 수정 cookie -> ResponseCookie
1 parent 7449bd0 commit 2c19aa9

File tree

2 files changed

+53
-24
lines changed

2 files changed

+53
-24
lines changed

springboot-backend/src/main/java/webChat/service/routing/CookieCheckEvent.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import org.apache.http.Header;
88
import org.apache.http.HttpResponse;
99
import org.springframework.beans.factory.annotation.Value;
10+
import org.springframework.boot.SpringApplication;
1011
import org.springframework.boot.web.context.WebServerInitializedEvent;
12+
import org.springframework.context.ApplicationContext;
1113
import org.springframework.context.annotation.Lazy;
1214
import org.springframework.context.event.EventListener;
1315
import org.springframework.http.HttpHeaders;
@@ -27,6 +29,7 @@
2729
import java.nio.charset.StandardCharsets;
2830
import java.util.Map;
2931
import java.util.Random;
32+
import java.util.concurrent.CompletableFuture;
3033

3134
/**
3235
* Nginx가 발급하는 sessionAffinity 쿠키를 수집하고,
@@ -47,6 +50,7 @@ public class CookieCheckEvent {
4750
@Lazy
4851
private final RoutingInstanceProvider instanceProvider;
4952
private final KafkaTemplate<String, KafkaEvent> kafkaTemplate;
53+
private final ApplicationContext applicationContext;
5054

5155
@Value("${cookie.check.domain:https://localhost:8443}")
5256
private String cookieCheckDomain;
@@ -73,7 +77,7 @@ public void collectOwnCookieAsync() throws InterruptedException, BadRequestExcep
7377
log.info("=== 인스턴스 제공 이벤트 init 시작 ===");
7478
instanceProvider.initInstanceProviderEvent();
7579

76-
log.info("=== 85% 최적화된 쿠키 수집 시작 ===");
80+
log.info("=== 최적화 쿠키 수집 시작 ===");
7781

7882
// Phase 1: 파드간 협력
7983
String cookie = tryCollectFromPeersWithRetry();
@@ -135,7 +139,7 @@ private String tryCollectFromPeers() {
135139
String peerInstanceId = entry.getKey();
136140
String peerCookie = entry.getValue();
137141

138-
log.info("발견된 파드: {} -> 쿠키: {}", peerInstanceId, peerCookie);
142+
log.debug("발견된 파드: {} -> 쿠키: {}", peerInstanceId, peerCookie);
139143

140144
// 실제 nginx 응답을 통한 검증만 수행
141145
if (validateActualCookie(peerCookie)) {
@@ -237,7 +241,7 @@ private int calculateOptimalRetryCount(int activePods) {
237241
result = Math.max(result, MIN_RETRY_COUNT);
238242
result = Math.min(result, MAX_RETRY_COUNT);
239243

240-
log.info("최적화 계산: N = [{}], 목표성공률 = [{} %], 계산된시도횟수 = [{}], 적용시도횟수 = [{}]",
244+
log.info("최적화 계산: N = [{}], 목표성공률 = [{}%], 계산된시도횟수 = [{}], 적용시도횟수 = [{}]",
241245
activePods, (int)(TARGET_SUCCESS_RATE * 100), (int)Math.ceil(optimalRetries), result);
242246

243247
return result;
@@ -286,10 +290,30 @@ private void handleImprovedFallback() throws InterruptedException, BadRequestExc
286290
// 잘못된 값(instanceId)을 Redis에 저장하지 않음
287291
cookieCollected = false;
288292

293+
// 3초 후 애플리케이션 graceful shutdown
294+
scheduleApplicationShutdown();
295+
289296
// 필요시 모니터링을 위한 메트릭 기록
290297
log.warn("파드 {}는 sessionAffinity 기능을 사용할 수 없는 상태입니다", instanceProvider.getInstanceId());
291298
}
292299

300+
/**
301+
* 파드 재시작을 위한 애플리케이션 graceful shutdown
302+
*/
303+
private void scheduleApplicationShutdown() {
304+
CompletableFuture.runAsync(() -> {
305+
try {
306+
Thread.sleep(3000); // 3초 대기 (로그 출력 및 정리 시간)
307+
log.info("=== 파드 재시작을 위한 애플리케이션 graceful shutdown 시작 ===");
308+
int exitCode = SpringApplication.exit(applicationContext, () -> 1);
309+
System.exit(exitCode);
310+
} catch (InterruptedException e) {
311+
Thread.currentThread().interrupt();
312+
log.error("애플리케이션 종료 중 인터럽트 발생", e);
313+
}
314+
});
315+
}
316+
293317
/**
294318
* Kafka로 쿠키 요청 이벤트 발행
295319
*/
@@ -410,7 +434,7 @@ private String extractCookieValue(String cookieString) {
410434

411435
private void saveCookieAndComplete(String cookie) throws BadRequestException {
412436
String instanceCookie = redisService.getRedisDataByDataType(RedisKeyPrefix.INSTANCE_COOKIE_PREFIX.getPrefix() + instanceProvider.getInstanceId(), DataType.INSTANCE_COOKIE, String.class);
413-
if(!StringUtil.isNullOrEmpty(instanceCookie) && instanceCookie.contains("|")) {
437+
if(!StringUtil.isNullOrEmpty(cookieCheckDomain) && !StringUtil.isNullOrEmpty(instanceCookie) && instanceCookie.contains("|")) {
414438
log.warn("=== 이미 쿠키가 존재 : [{}] :: [{}] ===", instanceProvider.getInstanceId(), instanceCookie);
415439
return;
416440
}

springboot-backend/src/main/java/webChat/service/routing/impl/RoutingServiceImpl.java

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import lombok.RequiredArgsConstructor;
77
import lombok.extern.slf4j.Slf4j;
88
import org.apache.coyote.BadRequestException;
9+
import org.springframework.http.ResponseCookie;
910
import org.springframework.stereotype.Service;
1011
import webChat.model.redis.DataType;
1112
import webChat.model.redis.RedisKeyPrefix;
@@ -55,8 +56,6 @@ public void setRoutingInfo(HttpServletRequest request, HttpServletResponse respo
5556
this.setRoomRedirectCookie(response, redirectCount + 1, 60);
5657
}
5758
}
58-
59-
6059
}
6160

6261
@Override
@@ -107,29 +106,35 @@ public String getCookie(HttpServletRequest request, RoutingCookie routingCookie)
107106
}
108107

109108
private void setServerCookie(HttpServletResponse response, String nginxCookie){
110-
Cookie cookie = new Cookie(CHATFORYOU_SERVER_COOKIE.getName(), nginxCookie);
111-
cookie.setPath("/");
112-
cookie.setMaxAge(24 * 60 * 60); // 24시간
113-
cookie.setHttpOnly(true); // JavaScript 접근 차단 (보안)
114-
cookie.setSecure(false); // HTTP에서도 전송 (개발환경)
115-
response.addCookie(cookie);
109+
ResponseCookie responseCookie = ResponseCookie.from(CHATFORYOU_SERVER_COOKIE.getName(), nginxCookie)
110+
.path("/")
111+
.maxAge(24 * 60 * 60)
112+
.httpOnly(true)
113+
.secure(false)
114+
.sameSite("None")
115+
.build();
116+
response.setHeader("Set-Cookie", responseCookie.toString());
116117
}
117118

118119
private void setRoomIdCookie(HttpServletResponse response, String roomId){
119-
Cookie cookie = new Cookie(ROOM_ID_COOKIE.getName(), roomId);
120-
cookie.setPath("/");
121-
cookie.setMaxAge(60); // 60초
122-
cookie.setHttpOnly(false); // JavaScript 접근 차단 (보안)
123-
cookie.setSecure(false); // HTTP에서도 전송 (개발환경)
124-
response.addCookie(cookie);
120+
ResponseCookie responseCookie = ResponseCookie.from(ROOM_ID_COOKIE.getName(), roomId)
121+
.path("/")
122+
.maxAge(60)
123+
.httpOnly(false)
124+
.secure(false)
125+
.sameSite("None")
126+
.build();
127+
response.setHeader("Set-Cookie", responseCookie.toString());
125128
}
126129

127130
private void setRoomRedirectCookie(HttpServletResponse response, int redirectCount, Integer age){
128-
Cookie cookie = new Cookie(ROOM_REDIRECT_COUNT.getName(), String.valueOf(redirectCount));
129-
cookie.setPath("/");
130-
cookie.setMaxAge(age == null ? 60 : age); // 60초
131-
cookie.setHttpOnly(true); // JavaScript 접근 차단 (보안)
132-
cookie.setSecure(false); // HTTP에서도 전송 (개발환경)
133-
response.addCookie(cookie);
131+
ResponseCookie responseCookie = ResponseCookie.from(ROOM_REDIRECT_COUNT.getName(), String.valueOf(redirectCount))
132+
.path("/")
133+
.maxAge(age == null ? 60 : age)
134+
.httpOnly(true)
135+
.secure(false)
136+
.sameSite("None")
137+
.build();
138+
response.setHeader("Set-Cookie", responseCookie.toString());
134139
}
135140
}

0 commit comments

Comments
 (0)