고정 세션 는 특정 사용자의 요청을 해당 사용자의 세션이 있는 동일한 서버로 리디렉션하는 것을 의미합니다. 서버에 장애가 발생하면 해당 서버에 연결된 모든 사용자의 세션이 손실되므로 안티 패턴으로 간주됩니다.
사용자와 서버 간의 연결은 일반적으로 로드 밸런서를 통해 구성되며, 다음과 같은 간단한 로드 밸런싱 전략이 사용됩니다. 라운드-robin 는 헤비 유저가 모두 같은 노드에 있을 수 있기 때문에 요청을 균등하게 분산시키기에 충분하지 않은 경우가 많습니다. 스티키 세션을 피할 수 있는 방법은 여러 가지가 있지만, 애플리케이션이 사용자의 데이터를 HTTPSession에 저장하는 경우 상당한 리팩토링 없이 사용할 수 있는 옵션은 다소 제한적입니다.
이 문제에 대한 한 가지 빠른 해결 방법은 서버의 메모리를 사용하는 대신 데이터베이스에 세션을 저장하는 것입니다. 이 시나리오에서는 어떤 노드가 요청을 수신하든 데이터 저장소에서 직접 사용자의 세션을 로드합니다. 이 접근 방식은 컨테이너별 솔루션보다 간단하며 데이터베이스의 다른 모든 항목처럼 세션을 쿼리할 수 있습니다.
내부 키-값 엔진을 사용하고 내부 캐시 계층을 활용하여 최근에 사용한 세션을 메모리에 보관하기 때문에 이 시나리오에 특히 적합한 솔루션입니다. 실제로는 대규모로도 잘 작동하는 솔루션이라는 뜻입니다. 이것이 바로 저희가 커뮤니티 지원을 추가하는 이유입니다. 봄 세션:
카우치베이스 봄 세션 를 사용하면 데이터베이스에 저장하여 클러스터된 세션을 쉽게 지원할 수 있으며 개발자 입장에서는 완전히 투명합니다. 다음 종속성을 추가하기만 하면 됩니다:
|
1 2 3 4 5 |
<dependency> <groupId>io.github.couchbaselabs</groupId> <artifactId>spring-session-data-couchbase</artifactId> <version>1.1</version> </dependency> |
를 추가한 다음 메인 클래스에서 @EnableCouchbaseHttpSession 어노테이션:
|
1 2 3 4 5 6 7 8 9 |
@SpringBootApplication @EnableCouchbaseHttpSession public class SessionStoreApplication { public static void main(String[] args) { SpringApplication.run(SessionStoreApplication.class, args); } } |
를 입력하면 끝입니다! 이제부터는 Spring이 자동으로 HTTPS세션을 Couchbase에 저장합니다:
|
1 2 3 4 5 6 7 8 |
@GetMapping("/newSession") public String newSession(HttpServletRequest request, Model model) throws Exception { request.getSession().invalidate(); HttpSession newSession = request.getSession(); newSession.setAttribute("foo", new Foo("key", "value")); return defaultPage(model, newSession); } |
기본적으로 세션은 데이터베이스에 ""와 같은 유형의 문서로 저장됩니다.세션“:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
//key : 5b357ade-6059-4d16-aea3-6f784765e7b5 { "_principal": null, "_interval": 1800, "_expireAt": 1554743279889, "_created": 1554741479889, "_accessed": 1554741479889, "_type": "sessions", "_attr": "\"rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABdAADZm9vc3IAHWNvbS5jYi5zZXNzaW9uc3RvcmUubW9kZWwuRm9vO5F+XaK9pV0CAAJMAAphdHRyaWJ1dGUxdAASTGphdmEvbGFuZy9TdHJpbmc7TAAKYXR0cmlidXRlMnEAfgAEeHB0AAZ2YWx1ZTF0AAZ2YWx1ZTJ4\"" } |
그러나 유형 속성의 이름, 유형 값 및 세션 지속 시간을 변경할 수 있습니다:
|
1 2 3 4 5 6 7 8 9 |
@SpringBootApplication @EnableCouchbaseHttpSession(typeName = "myType", typeValue = "myValueType", maxInactiveIntervalInSeconds = 1800) public class SessionStoreApplication { public static void main(String[] args) { SpringApplication.run(SessionStoreApplication.class, args); } } |
사용자 세션 쿼리하기
모든 세션의 데이터는 다음과 같은 어트리뷰트에 바이너리로 저장됩니다. _attr:
|
1 2 3 4 |
{ ... "_attr": "\"rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABdAADZm9vc3IAHWNvbS5jYi5zZXNzaW9uc3RvcmUubW9kZWwuRm9vO5F+XaK9pV0CAAJMAAphdHRyaWJ1dGUxdAASTGphdmEvbGFuZy9TdHJpbmc7TAAKYXR0cmlidXRlMnEAfgAEeHB0AAZ2YWx1ZTF0AAZ2YWx1ZTJ4\"" } |
Spring은 세션에 어떤 객체 유형이 있는지 알지 못하므로 사람이 읽을 수 있는 형식으로 변환할 수 있는 쉬운 방법이 없습니다. 속성을 설정하여 이 제한을 극복할 수 있습니다. keepStringAsLiteral 에서 사실과 같이 EnableCouchbaseHttpSession 어노테이션:
|
1 2 3 4 5 6 7 8 9 |
@SpringBootApplication @EnableCouchbaseHttpSession(keepStringAsLiteral = true) public class SessionStoreApplication { public static void main(String[] args) { SpringApplication.run(SessionStoreApplication.class, args); } } |
keepStringAsLiteral 가 알려줄 것입니다. 카우치베이스 봄 세션 를 사용하여 모든 세션의 문자열 속성을 문서 내의 최상위 프로퍼티로 저장할 수 있습니다. 예를 들어, 세션에 직접 인스턴스를 추가하는 대신 잭슨의 오브젝트 맵퍼:
|
1 2 |
ObjectMapper mapper = new ObjectMapper(); session.setAttribute("key", mapper.writeValueAsString(myClassInstance)) |
그런 다음 세션 카트를 읽어야 할 때 다시 객체로 변환합니다:
|
1 2 |
ObjectMapper mapper = new ObjectMapper(); mapper.readValue( session.getAttribute("key").toString(), MyClass.class); |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
//key : 5b2a2487-4825-43de-b089-1b61703556b2 { "_principal": null, "_interval": 1800, "_expireAt": 1554746972015, "_created": 1554745163803, "_accessed": 1554745172015, "key": "{\"shoppingCart\":{\"created\":1554745170784,\"items\":[{\"itemName\":\"Tennis Shoes\",\"price\":38.25186017511709,\"quantity\":3}]},\"user\":{\"username\":\"robertst\",\"phoneNumber\":\"(500)-383-1668\"},\"location\":{\"address\":\"90 Arrowhead Avenue Jonesboro, GA 30236\",\"country\":\"USA\",\"coordinates\":{\"lat\":10,\"lon\":37}}}", "_type": "sessions", "_attr": "\"rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeA==\"" } |
여전히 _att 어트리뷰트를 사용할 수 있습니다. 하지만 이제 객체에 대한 키입니다, 는 이전 예제에서 세션에 추가한 객체와 정확히 일치합니다.
이제 세션을 쿼리해 보겠습니다, N1QL 라는 함수가 있습니다. DECODE_JSON를 사용하여 JSON으로 인코딩된 문자열을 객체로 언마샬링할 수 있습니다:
|
1 2 3 4 5 |
SELECT meta().id as id, _created, ARRAY_COUNT(DECODE_JSON(sessionCart).shoppingCart.items) FROM sessionstore ORDER BY _created DESC LIMIT 10 |
참고: 프로덕션 환경에서는 쿼리 시점에 디코딩하는 대신 디코딩된 오브젝트로 인덱스를 생성하는 것이 좋습니다.
Couchbase 스프링 세션에 대해 자세히 알아보려면 다음을 확인하세요. 튜토리얼
궁금한 점이 있으면 언제든지 다음 주소로 문의해 주세요. 데니스브로사