Redis를 사용해서 세션관리
● Redis는 key-value 구조의 저장소, 각 유저의 고유 식별자를 key로 사용하고
값은 Hash 형식으로 저장

- session:{user_id} : 유저별 세션 정보 관리
- Hash 구조 예시
{
"userId": "test",
"nickname": "player1",
"loginTime": "2025-02-11T00:00:00Z",
"currentServer": "Town1",
"status": "online"
}
- 신경 써야할 부분은 'currentServer'
- 현재 설계한 서버의 형태로는 Town 서버가 여러 개 존재하기 때문에 현재 위치한 서버를 명시해야 한다.
- 'currentServer'를 명시해줌으로써 현재 어디에 있는지 정확하게 알 수 있다.
로그인은 MySQL로
● 클라이언트 UI에서 입력한 로그인 데이터 패킷을 서버로 보내면, 서버에서 MySQL에 해당 사용자를 조회하여
인증을 진행
● 인증이 성공하면 Redis에 로그인 정보에 부합한 사용자를 Redis에 세션 정보 저장
● Redis TTL(Time-To-Live) 설정하여 일정 시간 동안만 정보 유지 및 삭제
const redis = require("redis"); // Redis를 사용하기 위한 모듈 import
const client = redis.createClient(); // Redis 연결
const moment = require("moment"); // 시간을 보기 좋게 만들어주는 모듈
async function createSession(userId, username) {
const sessionKey = `session:${userId}`;
const sessionData = {
userId,
username,
loginTime: moment().toISOString(),
currentServer: "Town1", // 초기 접속 서버
status: "online"
};
// Redis에 세션 저장 (TTL 설정: 1시간)
await client.hSet(sessionKey, sessionData);
await client.expire(sessionKey, 3600); // TTL 설정 (초 단위)
}
- MySQL 데이터와 일치하여 로그인에 성공했을 때 사용
- session에 추가되었다는 것은 게임에 접속했다는 뜻임으로 status를 online으로 설정
● userSessions를 조회하고자 한다면
async function getSession(userId) {
const sessionKey = `session:${userId}`;
// Redis에서 세션 정보 가져오기
const sessionData = await client.hGetAll(sessionKey);
return sessionData;
}
- hGetAll : h는 Hash를 의미. 즉, Hash에서 GetAll 모두 가져와!
- 세션에 접속한 모든 유저를 조회
● 접속을 종료하거나 클라이언트가 비정상 종료되었을 때 사용할 함수
async function deleteSession(userId) {
const sessionKey = `session:${userId}`;
// Redis에서 세션 삭제
await client.del(sessionKey);
}
- del은 이름에서 유추하듯이 delete를 줄인 것, 삭제하는 메서드이다.
흐름을 정리하자면
● 로그인을 했다
1) 클라이언트 로그인 요청
2) MySQL에서 인증
3) 인증 성공 시 Redis에 세션 생성
→ 초기 상태 : 'status : online', 'currentServer : Town1'
● 서버 이동 (Town1에서 Dungeon으로 이동)
1) 클라이언트가 던전 UI에서 버튼을 클릭해서 이동 요청을 함
2) Manager Server에서 이동 처리 (Town 1 → Dungeon 1)
3) 'currentServer : Dungeon1' 상태 변경!
● 접속 상태를 확인하려면
1) Manager Server가 Redis에 userId value를 사용해서 특정 사용자의 상태('status')와
위치('currentServer')를 조회하고 관리
2) 접속한 사용자를 확인하는 UI가 있다면?
● 로그아웃 요청 시
1) 클라이언트 로그아웃 요청 → Manager Server에서 Redis 해당 세션 삭제
던전에서 게임이 팅겼어요!
● 던전에서는 다른 로직을 사용해서 추가적인 데이터를 저장하는 key-value로 설계
○ 어떤 데이터가 추가적으로 필요한가
- 마지막으로 있던 위치 = 가장 중요할 듯 함
- 클라이언트가 종료되던 시점의 스탯, 아이템 = 패널티로 초기부터 시작하도록 해도 되고...
● Hash 구조 예시
{
"userId": "test",
"username": "player1",
"currentDungeon": "Dungeon1",
"position": { "x": 100, "y": 200, "z" : 0 },
"hp": 80,
"mp": 50,
"lastUpdateTime": "2025-02-11T00:58:00Z"
}
● 코드로 작성
async function saveSession(userId, dungeonId, position, hp, mp) {
const sessionKey = `session:${userId}`;
const sessionData = {
userId,
currentDungeon: dungeonId,
position: JSON.stringify(position),
hp,
mp,
lastUpdateTime: moment().toISOString()
};
// Redis에 세션 정보 저장
await redisClient.hSet(sessionKey, sessionData);
// 해당 Dungeon의 플레이어 목록에 추가
await redisClient.sAdd(`dungeon:${dungeonId}:players`, userId);
}
- hset : HashSet
- sAdd : set Add
● 클라이언트가 재접속했을 때
- 클라이언트에서 Notice창을 띄어줌으로써 재접속했음을 서버에 알려줘야 한다.
- 서버가 재접속 패킷을 받았다면 던전에 다시 배치해주는 로직이 필요하다.
async function reconnectSession(userId) {
const sessionKey = `session:${userId}`;
// Redis에서 세션 정보 조회
const sessionData = await redisClient.hGetAll(sessionKey);
if (!sessionData) {
throw new Error("세션 정보 없음");
}
// 유저를 마지막 접속했던 Dungeon으로 재연결
const { currentDungeon, position, hp, mp } = sessionData;
return {
dungeon: currentDungeon,
position: JSON.parse(position),
hp: parseInt(hp),
mp: parseInt(mp)
};
}
근데... 아이템은?
● 따로 던전 내에서 먹은 아이템은 JSON에 저장해서 사용
[
{
"itemId": "item1",
"name": "한손검",
"type": "Weapon",
"attributes": {
"atk": 10,
"def": 0,
"magic": 0,
"speed": 0,
}
},
{
"itemId": "item1",
"item_name": "흰색 무지 반팔",
"item_type": "Armor",
"item_attributes": {
"atk": 0,
"def": 5,
"magic": 0,
"speed": 1,
}
},
]
- key 구조 : dungeon:{dungeonId}:items:{userId}
→ 사용자가 특정 던전에서 소유한 아이템 리스트
● 아이템을 획득했을 때
async function addItemToDungeon(userId, dungeonId, item) {
const key = `dungeon:${dungeonId}:items:${userId}`;
// 기존 아이템 리스트 가져오기
let items = await redisClient.get(key);
items = items ? JSON.parse(items) : [];
// 새로운 아이템 추가
items.push(item);
// Redis에 업데이트
await redisClient.set(key, JSON.stringify(items));
}
● 위치, 스탯을 재접속 시 가져왔을 때와 동일하게 세션 확인 후 데이터를 복구
async function reconnectToDungeon(userId, dungeonId) {
const key = `dungeon:${dungeonId}:items:${userId}`;
// Redis에서 기존 데이터 로드
const items = await redisClient.get(key);
if (!items) {
throw new Error("이전에 저장된 세션 데이터가 없습니다.");
}
return JSON.parse(items);
}
● 게임 설계 상 무기, 방어구 등은 던전에서만 사용하기 때문에 저장되던 key들은 던전 종료 시 삭제
async function leaveDungeon(userId, dungeonId) {
const key = `dungeon:${dungeonId}:items:${userId}`;
// Redis에서 데이터 삭제
await redisClient.del(key);
}
Redis를 설계하면서 생각하게 된 것들은...
● Redis는 확실히 캐시 데이터를 저장하기에 좋다.
● TTL 설정이 있기에 자동으로 세션이 삭제되기에 메모리 관리가 좋다.
● Redis에서도 JSON 형태로 저장해서 사용한 다음 삭제할 수 있다는 것을 알게 되었다.
'Project_xxx' 카테고리의 다른 글
| [Project_] R - DB 사용 방식과 Redis 사용 방향 (0) | 2025.02.21 |
|---|---|
| [Project_] R - 로그인, 캐릭터 생성에 대한 고민 (0) | 2025.02.14 |
| [Projcet_] TDO - Trouble Shooting (0) | 2025.01.31 |
| [Project_] MG - Trouble Shooting (0) | 2025.01.16 |
| [Project_] TD - Final (0) | 2025.01.02 |