2022. 10. 13. 18:18ㆍ강의 내용 정리/풀스택서비스네트워킹
ZeroMQ
1. 프로토콜의 위치
과거에는 운영체제 내부에 인터넷 프로토콜이 들어가있었지만 지금은 어플리케이션에 인터넷 프로토콜이 들어가있다. 이에 따라 성능이나 개발 및 발전 용이성에서 매우 유리해졌다.
쓰레드 간 통신은 메모리를 통해 이뤄진다. 진짜 파일은 보조기억장치 메모리를 사용한다. 이에 따라 굉장히 느리다. memory map 방식을 사용한다. 또한 기존의 통신에서 사용한 기능을 지원한다. TCP/IP, Memory map 등등을 지원한다.
1) 전통적인 인터넷 프로토콜
transport layer나 network layer가 OS 안으로 들어가 있는 것을 확인할 수 있다.
이에 따라 Socket API는 Kernel과 응용 프로그램을 연결하는 고리가 되었다. POSIX 계열의 운영체제는 소켓을 file descriptor로 어플리케이션에 노출했다. 즉, 소켓 또한 파일의 한 종류로 여긴 것을 알 수 있다.
2) ZeroMQ 프로토콜의 위치
ZeroMQ에서는 어플리케이션 layer에서 통신 프로토콜을 지원한다.
IPC, TCP, UDP, 멀티캐스트, 웹소켓 등등을 사용한다.
2. ZMQ의 이해
1) ZMQ란?
(1) Concurrency Framework
다양한 transport layer를 지원하고 in process, inter-process, TCP, multicast와 같은 기능을 모두 가지고 있고, 이를 동시다발적으로 사용할 수 있기 때문에 이는 concurrency framework하고 표현한다.
(2) 1:N, N:1, N:M 통신 지원
N:M의 기능을 지원하기에 1:N, N:1 등등의 기능도 기본적으로 지원한다. pub-sub, task distribution, request-reply 등등의 기능도 지원한다. 내부적으로는 멀티쓰레드, 멀티프로세스가 지원되기에 비동기적으로 동작한다. 따라서 이를 직접 구현할 필요가 없다. 또한 대부분의 운영체제에서 다 돌아간다.
cf) pub-sub의 예시로는 날씨 위젯있다. 서버에서는 클라이언트가 해당 지역에 있으면 publishing한다.
(3) 비동기 지원
비동기를 지원하기에 스피드가 매우 빠르다.
cf) 참고 사항
- ZeroMQ는 설치를 해야한다. ZeroMQ는 소켓이 사실상의 프로그램의 기능이기에 TCP/UDP의 소켓 이름을 비슷하게 가져온다.
- 서버와는 다르게 어플리케이션을 사용할 때는 라이센스가 매우 중요해진다. ZeroMQ는 LGPL 3 라이센스이지만 static linking은 예외로 쳤기에 static linking을 하더라도 라이센스를 공개하지 않아도 된다.
2) ZeroMQ의 특징
- ZeroMQ에서 Zero의 의미는 broker가 없고, zero latency, zero cost, zero administration을 의미한다.
- 비동기적으로 동작하기에 메세지를 주고 받는 것에 대해서는 크게 고민할 필요가 없다.
- 클라이언트 쪽으로 쓸 수 있는 기술이다.
- 기존에 있던 소켓 API를 흉내낸 것이 많다.
- TCP/UDP, in-process, inter-process, multicast, web socket 등등 다양한 trasports를 지원한다.
- 60여개의 도표와 750개의 예시의 가이드가 제시된다.
3) ZeroMQ patterns
(1) Request-Reply
통신을 위해 각 소켓에는 REQ, REP, DEALER, ROUTER의 역할을 부여해 통신을 한다.
(2) Pub-sub
구독한 사람들이 있으면 이에 맞춰 정보를 보낸다.
- XPUB, XSUB는 구독을 하지 않는다는 정보를 보낸다.
(3) pipeline
여러 스텝이 걸쳐가며 하나의 작업으로 연결되어있을 때 pipeline이라 한다.
- PULL, PUSH의 작업을 한다.
- fan-out/fan-in pattern을 가지고 여러 동작을 거친다.
(4) Exclusive pair
두 개의 소켓을 정보를 주고 받을 때 배타적으로 밀결합되어서 정보를 주고받을 때 이를 사용한다.
4) ZeroMQ Sockets
(1) Conventional sockets
바이트를 streams으로 보낸다.
- SOCK_STREAM: connection-oriented 바이트 스트림
- SOCK_DGRAM: connection-less 데이터 그램
(2) ZeroMQ sockets
비동기적인 메세지 큐의 추상화하여 사용하기에 소켓에 각 역할을 부여해 사용할 수 있다.
- N:N을 타겟한 형태이며, 메세지를 이산적으로 보낸다.
- 추상화하며 어떻게 비동기적으로 만들 것인지 등등에 대해 설정한다.
(3) ZeroMQ에서 비동기
TCP/UDP에서는 에러처리를 많이 해가면서 통신을 하고자 했다. ZeroMQ는 물리적으로 있는지, 서버가 끊어졌는지 등등은 ZeroMQ가 알아서 전담한다. 어플리케이션이 연결이 된건지 안된건지 모른 상태에서 데이터를 전송하더라도 정보를 전송할 수 있다. 서버가 다운되더라도 메세지를 전송할 수 있고, 나중에 다시 연결되었을 때 ZeroMQ가 알아서 메세지를 보낸다.
TCP/UDP에선 링크가 다운되었다면 다시 연결 설정을 해야하지만 ZeroMQ는 통신 연결이 되지 않아도 이를 보낼 수 있다. 프로그래머가 해야하는 일을 어플리케이션이 알아서 하게 된다.
(4) 소켓의 라이프타임
- BSD 소켓과 유사하다.
- 소켓에 대해 옵션을 configuration할 수 있다.
- 일대일 단계가 아니라 여러 프로그램들이 연결된다.
- 데이터를 옮기거나 메세지를 받는 데에 소켓이 사용된다.
(5) ZeroMQ의 bind, connect
기능적으로 누가 클라이언트, 서버인지를 확인해야한다. ZeroMQ는 서로 서버, 클라이언트가 상대적인 입장에서 될 수 있기에 bind, connect를 하는 과정이 간단하지 않다.
(a) bind 할 때
일반적으로 안정적인 컴퓨터에서 서버의 역할을 하기에 바인드를 한다. 예를 들어 request/reply 패턴에서는 서버(service provider)는 바인드를 하고, client는 connect 요청을 한다.
(b) connect를 사용할 때
만약 안정적인 컴퓨터가 어떤 것인지 알 수 없는 환경에서는 다른 모든 부분과 연결할 수 있는 중간에 있는 컴퓨터에게 connect를 하게 된다.
패턴이 있는 경우에는 그 패턴을 따르면 되지만 만약 패턴에 없는 경우에 bind를 사용할 때는 여러 프로그램 중 주로 상주해있는 프로그램이 bind한다. 즉, 서비스를 제공하는 측이 bind를 하는 것이 아닌 미리 살아나서 오래 있는 프로그램들이 통상 bind를 한다. 이는 대기상태가 된다.
반대로 상대적으로 있을 때도 있고, 없을 때도 있는 프로그램이 connect를 한다.
무조건 서버라고 해서 bind하고, 클라이언트가 connect하는 관계는 아니다. 예를 들어 정보를 저장하는 레포지토리로서 있는 프로그램은 일반적으로 오랫동안 살아있기에 bind를 할 수 있다. 이때 정보를 제공하는 측이 connect 하게 된다.
cf) 중앙집중형 Broker 기반 메세징 개념, RabbitMQ
중앙집중형 Broker 기반 메세징 개념으로 중앙에서 모두에게 뿌리는 작업을 한다. 대량 이메일/문자 발송 등등이 이에 해당된다.
정보가 제공되면 중앙집중형으로 존재하는 Broker가 메세지를 뿌리는 방식으로 ZeroMQ와는 차이가 있는 대표적인 개념이다.
통상 소스코드만 뿌리기보단 도커를 사용해서 전달하기도 한다.
3. ZMQ 패턴 및 샘플 코드
1) Request-Reply pattern
- TCP/UDP 서버를 짜는 것과 비슷하다.
- 클라이언트가 요청을 보내면 서버는 응답을 한다.
- 소켓의 이름은 REQ 소켓, REP 소켓이 된다.
- send, recv를 반복문에서 사용한다.
- 두개의 메세지를 연속으로 보내는 등의 다른 sequence가 실행되면 send와 recv의 호출에서 -1을 리턴한다.
(1) 서버 코드
서버에서는 쓰레드를 직접 만들어서 할당할 필요가 없다.
(2) 클라이언트 코드
- 클라이언트를 구분하는 코드가 없다.
- b는 아스키코드로 보내겠다고 사용하는 것을 의미한다. 통신에선 통상 아스키코드로 보낸다. 파이썬은 기본이 유니코드이다.
- 1:N인 관계이더라도 서버는 수정할 필요가 없다.
(3) 실행 화면
2) Publish-Subscribe pattern
Publisher는 정보를 만드는 사람이고, Subscribe는 정보를 받는 사람이다.
- Publisher, Subscriber 패턴은 one-way이다.
- Publisher에서 bind를 한다.
예를 들어 날씨 정보를 전달하는 예시를 생각해볼 수 있다.
- Publisher는 zip_code, 온도, 상대 습도와 같은 업데이트된 날씨 정보를 Subscriber에게 push할 수 있다.
- Client는 zip_code에 따라 본인이 업데이트할 정보를 받을 수 있다.
- 소켓 API의 옵션처럼 setsockopt_string을 설정해 클라이언트는 본인이 듣고 싶은 정보만 듣도록 설정할 수 있다.
(1) 서버 코드
- 업데이트된 정보를 랜덤값으로 만들어서 이를 send_string으로 보낼 수 있다.
- zmq.PUB를 하는 경우에는 send를 할 수 있지만 recv를 하는 경우에는 에러가 나온다. 이처럼 소켓에 따라 기능을 한정할 수 있다.
(2) 클라이언트 코드
- 프로그램을 실행할 때 입력하는 정보는 sys.argv에 들어간다.
- setsocketopt_string을 통해 받고자하는 메세지를 필터링으로 거를 수 있다.
- recv_string()는 setsockopt_string에서 필터링된 내용을 스트링 형태로 받는 함수이다.
(3) 실행 화면
- 비동기적으로 동작하고, unidirectional하다. Sub은 send를, Pub은 recv를 할 수 없다. 만약 Sub이 또 내용을 전달하는 경우에는 추가적인 기능을 또 다시 만들어야한다.
3) PUB/SUB with PUSH/PULL example
위의 예시에서는 REQ, REP를 해도 구현은 된다. 하지만 요청/응답에 대한 것보단 정보를 올리면 저장하는 역할을 하기 때문에 PUSH/PULL로 하는 것이 조금 더 정확한 역할이 될 수 있다.
딜러, 라우터는 클라이언트가 서버에게 다른 클라이언트는 어떤지 물을 때 사용한다. 이는 아래 예시에선 나오지 않다.
4) Pipeline pattern
여러 단계들이 연이어서 이뤄질 때 사용된다.
Fan-in은 여러 개를 하나로 모아서 전달되는 것이다. 이때 fair queuing을 지향하기에 n개의 PUSH에 대해 동등하게 하나씩 내용을 가져온다. REQ, REP와는 이 부분에서 차이가 있다.
Fan-out은 여러 개로 분산해서 보낸 것을 의미한다. 하둡의 구조와 비슷하다.
PUB/SUB with PUSH/PULL example 설명
(1) Server
- PUB, PULL 기능을 한다.
- 구동 시 Publish, PULL 서버 기능을 활성화한다.
- Client들이 주기적으로 보고하는 상태 정보를 PULL한 뒤, 다시 모든 Client들에게 Publish해야한다.
(2) Client
- SUB, PUSH 기능을 한다.
- 구동 시 Subscribe, PUSH 기능을 활성화한다.
- Server가 Publish한 본인을 포함한 전체 Client들의 상태를 Subscribe 기능을 통해 수신한다.
(1) 서버 코드
- Context는 하나만 만들었다.
- 각 기능별로 포트번호를 다르게 설정한다.
(2) 클라이언트 코드
- subscriber는 polling을 해서 받은게 있는지 확인하고, 받은 게 있다면 받은 메세지를 화면에 뿌린다. 이는 다른 동작도 해야하기 때문이다.
- tcp 채팅 프로그램과 유사하다.
(3) 실행 결과
ZeroMQ는 어플리케이션단에서 많은 고민을 해서 쓰레드 할당과 같은 아래 단계에서의 동작을 고민하지 않아도 된다.
5) Dealer-Router Pattern
Dealer와 Router가 정보를 주고 받는다.
화면에 최상단으로 노출된 것은 서버에 접속할 때 딜러의 형태로 접속한다. 그리고 서버 밑에는 여러 워커가 존재해 각 워커들은 쓰레드를 사용해서 구현할 수 있다.
즉, 클라이언트와 서버의 관계가 N:1이고, 서버와 워커와의 관계가 1:N일 때 사용할 수 있다.
- Dealer는 위에서 내려온 것을 아래로 내리는 역할을 한다.
- Router는 정보를 전달하는 것을 의미한다. 이에 따라 Router는 어느 방향에 보냈는지 기억하고, 이를 처리한다.
- request/reply로 짤 수도 있지만 아래로 내린다는 역할 때문에 이렇게 표현한다.
비동기적으로 request, reply를 처리하기에 클라이언트는 reply를 기다리지 않고 requests를 여러번 보낼 수 있다. Server는 새로운 requests를 기다리기 않고 응답을 여러개 보낼 수 있다.
각 클라이언트가 요청한 것들에 대해 서버가 이를 받고, 워커들에게 일을 처리하게끔한다. 따라서 이는 일반적인 N:M 클라이언트 서버의 Request, Reply의 일반적인 형태이다.
일반적으로는 두개의 단으로 나눠서 표현하곤 한다. 하지만 그럴 경우에는 Request, Reply와 크게 다른 점이 없다. 이에 따라 구체적으로 워커까지 포함한 세 개의 단으로 나눠 표현할 수 있다.
(1) 서버 코드
- 그림의 가운데에 있는 것은 ServerTask로 구현된다. 파라미터로 전달하는 것은 워커의 개수를 의미한다.
- main 함수는 start, join으로 대기하도록 한다.
- 워커들이 일할 수 있도록 서버와 워커 모두 Thread를 상속받아서 이를 사용한다.
- run 함수에서는 클라이언트의 요청을 받기 위해 ROUTER를 통해 클라이언트와 bind 한다. 이후 워커에게 전달해주기 위해 DEALER를 통해 bind해준다.
- Worker들도 DEALER 역할을 부여받는다.
- 반복문을 통해 워커들에게 숫자를 함께 부여하여 할 일을 전달한다.
- proxy를 통해 frontend로 들어온 것을 backend로 보내는 것을 알아서 한다.
- Dealer의 경우에는 메세지를 전달받을 때 recv_multipart() 메서드를 사용한다.
cf) inproc: 프로세스 내의 쓰레드에 연결한다.
(2) 클라이언트 코드
- subscriber.poll(100): 최대 100ms만큼의 시간을 기다리면서 poll이 있는지 확인한다.
- zmq.POLLIN: zmq를 사용할 때 정보를 읽어가겠다는 의미이다. 만약 소켓을 따로 다루는 경우에는 소켓의 이름을 설정해야한다. 하지만 그렇게 되면 소켓이 다양해지면 조건문이 지저분해질 수 있다. 이에 따라 Poller를 사용해 이를 처리해줄 수 있다.
- Poller()로 열고, register에 이를 등록한 뒤, 나중에 poll.poll()을 설정해 모든 소켓에 대해 잘 처리할 수 있도록 한다.
- 위의 예시에서의 클라이언트는 동기적으로 동작한다? 따라서 수신에 대한 핸들러 함수를 사용해 비동기적으로 동작할 수 있게끔 코드를 수정할 수 있다.
- 송신은 run 루프 내에서, 수신은 별도의 핸들러 함수를 만들어서 처리한다.
- 데이터베이스를 추가하거나 다른 컴퓨터와의 통신도 함께 처리할 수 있다.
cf) proxy는 들어가면 안되는 웹사이트를 차단(포르노 사이트)하거나 캐시를 저장해두는 역할을 한다. // 보안, 캐쉬
(3) 실행 결과
(a) single thread
(b) multiple threads(Server)
(4) 향상된 클라이언트 코드
비동기적으로 수신, 송신할 수 있도록 코드를 수정했다.
recvHandler 메서드를 추가하고, 각 client마다 수신에 대한 쓰레드를 할당하여 송수신을 비동기적으로 처리한다.
-> ROUTER와 DEALER는 REQ/REP와는 다르게 비동기적으로 처리되기 때문에 송수신이 비동기적으로 작동될 것 같은데 왜 분할했는가?
6) P2P
P2P에서는 영구적으로 살아있는 서버는 없고, 살아있고, 죽어있는 프로그램 간의 통신을 하는 것이다. 이에 따라 동적으로 연결을 하고, 끊어내는 네크워크를 의미한다. 모두가 N:M으로 처리를 한다.
192.168.55.이 같으면 동일한 네트워크로 가정할 수 있다. 이를 활용해 1번부터 254번까지 모두 연결(full mesh)을 한다. 따라서 모든 프로그램이 살아나면 대화를 할 수 있고, 한 컴퓨터에서 정보를 보내면 다른 컴퓨터에 모두 전달하는 방식으로 진행한다. 하지만 컴퓨터의 사양에 따라 동작하지 않을 수 있다.
모든 컴퓨터가 살아날 때는 listen을 하고, 살아있는 컴퓨터와의 통신을 진행할 수 있다.
cf) Peer to Peer라고 얘기하지만 서버가 아예 없을 수 없다. WebRTC도 마찬가지로 서버리스를 강조하지만 서버가 있기는 하다. 둘 이상이 대화를 나눌 때에는 둘 중 누군가가 서버 행위를 해야한다. 즉, 실질적으로 동작하기 위해서는 누군가는 임시로라도 서버 역할을 해야한다. P2P 또한 마찬가지이다.
(1) 코드
main
임시서버가 존재해서 채팅을 하고자 하는 프로그램을 등록하고 해지하는 역할(subscriber)을 한다. 채팅 서버도 존재한다.(publisher, collector), 서버가 있는 곳을 가리키는 역할(nameserver)도 한다. 해당 작업은 모든 클라이언트가 가지고 있어서 해당 서버가 없다면 자신이 그 역할을 한다.
- beacon: 주기적으로 메세지를 뿌리는 역할
- 초반에 나오는 if문은 서버가 있는지를 확인하고 없다면 본인이 그 역할을 한다. 그 이후에 있는 코드는 본인이 채팅을 보낼 때를 의미한다.
search_nameserver
- search_name_server: 서버가 있는지 확인하기 위해선 반복문을 통해 살아있는지 죽어있는지 확인한다.
- ZeroMQ는 비동기이니까 최대 2초까지 기다려도 괜찮다.
beacon_nameserver
- beacon_nameserver는 본인이 nameserver라고 응답한다. 이를 통해 nameserver가 있는지 확인한다.
user_manager_nameserver
- 현재 연결된 유저들의 정보를 담고 있다.
- 단순히 요청, 응답의 구조를 이루고 있기에 REQ, REP를 사용한다.
relay_server_nameserver
- PUB와 PULL을 사용해서 모든 유저에게 정보를 전달한다.
get_local_ip
- get_local_ip는 8.8.8.8에 접속한 뒤 외부에서 접속해본 뒤 컴퓨터의 ip 어드레스에 접속해보고 그 숫자를 알 수 있다. 이와 같이 하지 않으면 OS별로 IP address를 알기 위해 따로 처리해야한다. 해당 예제의 경우에는 외부의 네트워크를 사용해야한다. 하지만 동일한 네트워크의 컴퓨터와 통신하는 것을 가정했기에 이와 같이 작업하는 것도 좋다. 가상 머신은 내 컴퓨터 내에서 네트워크를 하나 더 만들어서 사용하기에 해당 통신을 진행하기 어려울 수 있다.
(2) 실행 결과
cf) 블록체인은 사실 P2P 프로그램에서 출발했다. / SOLID: 팀버너스리가 만든 자신의 정보를 자신이 가질 수 있도록 함
cf) ZeroMQ를 P2P로 구현하는 것은 패턴으로 존재하진 않는다.
과제 수행 시에는 P2P는 하지 않아도 된다.
'강의 내용 정리 > 풀스택서비스네트워킹' 카테고리의 다른 글
풀스택 서비스 네트워킹, 구글은 크롬을 왜 만들었을까? (0) | 2022.12.04 |
---|---|
풀스택 서비스 네트워킹(6), HTTP/1.1 (1) | 2022.10.18 |
풀스택 서비스 네트워크(4), Socket (0) | 2022.10.11 |
풀스택 서비스 네트워킹(3), OSI Architecture(L4) (1) | 2022.10.07 |
풀스택 서비스 네트워크(2), OSI Architecture(L1~L3) (1) | 2022.09.30 |