말을 하는데, 상대가 '응', '응' 하며 추임새를 넣어주면 말하는 사람도 신이 난다.
그런데 내 말엔 신경도 안 쓰고 지 할 일만 하는 놈도 있다.
'늬 내 말 듣고 있니?' 라고 답답해서 중간에 물으면 '야...다 듣고 있어!' 이러면 짜증 제대로다.
이번 글에서는 내가 talk을 날렸을 때, 상대편 대응 방식에 따라 어떻게 톡 프로그램을 코딩해야 하는 지 알아보자.
즉 대꾸하는 분과 대꾸없는 놈, 두 케이스에 대해서...
pub/sub 통신에서는 보내는 사람과 받는 사람, 즉 종단간의 연결 보다는 중간에 있는 broker 와의 대화에 집중한다.
broker와의 통화 품질을 QoS라고 하는데, 여기에는 다음 3가지가 있다.
QoS | 어려운 말 | 쉬운 말 | ㅈㄹ 쉬운 말 |
---|---|---|---|
0 | At most once | Fire and Forget | 쏘고 잊으세요 |
1 | At least once | Acknowledged delivery | 최소 한 번은 보내라 |
2 | Exactly once | Assured delivery | 딱 한 번만 보내라 |
Ref: MQTT Spec v3.1.1 4.3 Quality of Service levels and protocol flows
이건 client인 나와 broker인 너와의 관계다.
pub하는 client와 sub하는 server와 관계는 제 3자인 broker가 끼여드는데, 이것도 여기에 기초한다.
다음은 QoS 별 응답 프로토콜이다. 자세한 내용은 참고를 클릭하면 되는데....
슬프게도 영어다😭😭😭
지면도 남고 하니 하나씩 알아보자.
쏘고 잊으세요.
이거 딱 그거다. 월요일 아침에 하는 교장 선생님 훈화
연식 들통나는 건가 😜
자기 말만 하고 땡이다
그림 보면 알겠지만, 쏘기만 한다.
Broker도 마찬가지다.
Subscriber한테 쏘기만 한다.
요 걸 3자 관계로 그려보면 다음과 같다.
Publisher는 Broker한테 쏘고 Broker도 Subscriber한테 쏘기만 할 뿐 받았는지 관심이 없다.
말 그대로 쏘고 잊는다.
메세지 저장 이딴 것 없다.
따라서 ㅈㄹ 빠르다.
소스로는 { qos: 0} 만 추가하면 된다.
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/payload", 'hi', { qos: 0 }, (err, res) => {
if (err) console.log('err', err);
else console.dir(res);
client.end();
});
});
요걸 실행해보면
callback 함수 res 가 undefined다.
이 딴 걸 어디에다 쓰냐고?
다 필요하니까 만들어 둔 것이다.
온,습도 센서가 온,습도를 pub한다고 했을 때, 받는 쪽 입장에서는 최신 데이터가 젤 중요하다.
이럴 땐 QoS 0 이 딱이다.
중간 것 몇 개 잃어 버려도 개안타~
최소 한 번은 보내라.
이건 매번 확인하는 거다. 내 말 들었는지.
'응', '응' 하는 맞장구가 없으면 한 말 또 하는거다.
Publisher는 Broker한테 쏘고 Broker는 Subscriber한테 쏘고 PUBACK을 돌려준다.
PUBACK을 실패하면 Broker는 저장해두었던 걸 한 번 더 보낼 수 있다.
QoS 0 보다는 아니지만, 나름 빠른데, 1번 이상 Subscriber가 받을 수 있다.
QoS 만 0에서 1로 바꾼 소스다.
client.on("connect", () => {
client.subscribe("clients/client1");
client.publish("server/payload", 'hi', { qos: 1 }, (err, res) => {
if (err) console.log('err', err);
else console.dir(res);
client.end();
});
});
요걸 실행해보면
res.cmd 에 'publish', 그리고 broker가 따 준 messageId가 들어왔다.
중복 문제만 없다면 요거이 딱인데...😅
딱 한 번만 보내라.
요건 빼도 박도 못하게 도장 꽉꽉 찍어두는거다.
딱 한번만 보내고/받기 위해서는 Broker가 중간에서 보낸 놈과 받는 놈을 잡고 서로 주고 받은 걸 확인 시켜주는 작업을 한다.
그러다 보니 확인하는 프로토콜(PUBREC,PUBREL,PUBCOMP)이 많고, 당연히 통신도 느려진다.
Broker도 할 일이 많다보니 부하도 많이 걸리고, 그림 보면 알겠지만 Publisher, Subscriber 도 뭔가 복잡하고 바쁘다.
Qos 를 2로 바꾼 소스다.
client.on("connect", () => {
client.subscribe("clients/client1");
client.publish("server/payload", 'hi', { qos: 2 }, (err, res) => {
if (err) console.log('err', err);
else console.dir(res);
client.end();
});
});
이를 실행하면
res.cmd 에 'pubrel', 그리고 broker가 따 준 messageId가 들어왔다.
요거이 우리가 원하는 바이긴 한데, 느리다는 단점이 있다.
QoS 2와 같이 중복없이 가는데, 빠른 방법은 없을까?
받았다는 걸 확인할 수 있는 PUBACK은 받지만, 중복을 피하고 싶다.
즉 QoS = 2의 효과를 원하지만, 속도가 느린 건 원치 않는다. 이건데...
세상에 공짜가 있나
Broker 부하를 줄이려면 누군가 그 부하를 져야할텐데
누가 그 부담을 질 것인가?
Database <- 얘 밖에 없다.
어떻게?
Insert 대신 Upsert로
쫑날 수도 있으니, Upsert로 ...
Upsert하려면 PK가 있어야 하는데.
아 그러면 client에서 talk을 쏠 때 unique한 talk_id를 만들어서 쏘면 되지 않을까?
talk_id를 unique하게 만드는 방법은 Date.now() 를 쓰던 UUID를 쓰던 방법은 많을 것이다.
그래도 쫑이 걱정된다면 prefix를 줘서 나미 따듯 쫑 안 나게 비켜가게 해야지.
이는 각자 개발 짬에 따라 코딩하시라.
const { v4: uuidv4 } = require('uuid');
...
rl.on('line', function (line) {
if (line === 'exit') rl.close();
const talk = { data: { talk_id: uuidv4(), from: user_id, room: 'room1', msg: line } };
client.publish("server/talk", JSON.stringify(talk));
rl.prompt()
});
mysql_conn.sql(UPSERT_TALK, [json.data.talk_id, json.data.from, json.data.room, json.data.msg, json.data.talk_id], function(err, results, fields) {
room.forEach(client => {
if (talk.data.from === client) return false;
let client_topic = 'clients/' + client + '/talk';
server.publish(client_topic, message);
});
}
이건 번외지만 SQL 이 다음과 같이 바뀔 것이다.
Before
INSERT INTO talk_table (from, room, msg)
VALUES (#{from}, #{room}, #{msg});
After
INSERT INTO talk_table (id, from, room, msg)
VALUES (#{talk_id}, #{from}, #{room}, #{msg}) ON DUPLICATE KEY
UPDATE id = #{talk_id};
귀에 콕콕 박히네요. 늬 내 전화 아이받늬?ㅋㅋㅋ