Post

(TIL) 2025-02-24 FINAL(15)

(TIL) 2025-02-24 FINAL(15)

분산서버 작업 - 6일차


원래 오늘은 AWS의 ELB를 이용하여 로드밸런싱을 설정하고 잘 되면 테스트를 진행해볼려고 했으나 튜터님의 문의한 결과 로드 밸런싱을 작업하는 것보다 우선 게임 서버만 이중화를 시켜서 레디스를 통해서 데이터를 동기화하여 게이트 웨이 서버에서 동적으로 처리 할 수 있게 만들고, 현재 완성된 게임에서 스트레스 테스트나 프로파일링쪽을 좀 더 다뤄보는게 좋다고 말씀하여 ELB나 ECS쪽은 보류하기로 하였다.

Redis 데이터 설계

  1. 서버별 데이터
  • Server:Game / Server:Lobby / Server:Gateway
IndexElement
0x.x.x.x

게임, 로비 서버의 각 개수를 관리하는 테이블이다. List 자료구조를 사용하기로 하였고 각 서버가 실행이 되면 해당 테이블에 추가된다. 그리고 추가된 Index가 각 서버의 키 값이 되어 서버 상세정보를 조회하는 기준이 된다. Value 값은 IP가 저장된다.

  1. 서버 상세 데이터
  • Server:Game:index / Server:Lobby:index / Server:Gateway:index
FieldValue
addressx.x.x.x
status1
games0

서버별 상태정보를 관리하는 테이블이다. Hash 테이블을 사용하게 되었고 Address에는 IP가 저장되고 status에는 현재 서버 상태(원할/혼잡/미사용) 값을 저장한다. games에는 현재 서버에 생성된 게임 세션 갯수를 저장한다. 헬스체크에서 사용할 예정이기도 하고 게임을 시작할때 게임 서버의 games에 따라 어느 서버를 사용하게 될지(로드밸런싱) 결정을 할 예정이다.

Redis 작업

  1. 현재 서버의 IP주소 조회
1
2
3
4
5
6
7
8
9
10
11
12
  // 프라이빗 IPv4 주소
  function getLocalIP() {
    const interfaces = os.networkInterfaces(); //네트워크 인터페이스 정보를 담은 배열
    for (let interfaceName in interfaces) {
      for (let iface of interfaces[interfaceName]) {
        // IPv4, 비공개 IP 제외
        if (iface.family === 'IPv4' && !iface.internal) {
          return iface.address;
        }
      }
    }
  }

각 서버별로 자신의 Address를 레디스에 저장을 해야하는데 후에 스케일 아웃을 통한 서버 확장을 고려하여 서버가 추가되었을 때 자동으로 자신의 주소를 알아야한다. 그래서 OS 모듈에서 networkInterfaces를 이용하여 자신의 Address 값을 조회하기로 했다.

  1. PUB/SUB 레디스 기능중 하나이며, 송신자는 설정한 채널에 메세지를 전송한다. 해당 채널을 설정한 수신자들은 해당 메세지가 전송되면 수신받게 된다. 즉 이 기능을 통해서 서버가 자동으로 늘어날 때 마다 TCP연결을 처리할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
  const subscriber redisClient.duplicate();
    subscriber.on('connect', () => {
    console.log('Redis 구독자 연결성공!!');
  });

  // 연결 실패 시 에러 출력
  subscriber.on('error', (err) => {
    console.error('Redis 구독자 오류:', err);
  });
  
  await subscriber.connect();
  1. 게임/로비 서버 Redis 작업
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  const initOnRedis = async () => {
    const host = getLocalIP();
    const hashData = {
      address: host,
      status: 1,
      games: 0,
    };

    // 서버 상태 on
    await redisClient.watch('Server:Game');
    const serverList = await redisClient.lRange('Server:Game', 0, -1);
    let index = serverList.indexOf(host);
    if (index < 0) {
      index = serverList.length;

      const setReply = await redisClient
        .multi()
        .hSet('Server:Game:' + index, hashData)
        .lPush('Server:Game', host)
        .publish('ServerOn', 'Server:Game:' + index)
        .exec();
    } else {
      const setReply = await redisClient
        .multi()
        .hSet('Server:Game:' + index, hashData)
        .publish('ServerOn', 'Server:Game:' + index)
        .exec();
    }
    console.log('Redis 서버 알림 성공');
  };

게임 서버 기준으로 Server:Game 테이블에서 데이터를 조회한 후 이미 등록이 되었는지 아닌지 Address값으로 체크를 한다. 만약 있다면 서버가 재실행이 된거기에 상세 테이블에 서버 상태값만 초기화를 하고 없는 경우는 관리와 상세 테이블에 새롭게 추가를 한다. 그후 Pub/Sub의 publish를 통해서 설정한 채널에 서버 정보를 Message로 전달을 한다.

  1. Gateway 서버 작업
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  // 새로운 서버 알림 시, 연결
  await subscriber.subscribe('ServerOn', connectServer);

  const connectServer = async (name) => {
    const host = await redisClient.hGet(name, 'address');
    const type = name.split(':')[1];
    const portType = {
      Game: [5557, onGameConnection],
      Lobby: [5556, onLobbyConnection],
    };

    const port = portType[type][0];
    if (!port) {
      console.error('잘못된 서버 타입입니다');
      return;
    }

    const newServer = net.createConnection({ host: host, port: port }, () => {
      console.log(`${name}와 연결되었습니다.`);
      portType[type][1](newServer);
    });

    serverSession.addServer(name, newServer);
  };

Gateway서버에서는 게임/로비 서버에서 작업한걸 똑같이 추가를 하였고 PUB/SUB기능에서 보낸 Message를 받아서 처리하는 부분을 위와 같이 처리하였다. Message를 받아오게되면 게임 서버 테이블에서 해당 서버 정보를 조회해서 유효한 서버인지 체크를 한다. 이상이 없는 경우에는 해당 서버와 TCP연결을 진행한다.

테스트

처음으로 게이트 웨이 서버를 올리고 다음으로 로비서버를 올린다음 게이트 웨이 서버가 Message를 잘 전달 받아 로비 서버와 TCP연결이 잘 되는지 확인을 하고 이상 없으면 게임 서버를 기동하여 게이트 웨이 서버와 게임 서버가 잘 연결이 되면 성공이다.

  1. 게이트 웨이 서버 On 게이트 웨이

이상 없이 서버가 잘 실행된 것을 확인할 수 있었다.

  1. 로비 서버 On 로비 서버 로비 Redis

PUB/SUB을 통해 정상적으로 게이트 웨이 서버와 로비 서버가 연결되었다. Redis에도 값이 정상적으로 저장된 것을 확인했다.

  1. 게임 서버 On 게임 서버 게임 Redis

게임서버 또한 게이트 웨이 서버와 연결이 잘 되었고 Redis에도 값이 정상적으로 저장된 것을 확인했다.

이것으로 레디스를 통해서 서버 정보를 동기화 하고 연결하는 작업은 마무리가 되었다. 내일은 서버 정보값을 각 서버별로 Redis에서 불러와 처리하는 방향으로 수정하는 작업과 시간이 되면 헬스체크 기능에 대해서 구현을 할 예정이고 추가로 로드밸런싱까지 작업을 해볼 생각이다.

This post is licensed under CC BY 4.0 by the author.