실재 서비스는 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개를 띄울 거니까, 어떤 프로세스가 반응했는지 보기 위해
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 하고 있으니까 당연히 이렇게 반응한 것인데.
그런데 우리가 원하는 건 이게 아니다.
한 번에 한 서버만 반응해야지.
그래야 로드밸런싱도 되고, 한 놈이 죽어도 다른 놈이 일을 하니까 이중화라고 말할 수 있고...
라운드로빈 방식으로 한 번에 한 놈한테만 일을 시키는 방법은 없을까?
화투판에서 화투는 선이 한 장씩 줘야한다.
방법이 있다.
제목대로 일반 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가 반응할 것이다.
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 subscriptions 은 Roger Light
의 https://cedalo.com/blog/mqtt-shared-subscriptions-guide/ 글을 읽어 보기 바란다.
정확한 스펙은 https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901250 를 참고.