본 포스팅은 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 보고 작성하였음
1. 어노테이션 기반
같은 코드가 계속해서 반복되는 부분 -> 어노테이션 기반으로 변경
- ex) IndexController에서 세션 값을 가져오는 부분을 메소드 인자로 세션값을 바로 받을 수 있도록 변경
1.1 @LoginUser 어노테이션 생성
@Target(ElementType.PARAMETER) ⓐ
@Retention(RetentionPolicy.RUNTIME) ⓑ
public @interface LoginUser { ⓒ
}
- ⓐ @Target(ElementType.PARAMETER)
- 해당 어노테이션이 생성될 수 있는 위치를 지정함
- PARAMETER로 지정했으니 메소드의 파라미터로 선언된 객체에서만 사용 할 수 있음
- ElementType.PACKAGE : 패키지 선언
- ElementType.TYPE : 타입 선언
- ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언
- ElementType.CONSTRUCTOR : 생성자 선언
- ElementType.FIELD : 멤버 변수 선언
- ElementType.LOCAL_VARIABLE : 지역 변수 선언
- ElementType.METHOD : 메서드 선언
- ElementType.PARAMETER : 전달인자 선언
- ElementType.TYPE_PARAMETER : 전달인자 타입 선언
- ElementType.TYPE_USE : 타입 선언
- ⓑ @Retention(RetentionPolicy.RUNTIME)
- Annotation 이 실제로 적용되고 유지되는 범위를 의미
- RetentionPolicy.RUNTIME
- 컴파일 이후에도 JVM 에 의해서 계속 참조가 가능
- 주로 리플렉션이나 로깅에 많이 사용됨
- RetentionPolicy.CLASS
- RetentionPolicy.SOURCE
- 컴파일 전까지만 유효함
- 즉, 컴파일 이후에는 사라지게 됨
- ⓒ @interface
1.2 LoginUserArgumentResolver
@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
private final HttpSession httpSession;
@Override
public boolean supportsParameter(MethodParameter parameter) { ⓐ
boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());
return isLoginUserAnnotation && isUserClass;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { ⓑ
return httpSession.getAttribute("user");
}
}
- ⓐ supportsParameter()
- 컨트롤러 메서드의 특정 파라미터를 지원하는지 판단함
- 상기 매서드에서는 파라미터에 @LoginUser 어노테이션이 붙어 있고, 파라미터 클래스 타입이 SessionUser.class인 경우 true를 반환함
- ⓑ resolveArgument()
- 파라미터에 전달할 객체를 생성함
- 상기 소스에서는 세션에서 객체를 가져옴
1.3 IndexController 개선
@RequiredArgsConstructor
@Controller
public class IndexController {
private final PostsService postsService;
@GetMapping("/")
public String index(Model model, @LoginUser SessionUser user) { ⓐ
model.addAttribute("posts", postsService.findAllDesc());
if (user != null) {
model.addAttribute("userName", user.getName());
}
return "index";
}
}
- ⓐ @LoginUser SessionUser user
- 기존 SessionUser user = (SessionUser) httpSession.getAttribute("user");로 가져오던 세션 정보 값이 개선 됨
- 해당 커스텀 어노테션을 사용하면 세션 정보를 가져올 수 있게 됨
2. 세션 저장소로 데이터베이스 사용하기
2.1 세션(Session)?
- 클라이언트와 웹서버 간 네트워크 연결이 지속 유지되고 있는 상태를 말함
- 즉, 사용자가 브라우저를 열어 서버에 접속한 뒤 접속을 종료할 때가지의 시점을 이야기함
- HTTP 프로토콜은 비접속형 프로토콜이므로, 매 접속마다 새로운 네트워크 연결이 이루어는데 세션이 연결 유지를 가능하게 함
- 정보들이 서버단에 저장되기 때문에 보안 면에서 쿠키보다 우수함
2.2 다중 서버 환경에서 세션관리 방법
(참조 : https://hyuntaeknote.tistory.com/6)
- Sticky Session (Load Balancer)
- Session Clustering (TOMCAT 세션)
- Session Storage
- Disk Based Database (MySQL, Oracle, MS-SQL 등과 같은 관계형 데이터베이스)
- In-Memory Database (Redis, Memcached 등)
2.2.1 Sticky Session
- 고정된 세션
- Load Balance가 기본적으로 라운드 로빈 방식으로 트래픽을 분산
- 특정 사용자가 접속을 시도했을 때 처음 접속된 서버로 계속해서 접속되도록 트래픽을 처리하는 방식
- 장점
- 사용자는 세션이 유지되는 동안 동일한 서버만을 사용
- 정합성 이슈에서 자유로움
- 단점
- 사용자가 접속해야하는 서버가 정해져 있기 때문에 특정 서버에 트래픽이 집중될 위험이 있음
- 사용자가 자신의 세션이 없는 다른서버 이용불가
- 가용성이 떨어짐
2.2.2 Session Clustering(세션 클러스터링)
- 여러 대의 컴퓨터들이 하나의 시스템 처럼 동작하도록 만드는 것
- WAS가 2대 이상 설치 형태일 때 동일한 세션으로 관리하는 것을 의미
- 장점
- 세션을 복제하여 사용자가 어떤 서버에 접속하더라도 데이터가 세션에 복제됨으로써 정합성 이슈 해결
- 서버 하나에 장애가 발생하더라도 서비스는 중단되지 않고 운영 가능
- 단점
- 모든 서버가 동일한 세션 객체를 가져야하기 때문에 많은 메모리가 필요
- 세션 저장소에 데이터가 저장될 때마다 모든 서버에 값을 입력해야함
- 서버 수에 비례하여 네트워크 트래픽이 증가하는 등 성능저하가 발생
2.2.3 Session Storage(세션 저장소)
- 서버가 아무리 늘어나도 세션 스토리지에 대한 정보만 각각의 서버에 입력해주면 세션을 공유할 수 있게됨
- 트래픽이 비정상적으로 몰리는 현상을 고려하지 않아도 됨
- 서버가 하나 장애가 발생하더라도 별도의 세션 저장소가 존재하기 때문에 서비스를 계속해서 제공할 수 있음 (가용성을 확보할 수 있음)
- 여러대의 서버가 하나의 세션을 사용하기 때문에 데이터 불일치가 발생하지 않음 (정합성 문제 해결)
- 별도의 세션 복제를 할 필요 없기에 성능적인 문제도 해결이 가능
- But, 세션 저장소 서버 장애를 방지하기 위해 동일한 세션 저장소 하나를 더 구성하여 복제하는것이 좋음
- Disk Based Database
- 디스크에 저장 및 사용
- MySQL, Oracle, MS-SQL 등과 같은 관계형 데이터베이스
- 세션 저장소를 할 수 있는 가장 쉬운방법
- 많은 설정 필요 없음
- 처리 속도가 오래걸림, DB I/O가 발생하여 성능상 이슈가 발생할 수 있음
- 빈번한 Read/Write가 이루어지는 세션 저장소로써 Disk 기반의 데이터베이스는 상대적으로 I/O 속도가 느리기 때문에 적합하지 않음
- 보통 로그인 요청이 많이 없는 백오피스, 사내 시스템 용도에서 많이 사용
- In-Memory Database
- 메모리에 저장 및 사용
- Redis, Memcached 등이 있음
- 세션에 저장하는 데이터들은 영구적으로 저장하는 데이터가 아님
- In-Memory 데이터베이스는 전원 공급이 중단되면 데이터를 잃어버리지만, 세션 저장소에 저장되는 데이터는 상대적으로 피해가 적기 때문에 In-Memory 데이터베이스 사용이 적합합니다. (일부 데이터베이스는 Replication을 지원하기 때문에 가용성을 확보할 수 있습니다.)
- 데이터를 메모리에서 Read/Write 할 수 있다는 점에서 빠른 속도로 데이터를 처리할 수 있기 때문에 세션 저장소로써 적합
2.3 세션 저장소로 데이터베이스(Disk Based Database) 설정
- gradle에 의존성 추가
- implementation 'org.springframework.session:spring-session-jdbc'
- application.properties에 추가
- spring.session.store-type=jdbc