분산 서버
현재 진행한 분산 서버 구조에 개발을 마친 Game 서버 로직을 병합해주는 작업을 진행해준다!
-
Protobuf 구조 병합
-
Game 서버의 Handler를 분산 서버에 맞게 구조 수정
-
새로 만들어진 PacketType들을 Gateway 서버에 맵핑
Latency 체킹
분산 서버가 되어 Gateway 서버가 추가되었기에 각 서버와의 latency가 게임에 영향이 크지 않을 정도로 나오는지 확인하기 위해 로직을 추가해준다.
- Gateway 서버의 Server Class에 레이턴시 확인용 인터벌 메서드를 설정해주고, 받아왔을 때 Log로 보여줄 핸들러를 만들어준다!
// 레이턴시 확인 인터벌
latencyCheck = () => {
const packet = makeServerPacket(
// timestamp 가 있는 packetType을 임시로 사용하였다!
config.packetType.S_ERROR_NOTIFICATION,
{
errorMessage: 'latencyCheck',
timestamp: date.now(),
},
-1,
);
this.socket.write(packet);
};
// 패킷을 다시 받아왔을때 핸들러
const latencyCheckHandler = ({ socket, payload, userId }) => {
if (userId !== -1) return;
const { errorMessage, timestamp } = payload;
const latency = date.now() - timestamp;
console.log(
`###Latency### ${errorMessage} 와의 총 왕복 시간${latency} / 평균 Latency ${latency / 2}`,
);
};
- 이제 다른 서버에서 이를 받아왔을때 errorMessage에 서버이름을 저장해 다시 보내주는 핸들러를 추가해준다!
const latencyCheckHandler = ({ socket, payload, userId }) => {
if (userId !== -1) return;
let { errorMessage, timestamp } = payload;
if (errorMessage === 'latencyCheck') errorMessage = roomSession.name;
const packet = makePacket(
config.packetType.S_ERROR_NOTIFICATION,
{ errorMessage, timestamp },
userId,
);
socket.write(packet);
};
- 처음 Latency는 혼잡제어나 흐름제어에 의한 통신량이 점차늘어나는 방식때문에 비교적 늦는 것 같다!
로컬 테스트
현재 분산 서버를 적용하여 테스트를 하기 쉽도록 하나의 브랜치에 폴더들을 정리해두었는데..
생각해보니 Redis Cloud를 공유하기 때문에 서로 폴더 구조를 침범하는 상황이 생길 것 같다!
그래서 폴더명 앞에 자신의 이름을 붙일 수 있는 구조로 재설계 해주었다.
- Gateway의 Redis 연동 서버 연결 부
const host = getLocalIP();
// 이게 폴더 구조 앞에 환경변수 이름을 추가하도록 설계함
const mainName = config.redis.custom + 'Server:Gateway';
const hashData = {
// 서버 주소
address: host,
// 서버 상태
status: 1,
};
await redisClient.watch(mainName);
// [1] list에서 서버 조회
const serverList = await redisClient.lRange(mainName, 0, -1);
const index = serverList.indexOf(host);
// [2] hashKey 생성 lobby:2 lobby:3 ... + 값 저장
// [3] 중복 여부에따라 List 업데이트
let name = mainName + ':' + index;
if (index < 0) {
name = mainName + ':' + serverList.length;
await redisClient.multi().hSet(name, hashData).rPush(mainName, host).exec();
} else await redisClient.multi().hSet(name, hashData).exec();
console.log('Redis 서버 오픈 알림 성공');
const lobby = config.redis.custom + 'Server:Lobby';
// Gateway 서버의 연결 부
const LobbyServers = await redisClient.lRange(lobby, 0, -1);
for (let i = 0; i < LobbyServers.length; i++) {
await connectServer(lobby + ':' + +i); //로비서버 TCP연결
}
const game = config.redis.custom + 'Server:Game';
const GameServers = await redisClient.lRange(game, 0, -1);
for (let i = 0; i < GameServers.length; i++) {
await connectServer(game + ':' + i); //게임서버 TCP연결
}
console.log('Redis List 모든 서버 연결 성공');
- 다른 서버의 Redis 연동 부
const host = getLocalIP();
// 이게 폴더 구조 앞에 환경변수 이름을 추가하도록 설계함
const mainName = config.redis.custom + 'Server:Lobby';
const hashData = {
// 서버 주소
address: host,
// 서버 상태
status: 1,
check: 'new',
};
await redisClient.watch(mainName);
// [1] list에서 서버 조회
const serverList = await redisClient.lRange(mainName, 0, -1);
const index = serverList.indexOf(host);
// [2] hashKey 생성 lobby:2 lobby:3 ... + 값 저장
// [3] 중복 여부에따라 List 업데이트
let name = mainName + ':' + index;
if (index < 0) {
name = mainName + ':' + serverList.length;
await redisClient
.multi()
.hSet(name, hashData)
.rPush(mainName, host)
.publish('ServerOn', name)
.exec();
} else await redisClient.multi().hSet(name, hashData).publish('ServerOn', name).exec();
roomSession.name = name;
// 헬스체크 Sub 매핑
subscriber.subscribe(name, healthCheck);
console.log('Redis 서버 오픈 알림 성공');
한줄 평 + 개선점
- 디버깅에 바쁜 나날이였다