티스토리 뷰
얼마전 신입분이 현재 개발중인 소스에 결함이 있다며 부르셨다. 내용을 보니 JSESSIONID가 같아서 한쪽에서 세션이 풀리는 문제였는데, 그렇게 설명은 해드렸지만 좀 더 자세하게 알아볼 필요가 있어서 글로 남긴다.
JSESSIONID는 서블릿 컨테이너에서 접속한 사용자를 식별하기 위한 식별자라고 보면 된다. 톰캣에서는 아래와 같이 JSESSIONID를 생성한다.(https://tomcat.apache.org/ 에서 Source Code Distributions에서 tar.gz이나 zip으로 소스를 받은 뒤 열어보면 확인할 수 있다.)
package org.apache.catalina.util;
public class StandardSessionIdGenerator extends SessionIdGeneratorBase {
@Override
public String generateSessionId(String route) {
byte random[] = new byte[16];
int sessionIdLength = getSessionIdLength();
// Render the result as a String of hexadecimal digits
// Start with enough space for sessionIdLength and medium route size
StringBuilder buffer = new StringBuilder(2 * sessionIdLength + 20);
int resultLenBytes = 0;
while (resultLenBytes < sessionIdLength) {
getRandomBytes(random);
for (int j = 0;
j < random.length && resultLenBytes < sessionIdLength;
j++) {
byte b1 = (byte) ((random[j] & 0xf0) >> 4);
byte b2 = (byte) (random[j] & 0x0f);
if (b1 < 10) {
buffer.append((char) ('0' + b1));
} else {
buffer.append((char) ('A' + (b1 - 10)));
}
if (b2 < 10) {
buffer.append((char) ('0' + b2));
} else {
buffer.append((char) ('A' + (b2 - 10)));
}
resultLenBytes++;
}
}
if (route != null && route.length() > 0) {
buffer.append('.').append(route);
} else {
String jvmRoute = getJvmRoute();
if (jvmRoute != null && jvmRoute.length() > 0) {
buffer.append('.').append(jvmRoute);
}
}
return buffer.toString();
}
}
보통 Session에 아이디를 저장하고 값을 읽어올 때 아래와 같이 코드를 짠다.
String id = session.getAttribute("id");
그런데 사용자가 다름에도 id는 "id"라는 동일한 키로 가져온다. 이것이 가능한 이유가 JSESSIONID때문이다. JSESSIONID를 키로 세션 데이터를 저장하기 때문에 위와 같은 코드일지라도 JSESSIONID로 먼저 value를 찾고, 그 value에서 "id"를 key로 value를 찾는 것이다.
하나의 세션에 대해 하나의 JSESSIONID가 주어진다. 그렇다면 만약 서버가 여러대라면 어떻게 될까? 로그인한 유저라면 A서버에나 B서버에나 동일한 요청을 했을 때 동일한 응답을 받을 수 있어야 하는데, 만약 세션이 A서버에 한정되어 있다면 B서버에 요청했을 때 존재하지 않는 JSESSIONID이므로 성공적인 응답을 받을 수 없다.(분산 서버 환경에서의 세션 불일치 문제)
이를 해결하는 방법은 한 접속자에 대한 요청은 최초 요청을 처리한 서버로만 고정하는 방법, 여러 서버가 한곳에 세션정보를 공유해서 사용하는 방법 두 가지가 있다.
첫번째 방법은 Sticky Session으로, 부하가 불균형하게 분산되고 확장성과 장애 복구에 어려움이 있고 유지 관리 비용이 발생한다는 단점이 있다.
두번째 방법은 세션 클러스터링으로, 한 대의 서버에서 장애가 발생해도 다른 서버들에는 이상이 없고, 확장성을 높일 수 있고, 부하가 골고루 분산된다는 장점이 있다. 단점으로는 데이터의 일관성과 동기화를 위한 추가적인 리소스가 필요할 수도 있고, 세션 상태 관리와 동기화를 유지하기 위한 리소스가 필요할 수도 있다는 것이다.
세션 클러스터링을 하는 데에 Redis를 사용할 수 있다. 세션 데이터를 저장할 때 기본 키-값 저장 방식으로 각 세션을 고유키로 저장하거나, Hash 데이터 구조를 사용하여 하나의 키에 여러 세션 속성을 저장하는 방법 중 저장 방식을 선택할 수 있다.
'업무 경험 및 성과' 카테고리의 다른 글
간단하게 디스크 용량이 부족할 경우의 테스트 환경 만들기 (0) | 2023.09.04 |
---|---|
[회고] 시스템 개발 프로젝트 (0) | 2023.07.21 |
bootstrap-datepicker로 일 선택, 월 선택 전환 가능하게 만들기 (0) | 2023.06.28 |
비동기작업이 끝났는지 CountDownLatch로 확인하기 (0) | 2023.06.19 |
산출물 취합에서 공동작업으로, 시간 절약 (0) | 2023.03.26 |