MQTT로 카톡서버 만들어보자 - 9/10. server.js 이중화

빙고리우스·2024년 4월 12일
2
post-thumbnail

broker만 이중화하냐?

실재 서비스는 server.js 가 하는데, server.js가 죽으면 어떡하죠?

앞에서 broker가 죽으면 다른 broker가 받을 수 있도록 했다.
그래 broker는 이제 안심이다.

그런데 server.js가 죽으면 어떡할 건데.
어떡하긴 뭘 어떡해.
이것도 똑 같다.

server.js 도 여러개 띄워야 한다.

아시다시피 nodejs 는 Single Process / Single Thread 다.
죽으면 서비스 끝이다.

담당자는 팀장한테 끌려간다.😓

그래서 예전엔 죽으면 살리고, 또 죽으면 또 살리고 하는 forever.js 같은 모듈을 사용하기도 하였다.
요즈음은 프로세스 매니저라고 해서 pm2 를 사용한다.
보통 CPU 코어 수 만큼 server.js를 띄우고 요청이 들어오면 돌아가면서 처리하도록 하는거다.
유식한 말로 이거 scale out이라 한다.

이거 해보자.

server.js를 2 개 띄워보자.

앞에서 계속 봐 왔던 server.js 이다.
다만 2개를 띄울 거니까, 어떤 프로세스가 반응했는지 보기 위해

    console.log(process.pid, '가 반응하였음.');

를 추가했다.

server.js

const mqtt = require("mqtt");
const server = mqtt.connect("mqtt://localhost");

server.on("connect", () => {
    server.subscribe("server/login");
});

server.on("message", (topic, message) => {
    console.log(process.pid, '가 반응하였음.');
    var data = JSON.parse(message);
    var client_topic = 'clients/' + data.id;
    if (data.pwd === 'pwd') {
        var res = { msg: 'login ok' };
        server.publish(client_topic, JSON.stringify(res));
    } else {
        var res = { msg: 'login fail' };
        server.publish(client_topic, JSON.stringify(res));
    }
});

자 이 상태에서 server.js를 2개 실행해보자.
1번 server.js 실행

$> node server.js

2번 server.js 실행

$> node server.js

여기서 client.js를 실행하면 어떡게 될까?

$> node client.js

client.js 소스는 아래처럼 바뀐게 없다.

client.js

const mqtt = require("mqtt");
const client = mqtt.connect("mqtt://localhost");

const user = { id: 'client1', pwd: 'pwd' };

client.on("connect", () => {
    client.subscribe("clients/client1");
    client.publish("server/login", JSON.stringify(user));
});

client.on("message", (topic, message) => {
    console.log(message.toString());
    client.end();
});

client.js 실행

$> node client.js

client.js 실행 결과

어이쿠😨😨😨, login ok 메세지가 2번 떨어진다.

아니나 다를까, 1번, 2번 server.js 가 둘 다 반응하였다.😨😨😨

1번 server.js 실행 결과

2번 server.js 실행 결과

뭐 생각해보면 당연한 결과다.
2개의 server.js가 server/login topic에 sub 하고 있으니까 당연히 이렇게 반응한 것인데.

그런데 우리가 원하는 건 이게 아니다.
한 번에 한 서버만 반응해야지.
그래야 로드밸런싱도 되고, 한 놈이 죽어도 다른 놈이 일을 하니까 이중화라고 말할 수 있고...

라운드로빈 방식으로 한 번에 한 놈한테만 일을 시키는 방법은 없을까?

화투판에서 화투는 선이 한 장씩 줘야한다.

Shared subscriptions

방법이 있다.
제목대로 일반 sub을 공유 sub으로 바꾸면 된다.
어떡해 바꾸냐?
방법은 간단하다.

topic 이름을 server/login 에서 $share/GroupA/server/login 로 바꾸면 된다.

server.js

...
server.on("connect", () => {
    server.subscribe("$share/GroupA/server/login");
});
...

GroupA 라고 썼는데, 이것은 그룹핑을 하기 위한 이름으로 이름이 같으면 하나로 묶인다.

client.js 는 그대로 server/login에 pub 하면 된다.

자 그 결과를 보자.
귀찮아서 그림을 하나로 묶었다.

제일 위에 node clinet.js 실행한 결과를 보면 알겠지만, 1번 server.js만 반응하였다.
한번 더 실행하면 2번 server.js가 반응할 것이다.

pm2

server.js를 하나씩 띄우기도 귀찮을뿐만 아니라, 죽었는지 확인하는 것도 그렇고, 죽으면 다시 살리는 것도 번거럽고.
그래서 나온게 pm2다.

라떼엔 forever.js 이런 걸로 짰지.

이러면 연식이 좀 된 개발자라 생각하면 된다.

ecosystem.config.js

module.exports = {
  apps: [{
    name: 'server',
    script: './server.js',
    instances: 'max',
    exec_mode: 'cluster'
  }]
}

instances : 'max' 로 주면 CPU 코어 수만큼 프로세스가 실행된다.

다음은 client.js를 실행한 결과다.

8 Core PC라 8개 프로세스가 떴지만, client.js 에는 하나만 반응하였다.

Cloud1, Cloud2 2개 있다고 하자.

Cloud1에도 GroupA 를 만들고, Cloud2에도 GroupA를 만들어 두면, Cloud1에 문제가 생겨도 서비스에는 지장이 없다.

멋지지 아니한가~

사실 내가 그린 그림보다 아래 나오지만, Roger Light의 이 그림이 이해하는 더 도움이 된다.
한 번에 한 장씩.🎴🎴🎴


Shared subscriptionsRoger Lighthttps://cedalo.com/blog/mqtt-shared-subscriptions-guide/ 글을 읽어 보기 바란다.
정확한 스펙은 https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901250 를 참고.

profile
다할줄아는 사람보다 뭔가 한가지 똑부러지게하는 사람이되자.

0개의 댓글