Communication
방법
서비스간 혹은 서비스와 브라우저간 서로 통신을 하려면 어떻게 하는 것이 좋을까? 내 컴퓨터에서 실행되는 Java와 C++ 프로그램 사이의 통신처럼, 동일 머신내 서로 다른 프로세스 사이의 통신을 IPC라고 한다. IPC에는 여러 가지 방법이 있는데, 이들을 응용 및 확장하여 네트워크 환경에서도 적용하는 방향으로 발전해 온 것 같다. 대강 검색해보니 아래와 같은 방법들이 보였다.
- Socket : TCP Socket
- Message Queue : Mosquitto, RabbitMQ, HiveMQ, Kafka, ZeroMQ
- Remote Procedure Call : zeroRPC, gRPC, Thrift
- python-shell
- 이 밖에 Shared Memory, Pipe, Memory Map 등이 있으나 웹 개발과 관련이 적어 패스
여기서는 Socket, Message Queue, RPC를 주제로 간단한 실험을 해 본다. 우선 간단히 개념을 정리해보면 다음과 같다.
- Socket : 네트워크 통신을 위해 데이터가 드나들 수 있도록 특정 포트에 할당된 일종의 창구
- Message Queue: 큐를 사용한 전송 기법. 네트워크에서는 프로토콜이 필요한데 MQTT(Application Layer, TCP/IP 기반)가 대표적
- RPC : 원격 프로시저 호출을 통해 데이터를 얻는 기법. 자세한 원리는 대학 교재에 나올 듯
그러면 바로 비교 실험으로 들어가 보자.
TCP Socket
Node 클라이언트에서 파이썬 서버로 TCP 스트림 소켓을 통해 10바이트 패킷을 30회 전송하고 응답을 받았을 때 소요된 시간은 80ms, 회당 2.66ms 정도가 된다. 또, Node에서 파이썬으로 패킷 전송을 8ms 간격으로 30회 수행했을 때 총 소요 시간은 270ms였다.
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 5555))
sock.listen()
while True:
client, address = sock.accept()
client.settimeout(60)
buffsize = 10
while True:
try:
data = client.recv(buffsize)
if data:
response = data
print(response)
client.send(response)
except:
print('--- disconnected ---')
break
var net = require('net');
const client = new net.Socket();
client.connect({ port: 5555, host: '' }, function() {
console.log('connected...');
let count = 0;
while(true) {
let d = new Date()
let c =
let m =
client.write('NODE:'+("00"+count).slice(-2)+("000"+d.getMilliseconds()).slice(-3));
count++;
if(count > 30)
break;
}
});
client.on('data', function(chunk) {
let d = new Date()
console.log('RECV : ', chunk.toString(), d.getSeconds(), d.getMilliseconds());
});
Socket.io, Engine.io
웹소켓을 사용하면 브라우저에서 TCP 통신을 수행할 수 있다. 브라우저와 Node 서버간 30회의 전송에 소요된 시간은 70~100ms, 회당 2.33~3.33ms로 다른 방법들에 비해 들쭉날쭉한 편이었다. 또, 8ms 간격으로 30회 수행했을 때 총 소요 시간은 250~300ms 정도였다. 다른 방법들에 비해 안정적이지 않은 느낌.
const engine = require('engine.io');
const server = engine.listen(5555, {cors:true});
server.on('connection', socket => {
socket.on('message', msg => {
console.log(msg);
let d = new Date()
socket.send(msg + " - " +("000"+d.getMilliseconds()).slice(-3));
});
// socket.send(Buffer.from([0, 1, 2, 3, 4, 5])); // binary data
});
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="/engine.io.min.js"></script>
<script>
const socket = eio('ws://localhost:5555');
socket.on('open', () => {
let count = 0;
while(true) {
let d = new Date()
socket.send(("00"+count).slice(-2)+("000"+d.getMilliseconds()).slice(-3));
count++;
if(count > 30)
break;
}
socket.on('message', (data) => { console.log(data) });
});
</script>
</head>
<body>
</body>
</html>
Mosquitto
유명한 MQTT 브로커 중 하나이다. MQTT는 사물인터넷과 같은 적은 리소스 환경에서 다대다의 신뢰성 있는 통신을 위해 설계된 경량의 프로토콜이며 센서 네트워크 등에 적합하다고 한다. Mosquitto는 계층구조의 Topic, 영구 세션 옵션, QoS 0/1/2, Authentication, MQTT over WebSocket 등을 지원한다.
MQTT는 Broker, Publisher, Subscriber 세 가지로 구성된다. 유튜브를 예로 들면 유튜브=Broker, 유튜버=Publisher, 구독자=Subscriber가 될 것이다. 유튜버 A가 동영상을 유튜브에 올리면 A의 구독자들이 알림을 받는 것과 비슷한 방식이다.
우선 브로커(Broker)를 실행해보자. 윈도우에서는 mosquitto 인스톨러(mosquitto-2.0.2-install-windows-x64)를 실행하면 설치된다. 설치된 폴더에서 mosquitto.exe를 실행하면 검은 창이 그대로 멈춰있는데 이 상태가 브로커가 구동된 것이다.
다음은 구독자(Subscriber)를 Node 스크립트로 만들어보자. mqtt 라는 패키지를 사용한다. 실행해보면 메시지 수신 대기 상태가 될 것이다.
npm install --save mqtt
var mqtt = require('mqtt')
var client = mqtt.connect('mqtt://localhost')
client.on('connect', function () {
console.log('connected...')
client.subscribe('/TOPIC/1', function (err) { })
})
client.on('message', function (topic, message) {
let d = new Date()
console.log(message.toString(), d.getSeconds(), d.getMilliseconds())
// client.end()
})
이번엔 발행자(Publisher)를 파이썬으로 만들어보자. paho-mqtt 패키지를 사용한다. 실행하면 메시지를 발행한다.
pip37 install paho-mqtt
import paho.mqtt.client as mqtt
import time
client = mqtt.Client()
client.connect('localhost')
client.loop_start()
count = 0
while True:
start = datetime.datetime.now()
client.publish('/TOPIC/1', "Hello World..." + str(count+1) + " " + str(start.microsecond / 1000.0), 1)
count += 1
if count == 30:
break
for i in range(30):
client.publish('/TOPIC/1', "Hello World...", 1)
time.sleep(0.008)
client.loop_stop()
client.disconnect()
참고로 mosquitto 설치 폴더에서 명령어를 사용해 발행을 할 수도 있다.
mosquitto_pub -t MY_TOPIC -m "HELLO WORLD"
새 메시지가 발행되면 구독자(Node) 쪽에 메시지가 도착하는 것을 볼 수 있다. 로컬호스트 환경에서 파이썬에서 Node로 발행과 수신을 30회 수행했을 때 총 소요 시간은 70ms, 회당 2.33ms였다. 또, 파이썬에서 Node로 발행과 수신을 8ms 간격으로 30회 수행했을 때 총 소요 시간은 250ms였다.
Mosquitto 양방향통신
양방향 통신을 위해서는 다음과 같이 생각해볼 수 있다. 구독자도 유튜버가 되어 동영상을 업로드하기 시작했고, 유튜버A는 구독자의 동영상을 구독하기 시작했다. 즉, 파이썬 쪽과 Node 모두 발행자이자 구독자가 되는 것이다. 이렇게 테스트했을 때의 소요 시간은 2배로 늘어날 줄 알았는데 신기하게도 70ms 거의 그대로였다.
import paho.mqtt.client as mqtt
import datetime
import time
import math
def on_message(client, userdata, message):
msg=str(message.payload.decode("utf-8"))
client.publish('/FROMPY', "PY:" + msg, 1)
client = mqtt.Client()
client.connect('localhost')
client.on_message=on_message
client.loop_start()
client.subscribe("/FROMNODE")
while True:
time.sleep(0.001)
#client.loop_stop()
#client.disconnect()
var mqtt = require('mqtt')
var client = mqtt.connect('mqtt://localhost')
client.on('connect', function () {
console.log('connected...')
client.subscribe('/FROMPY', function (err) { })
let count = 0;
while(true) {
let d = new Date()
let msg = 'NODE:'+("00"+count).slice(-2)+("000"+d.getMilliseconds()).slice(-3)
client.publish('/FROMNODE', msg)
count++;
if(count > 30)
break;
}
})
client.on('message', function (topic, message) {
let d = new Date()
console.log('ONMESSAGE: '+ message.toString() + ' NODE:', d.getMilliseconds())
// client.end()
})
Browser - Mosquitto
mosquitto 브로커에서 웹소켓을 사용하도록 설정해 브라우저에서 바로 접근할 수 있다. 그러나 윈도우 버전에서는 인스톨러로 설치했을 때 바로 웹소켓을 사용할 수는 없었다. libwebsockets 등을 빌드하고 어쩌고 하는 과정이 필요한 것 같다. 빌드 등을 끝내면 mosquitto.conf를 아래처럼 수정하면 된다고 한다. 여기서는 일단 테스트를 위해 브로커는 원격지의 broker.hivemq.com
를 대신 사용하기로 했다.
listener 9001
protocol websockets
Browser - MQTT.js
위에서 사용한 mqtt.js는 브라우저에서도 사용할 수 있다. 아래와 같이 browser-build를 실행하면 mqtt.min.js를 생성할 수 있다. 브라우저에서 이를 불러오면 Node에서와 같이 mqtt 브로커에 접속할 수 있다. 이 때 접속 주소에 웹소켓 프로토콜을 지정해주는 것을 유의하자. 웹브라우저에서 broker.hivemq.com
로 발행과 수신을 30회 수행하였는데, 30~40ms 간격으로 통신할 수 있었다.
cd node_modules/mqtt
npm install . // install dev dependencies
npm run browser-build
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>gRPC-Web Example</title>
<script src="./browser/mqtt.min.js"></script>
</head>
<body>
<p>Open up the developer console and see the logs for the output.</p>
<script>
var client = mqtt.connect('ws://broker.hivemq.com:8000/mqtt')
client.on('connect', function () {
console.log('connected...')
client.subscribe('/TEST/1', function (err) {
if (!err) {
let count = 0;
let pub = setInterval(()=> {
client.publish('/TEST/1', 'Hello mqtt')
count++;
if(count > 30) clearInterval(pub);
}, 30)
}
})
})
client.on('message', function (topic, message) {
let d = new Date()
console.log(message.toString(), d.getSeconds(), d.getMilliseconds())
// client.end()
})
</script>
</body>
</html>
Browser - Eclipse Paho Javascript Client
Eclipse Paho Javascript Client를 사용해보았다. 마찬가지로 웹브라우저에서 broker.hivemq.com
로 발행과 수신을 30회 수행하였는데, 30~40ms 간격으로 통신할 수 있었다. Paho의 경우 코드는 조금 길어지지만 라이브러리 용량이 34KB로 mqtt.js(191KB)에 비해 훨씬 작다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>gRPC-Web Example</title>
<script src="./browser/paho-mqtt-min.js"></script>
</head>
<body>
<p>Open up the developer console and see the logs for the output.</p>
<script>
client = new Paho.MQTT.Client('broker.hivemq.com', 8000, 'test');
client.onMessageArrived = onMessageArrived;
client.connect({onSuccess:onConnect});
function onConnect() {
console.log("connected...");
client.subscribe("/TEST/1");
let count = 0;
let pub = setInterval(()=> {
message = new Paho.MQTT.Message("Hello MQTT over Websockets");
message.destinationName = "/TEST/1";
client.send(message);
count++;
if(count > 30) clearInterval(pub);
}, 30)
}
function onMessageArrived(message) {
let d = new Date()
console.log(message.payloadString, d.getSeconds(), d.getMilliseconds())
}
</script>
</body>
</html>
이것을 웹서버 같은 서비스에 어떻게 적용할 것인지는 일단 미루어두고, 이번에는 RPC로 넘어가보자.
zeroMQ / zeroRPC
zeroMQ는 Mosquitto 등의 MQTT 브로커들과 달리 브로커 없이 동작하는 메시징 라이브러리이다. 여러가지 패턴(pub/sub, request/reply, client/server and others)과 전송(TCP, in-process, inter-process, multicast, WebSocket and more)을 지원한다고 한다. 정체가 뭔지 잘 모르겠다.
zeroRPC는 zeroMQ기반의 메시징/RPC 프레임워크다. 도커 개발자가 만들었다고 하며, 사용법이 직관적이고 스트리밍도 가능하다. zeroRPC를 기반으로 한 node-python-collaboration
이라는 Node 패키지를 사용하면 좀 더 간편하게 통신할 수 있다. mqtt.js나 gRPC-Web처럼 브라우저에서 직접 zeroRPC 서버로 접근하는 방법은 좀 더 찾아봐야겠다.
Node에서 zeroRPC 설치가 잘 되지 않은 관계로 우선 파이썬으로 해보았다. 파이썬에서 파이썬의 hello
메소드를 30회 호출하고 결과를 전달받기까지 총 소요 시간은 150ms, 회당 5ms였다.
pip37 install zerorpc
import zerorpc
class HelloRPC(object):
def hello(self, name):
return "Hello, %s" % name
s = zerorpc.Server(HelloRPC())
s.bind("tcp://0.0.0.0:4242")
s.run()
import zerorpc
import datetime
import time
c = zerorpc.Client()
c.connect("tcp://127.0.0.1:4242")
count = 0
while True:
start = datetime.datetime.now()
print(c.hello("RPC"), count+1, start.microsecond / 1000.0)
count += 1
if count == 30:
break
추가 : Node를 14버전에서 10버전으로 다운그레이드하면 npm install zerorpc로 잘 설치가 된다. (windows-build-tools –vs2017 설치) Node에서 파이썬의 hello
메소드를 30회 호출하고 결과를 전달받기까지 총 소요 시간은 60ms, 회당 2ms로 파이썬보다 훨씬 빨랐다. 스트림을 사용하면 더 빠를 것 같다. 참고로 Node 14버전에서는 실행이 안 된다.
var zerorpc = require("zerorpc");
var client = new zerorpc.Client();
client.connect("tcp://127.0.0.1:4242");
let count = 0;
while(true) {
let d = new Date()
let msg = 'NODE:'+("00"+count).slice(-2)+("000"+d.getMilliseconds()).slice(-3)
client.invoke("hello", msg, function(error, res, more) {
console.log(res);
});
count++;
if(count > 30)
break;
}
gRPC
경량화/푸시/스트리밍 등을 지원하는 HTTP/2 기반에, 작은 메시지 페이로드를 갖는 ProtoBuf 직렬화 포맷 이진 데이터를 사용하는 RPC 프레임워크다. HTTP/1.1+JSON 방식에 비해 상당히 효율적이라고 한다. 특히 gRPC-Web을 사용하면 브라우저에서 gRPC 서비스에 접근할 수 있다. 작은 서비스들을 유기적으로 결합한 마이크로 서비스, 백엔드 간 실시간 양방향 스트리밍 통신, 다중 언어를 사용하거나 동일한 머신에 있는 앱 간의 통신, 네트워크가 제한된 환경 등에 유용하다고 한다. 구글에서 만들었다.
Node에서 파이썬의 sayHello
함수를 30회 호출하고 결과를 전달받기까지 총 소요 시간은 100ms, 회당 3.33ms였다.
python37 -m pip install --upgrade pip
python37 -m pip install grpcio
python37 -m pip install grpcio-tools
cd grpc/examples/python/helloworld
python37 greeter_server.py
git clone -b v1.34.0 https://github.com/grpc/grpc
cd grpc/examples/node/dynamic_codegen
npm install
node greeter_client.js
// grpc/examples/protos/helloworld.proto - service Greeter 부분에 추가
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
# .proto 파일을 수정하면 컴파일해야 한다. node-watch 등으로 자동 컴파일이 되려나?
python37 -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/helloworld.proto
# grpc/examples/python/helloworld/greeter_server.py - class Greeter 부분에 추가
def SayHelloAgain(self, request, context):
return helloworld_pb2.HelloReply(message='Hello again, %s!' % request.name)
// grpc/examples/node/dynamic_codegen/greeter_client.js - main() 부분에 추가
client.sayHelloAgain({name: 'you'}, function(err, response) {
console.log('Greeting:', response.message);
});
gRPC-Web
윈도우 환경에서 퀵스타트 문서를 따라 실행해보았으나 docker-compose up
15단계에서 실패…
https://docs.docker.com/docker-for-windows/install/ # docker, docker-compose for windows
git clone https://github.com/grpc/grpc-web
cd grpc-web
docker-compose pull
docker-compose up -d node-server envoy commonjs-client # 15단계에서 bash 어쩌구 뜨면서 실패
http://localhost:8081/echotest.html
docker-compose down
그래서 gRPC-Web Hello World Guide를 따라 해보았다.
protoc-gen-grpc-web과 protoc를 사용해 helloworld_pb.js
과 helloworld_grpc_web_pb.js
를 생성한다.
protoc -I=. helloworld.proto --js_out=import_style=commonjs:. --grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
Node 서버를 실행한다.
npm install
npx webpack client.js
node server.js
윈도우라서 envoy.yaml을 약간 수정해주고 도커로 envoy를 실행한다. 실행명령어도 윈도우는 조금 다르다.
hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}]
# linux
docker run -d -v "$(pwd)"/envoy.yaml:/etc/envoy/envoy.yaml:ro --network=host envoyproxy/envoy:v1.16.1
# windows
docker run -d -v D:/grpc-web-hw/envoy.yaml:/etc/envoy/envoy.yaml:ro -p 8080:8080 -p 9901:9901 envoyproxy/envoy:v1.16.1
파이썬 심플 서버도 띄워준다.
python37 -m http.server 8081
브라우저로 localhost:8081
로 들어가 콘솔을 열어 보면 Hello! World
가 찍혀 있는 것을 볼 수 있다.
샘플에 있는 스트림 예제인 sayRepeatHello
를 사용해 Hey! World
를 브라우저로 30회(매회 10ms의 delay) 전달하는 데 소요된 시간은 로컬호스트 환경에서 평균 350ms 정도였다.
마지막으로 gRPC 서버를 Node가 아닌 파이썬으로 대체해보았다. 파이썬 예제의 HelloWorld 서버와 브라우저 gRPC-Web이 서로 잘 통신하여 Hello, World!
가 찍히는 것을 확인했다.
순위
서비스 대 서비스 / 1회 통신 소요 시간
-
zeroRPC : 파이썬↔Node / 2ms
-
Mosquitto : 파이썬↔Node / 2.33ms
-
TCP Socket : 파이썬↔Node / 2.66ms
-
gRPC : 파이썬↔Node / 3.33ms
-
zeroRPC : 파이썬↔파이썬 / 5ms
브라우저 대 서비스 / 1회 통신 소요 시간
-
gRPC-Web : 크롬↔Node / 0.5ms
-
socket.io, engine.io : 크롬 ↔ Node / 2.33~3.33ms
-
mqtt.js, paho js : 크롬↔원격지브로커(broker.hivemq.com) / 30~40ms
결과
서비스 간 통신에서는 Socket, Message Queue, RPC 모두 좋은 성능을 보여주었으며, 파이썬에 비해 Node일 때 성능이 좀 더 좋았다. 브라우저와 서비스 간의 통신에서는 아쉽게도 정확한 비교가 어려웠지만, gRPC-Web 의 성능이 엄청난 것을 확인할 수 있었다.
참고
- 마이크로 서비스의 서비스 간 통신 - Azure Architecture Center …
- 프로세스 간 통신
- 가볍게 마이크로서비스 구축해보기-1. 마이크로서비스 아키텍처 …
- IPC 통신(PIPE, Message, Shared, Memory Map, Socket, RPC)
- Low-latency communication of micro-services in remote, IPC and threading scenarios
- IPC Between Node & Python
- Evolution of calling Python from Node
- python client와 node.js server의 tcp 통신 질문 입니다.
- TCP 소켓 서버와 클라이언트 구현
- Running Python code from Node.js using Socket.IO - Medium
- socket.io
- engine.io
- socket.ENGINE
- 오픈소스 메시지큐(Message Queue) 알아보기
- MQTT란?
- MQTT 적용을 통한 중계시스템 개선 - 우아한형제들 기술 블로그
- 영혼 없이 Windows에 Mosquitto 설치하기 - bigboss.io
- Eclipse Paho를 활용한 Python 기반의 MQTT 통신 구현 : 네이버 …
- MQTT Broker Mosquitto 분석
- Two Way communication Using MQTT and Python
- Use MQTT to stream real-time data – IBM Developer
- MQTT와 마이크로 서비스로 공공 데이터를 공공 이벤트 스트림으로 변환하기
- MQTT.js - Browser
- Eclipse Paho JavaScript Client
- HTTP/2
- ZeroMQ Introduction - Velog
- ZeroMQ 정리
- Libzmq Chapter 1 - Basics
- Connecting ZeroMQ from browser to server
- jszmq
- node-python-collaboration - npm
- 네이버클라우드 기술&경험 - 시대의 흐름, gRPC 깊게 파고들기 #1
- 네이버클라우드 기술&경험 - 시대의 흐름, gRPC 깊게 파고들기 #2
- gRPC 서비스와 HTTP API 비교 - Microsoft Docs
- gRPC python quickstart
- gRPC node quickstart
- gRPC web quickstart
- gRPC-Web: Moving past REST+JSON towards type-safe Web APIs
- 프로덕션 환경에서 사용하는 golang과 gRPC - 뱅크샐러드
- 디지털 세상을 만드는 아날로거 - Microservices with gRPC
- gRPC-Web Hello World Guide
- 브라우저 앱에서 gRPC 사용
- Performance Test — gRPC vs Socket vs REST API
- Data Streaming via GRPC vs MQTT vs Websockets
- gRPC Anywhere - StackRox
- 프로세스 간 통신 방법
- IPC-Bench