spring_session_redis_masterreplica
Spring Session Redis Master-Replica
레디스 개발자 교육 신청 |
레디스 정기점검/기술지원 Redis Technical Support |
레디스 엔터프라이즈 서버 Redis Enterprise Server |
---|
Java Program Project Configuration
구성 소프트웨어 버전
Spring: 6.0.14
Srping-session-data-redis:3.1.3
Lettuce: 6.2.7
Redis: 7.0.10
Java: 17
Project 생성: start.spring.io
build.gradle
dependencies에 spring-session-data-redis 가 있는지 확인한다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.session:spring-session-data-redis'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
application.properties
Standalone 설정에서 추가된 항목에서 중요한 것은 replicas.host, port와
readFrom이 추가되었다.
readFrom은 조회 명령을 복제(replica) 서버에서 실행하게 해서 부하분산을 하는 기능입니다.
Lettuce 설정에 있습니다. 여기서는 replicaPreferred으로 설정했습니다.
자세한 내용은 여기(readFrom)를 보세요.
참고
설정에서 spring.session.store-type:redis 이나 @EnableRedisHttpSession이 필요하다는 글도 있으나 현재 테스트 버전에서는 필요하지 않았다.
Java Program
Main Class: SpringSessionRedisMasterReplicaApplication
Class RedisMasterReplicaInfo
application.properties 설정 값을 읽어오는 클래스
Class RedisMasterReplicaConfig
master.host, port, replicas.host, port, readFrom 등의 정보로
redisConnectionFactory를 만든다.
application.properties에 readFrom이 없을 경우 ANY로 설정하게 했다.
Class SessionController
- hello1(): session.setAttribute("hello1","Charlie");
- hello2(): session.setAttribute(); 2회 실행
- hello3(): ① 속성명 지정해서 값 가져오기. ② 모든 속성명, 값 가져오기. ③ Session ID, 생성일시, 마지막 액세스(접근) 일시 가져오기
- hello4(): Redis HGETALL 명령으로 세션 데이터 가져오기. 메서드 명은 redisHash.entries()
Redis Master-Replica 구성도
Redis Server에서 실행되는 명령과 클라이언트 로그
Redis Server에서 실행되는 명령 확인은 redis-cli의 monitor 기능을 사용했습니다.
방법: redis-cli -h ip -p port -a password monitor
http://localhost:8080/hello1
hello1() source
레디스 서버에서 실행되는 명령
처음 실행: 새 브라우저에서 처음 실행할 때
Master (마스터)
- 총 10 개 명령 실행: 레디스 서버 접속 관련 명령 6개 실행, Spring Session 명령 4개 실행.
- HMSET 명령은 4개 필드 lastAccessedTime, maxInactiveInterval, creationTime, sessionAttr를 저장한다.
- 가능한 한 줄에 보이게 하려고 'spring:session:sessions:a1ad6ded-b803-423f-a14e-52b4af3e64fe' 에서 'spring:session:sessions:' 부분을 삭제했다.
1701456142.575944 [Client:12660] "ROLE"
1701456142.611607 [Client:12665] "HELLO" "3" "AUTH" "(redacted)" "(redacted)" "SETNAME" "redisgate"
1701456142.621820 [Client:12665] "CLIENT" "SETNAME" "lettuce#MasterReplicaTopologyRefresh"
1701456142.629173 [Client:12665] "PING" "NODES"
1701456142.679598 [Client:12667] "HELLO" "3" "AUTH" "(redacted)" "(redacted)" "SETNAME" "redisgate"
1701456142.689638 [Client:12667] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"maxInactiveInterval" "\xac\xed\x00\x05sr\x00\x11java.lang.Integer"
"creationTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello1" "\xac\xed\x00\x05t\x00\aCharlie"
1701456142.713470 [Client:12667] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701457942547"
1701456142.835641 [Client:12667] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456142.842052 [Client:12667] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701457943459"
Replica1 (복제1)
- 총 11 개 명령 실행: 레디스 서버 접속 관련 명령 6개 실행, 마스터에서 전파된 Spring Session 명령 5개 실행.
- "SELECT" 명령은 마스터에서 명령이 처음 전파될때 DB를 선택하는 명령이 추가된다.
1701456142.576375 [Client:12661] "ROLE"
1701456142.609771 [Client:12664] "HELLO" "3" "AUTH" "(redacted)" "(redacted)" "SETNAME" "redisgate"
1701456142.621824 [Client:12664] "CLIENT" "SETNAME" "lettuce#MasterReplicaTopologyRefresh"
1701456142.630673 [Client:12664] "PING" "NODES"
1701456142.689914 [Master:18510] "SELECT" "0"
1701456142.689943 [Master:18510] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"maxInactiveInterval" "\xac\xed\x00\x05sr\x00\x11java.lang.Integer"
"creationTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello1" "\xac\xed\x00\x05t\x00\aCharlie"
1701456142.713615 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701457942547"
1701456142.753148 [Client:12669] "HELLO" "3" "AUTH" "(redacted)" "(redacted)" "SETNAME" "redisgate"
1701456142.835792 [Master:18510] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456142.842148 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701457943459"
Replica2 (복제2)
- 총 20 개 명령 실행: 레디스 서버 접속 관련 명령 6개 실행, 마스터에서 전파된 Spring Session 명령 5개 실행, Client에서 실행된 명령 9개.
- Client에서 실행된 명령 9개는 조회 명령은 복제서버에서 실행되도록 readFrom을 설정했기 때문이다.
- "SELECT" 명령은 마스터에서 명령이 처음 전파될때 DB를 선택하는 명령이 추가된다.
1701456142.576256 [Client:12662] "ROLE"
1701456142.612773 [Client:12666] "HELLO" "3" "AUTH" "(redacted)" "(redacted)" "SETNAME" "redisgate"
1701456142.621951 [Client:12666] "CLIENT" "SETNAME" "lettuce#MasterReplicaTopologyRefresh"
1701456142.630825 [Client:12666] "PING" "NODES"
1701456142.689891 [Master:18510] "SELECT" "0"
1701456142.689919 [Master:18510] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"maxInactiveInterval" "\xac\xed\x00\x05sr\x00\x11java.lang.Integer"
"creationTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello1" "\xac\xed\x00\x05t\x00\aCharlie"
1701456142.713600 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701457942547"
1701456142.748495 [Client:12668] "HELLO" "3" "AUTH" "(redacted)" "(redacted)" "SETNAME" "redisgate"
1701456142.757261 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456142.808719 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456142.829065 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456142.835802 [Master:18510] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456142.842159 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701457943459"
1701456142.853307 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456142.860690 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456142.931101 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456142.938580 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456142.945206 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456142.956658 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
두 번째 실행
Master (마스터)
- 두 번째 실행할 때는 마스터에서는 2개 명령이 실행된다. 마스터 서버의 부담이 줄었다.
- HGETALL 명령이 실행되면 HMSET 명령으로 lastAccessedTime을 수정한다. 그리고 PEXPIREAT 명령으로 만료(삭제)일시를 수정한다. -> HGETALL 명령은 복제2 에서 실행되었다.
- "HMSET"과 "PEXPIREAT" 명령은 복제서버로 전파된다.
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello1" "\xac\xed\x00\x05t\x00\aCharlie"
1701456374.994586 [Client:12667] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458175569"
Replica1 (복제1)
- 마스터에서 전파된 2개 명령이 실행되었다.
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello1" "\xac\xed\x00\x05t\x00\aCharlie"
1701456374.994692 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458175569"
Replica2 (복제2)
- 조회 명령 4개와 마스터에서 전파된 명령 2개가 순서대로 실행되었다.
- 부하분산을 위한 readFrom 설정 효과로 조회 명령이 복제2에서 실행되었다.
1701456374.937656 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456374.985901 [Master:18510] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello1" "\xac\xed\x00\x05t\x00\aCharlie"
1701456374.994708 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458175569"
1701456375.002342 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456375.008803 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
명령이 마스터와 복제에서 분산 실행되므로 문제가 발생할까?
마스터와 복제2에서 분산되어서 실행되는 명령을 시간 순으로 살펴보자.
- 927ms: 복제2 HGETALL 명령 실행
- 937ms: 복제2 EXISTS 명령 실행, 10ms 차이
- 985ms: 마스터 HMSET 명령 실행, 48ms 차이
- 985ms: 복제2 HMSET 명령 실행, 0ms 차이
- 994ms: 마스터 PEXPIREAT 명령 실행, 9ms 차이
- 994ms: 복제2 PEXPIREAT 명령 실행, 0ms 차이
- 002ms: 복제2 HGETALL 명령 실행, 8ms 차이
- 008ms: 복제2 EXISTS 명령 실행, 6ms 차이
명령 사이의 시간 간격은 약 10ms이고, 마스터에서 복제서버로 전파되는 시간은 1ms 이내이다.
그러므로 일련의 명령이 복제서버와 마스터에서 나누어서(분산) 실행되어도 순서가 바뀌어서 문제가 될 확률은 거의 없다.
(이 측정치는 부하(load) 정도, 서버나 네트워크 상황에 따라서 다를 수 있습니다)
http://localhost:8080/hello2
hello2() source
레디스 서버에서 실행되는 명령
Master (마스터)
- 마스터에서는 2개 명령이 실행된다. 마스터 서버의 부담이 줄었다.
- setAttribute()로 인해서 HMSET 명령에 sessionAttr:hello2-2, sessionAttr:hello2-1 필드와 값이 추가되었다.
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello2-2" "\xac\xed\x00\x05t\x00\x06Kwon-2"
"sessionAttr:hello2-1" "\xac\xed\x00\x05t\x00\x06Kwon-1"
1701456526.677153 [Client:12667] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458327289"
Replica1 (복제1)
- 마스터에서 전파된 2개 명령이 실행되었다.
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello2-2" "\xac\xed\x00\x05t\x00\x06Kwon-2"
"sessionAttr:hello2-1" "\xac\xed\x00\x05t\x00\x06Kwon-1"
1701456526.677274 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458327289"
Replica2 (복제2)
- 부하분산을 위한 readFrom 설정 효과로 조회 명령이 복제2에서 실행되었다.
- 조회 명령 4개와 마스터에서 전파된 명령 2개가 순서대로 실행되었다.
1701456526.660285 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456526.667545 [Master:18510] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
"sessionAttr:hello2-2" "\xac\xed\x00\x05t\x00\x06Kwon-2"
"sessionAttr:hello2-1" "\xac\xed\x00\x05t\x00\x06Kwon-1"
1701456526.677272 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458327289"
1701456526.688007 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456526.699060 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
http://localhost:8080/hello3
hello3() source
레디스 서버에서 실행되는 명령
Master (마스터)
- 마스터에서는 2개 명령이 실행된다. 마스터 서버의 부담이 줄었다.
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456679.946950 [Client:12667] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458480552"
Replica1 (복제1)
- 마스터에서 전파된 2개 명령이 실행되었다.
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456679.947071 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458480552"
Replica2 (복제2)
- 부하분산을 위한 readFrom 설정 효과로 조회 명령이 복제2에서 실행되었다.
- 조회 명령 4개와 마스터에서 전파된 명령 2개가 순서대로 실행되었다.
1701456679.930006 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456679.939170 [Master:18510] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456679.947046 [Master:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458480552"
1701456679.968754 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456679.977612 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
클라이언트 로그 - 해당 java source를 같이 표시했다.
String hello1Value = (String) session.getAttribute("hello1");
log.info("hello1 Value: {}",hello1Value);
T03:51:20.552 c.r.s.SessionController : hello1 Value: Charlie
// 모든 속성명, 값 가져오기
Enumeration<?> attrName = session.getAttributeNames();
while (attrName.hasMoreElements()) {
String attr = (String) attrName.nextElement();
log.info("{}:{}",attr,session.getAttribute(attr));
}
T03:51:20.552 c.r.s.SessionController : hello2-1:Kwon-1
T03:51:20.552 c.r.s.SessionController : hello1:Charlie
T03:51:20.552 c.r.s.SessionController : hello2-2:Kwon-2
// Session ID, 생성일시, 마지막 액세스(접근) 일시 가져오기
String sessionId = session.getId();
log.info("sessionId: {}",sessionId);
T03:51:20.552 c.r.s.SessionController : sessionId: a1ad6ded-b803-423f-a14e-52b4af3e64fe
Date ctime = new Date(session.getCreationTime());
log.info("CreationTime: {}",ctime);
T03:51:20.552 c.r.s.SessionController : CreationTime: Sat Dec 02 03:42:22 KST 2023
Date atime = new Date(session.getLastAccessedTime());
log.info("LastAccessedTime: {}",atime);
T03:51:20.553 c.r.s.SessionController : LastAccessedTime: Sat Dec 02 03:51:20 KST 2023
http://localhost:8080/hello4
hello4() source
레디스 서버에서 실행되는 명령
Master (마스터)
- 마스터에서는 2개 명령이 실행된다. 마스터 서버의 부담이 줄었다.
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456867.998069 [Client:12667] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458668568"
Replica1 (복제1)
- 마스터에서 전파된 2개 명령이 실행되었다.
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456867.998156 [Master.18:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458668568"
Replica2 (복제2)
- 부하분산을 위한 readFrom 설정 효과로 조회 명령이 복제2에서 실행되었다.
- 조회 명령 5개와 마스터에서 전파된 명령 2개가 순서대로 실행되었다.
- 소스 "Map
entries = redisHash.entries(springSessionId);" 의 결과로 두 번째 HGETALL이 실행되었다.
1701456867.957537 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456867.967415 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456867.987389 [Master.18:18510] "HMSET" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
"lastAccessedTime" "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;"
1701456867.998170 [Master.18:18510] "PEXPIREAT" "a1ad6ded-b803-423f-a14e-52b4af3e64fe" "1701458668568"
1701456868.005919 [Client:12668] "HGETALL" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
1701456868.014797 [Client:12668] "EXISTS" "a1ad6ded-b803-423f-a14e-52b4af3e64fe"
클라이언트 로그
소스에서는 세 가지 방법으로 출력(로그)했으나 결과는 모두 같으므로 여기서는 "방법1: Lambda 사용"만 표시했다.
T03:54:28.586 c.r.s.SessionController : 방법1: Lambda 사용
T03:54:28.586 c.r.s.SessionController : key:sessionAttr:hello1, value:Charlie
T03:54:28.586 c.r.s.SessionController : key:creationTime, value:java.lang.Long;
T03:54:28.586 c.r.s.SessionController : key:lastAccessedTime, value:java.lang.Long;
T03:54:28.586 c.r.s.SessionController : key:maxInactiveInterval, value:java.lang.Integer
T03:54:28.586 c.r.s.SessionController : key:sessionAttr:hello2-1, value:Kwon-1
T03:54:28.587 c.r.s.SessionController : key:sessionAttr:hello2-2, value:Kwon-2
<< Session Standalone | Session Master-Replica | Session Sentinel >> |
---|