네트워크

ZeroMQ & basic ZeroMQ Patterns

teo_99 2024. 4. 20. 00:08

선조의 개발자들은 클라이언트나 서버를 구현하기 위해 Berkeley Sockets와 같은 Socket API를 사용해 프로그램을 작성했을 것이다. 다만 Socket API의 경우 운영체제와 바로 맞닿아 있는 부분이기에, 저수준의 지엽적인 내용이 많고 사용하기가 복잡하다.

 

따라서 자주 발생하는 부분은 패턴으로 정의하고, 추상화하고자 하는 니즈가 자연스레 생겨났다. 그 결과 전송 계층 위에서 네트워크를 제어할 수 있는 소프트웨어들이 등장하게 되었고, ZeroMQ는 그러한 소프트웨어 중 하나이다.

ZeroMQ 소개

ZeroMQ는 분산 어플리케이션이나 동시처리 어플리케이션에서 활용할 수 있는 고성능 비동기 메세징 라이브러리이다. 다만 이 말의 뜻을 개인적으로는 이해하기 어려워서 추가적으로 조사해봤다. 

 

분산 어플리케이션(distributed application)이란, 네트워크로 연결된 여러 컴퓨터에서 실행되는 프로그램을 의미한다. 가장 이해하기 쉬운 예시로는 프론트엔드, 백엔드가 있는데, 프론트엔드는 웹 브라우저에서 실행되고 백엔드는 별도의 엔드포인트에서 구축되기 때문에 다른 어플리케이션이다. 그렇지만 이들은 하나의 목적(회원가입, 쇼핑 등)을 처리하기 위해 네트워크로 협력하기 때문에 분산 어플리케이션이라고 볼 수 있는 것이다.

 

동시처리 어플리케이션(concurrent application)이란, 말 그대로 동시에 여러 작업을 처리할 수 있는 어플리케이션을 의미한다. 멀티스레드와 같은 기법을 사용해 동시성 제어를 수행하거나, 비동기 처리를 진행한다면 동시처리 어플리케이션이라고 볼 수 있겠다.

 

아무튼, ZeroMQ가 분산 어플리케이션이나 동시처리 어플리케이션에서 사용된다는 사실을 이해했으니, 1-depth 더 들아가보도록 하자.

 

'Zero'의 의미

우선 ZeroMQ의 설계 철학을 알아보기 위해 'Zero'가 의미하는 바가 무엇인지부터 이해해보자. 공식 문서에 따르면, 'Zero'는 zero broker, zero latency, zero cost(open source), zero administration을 의미한다고 한다. 이 중 중요한 부분은 'zero broker'라는 사실인데, 이는 메세지 브로커가 없이 동작할 수 있음을 의미한다.

 

메세지 브로커란 애플리케이션이나 시스템 및 서비스가 서로 간에 통신하고 정보를 교환할 수 있도록 해주는 소프트웨어인데, 쉽게 말해 sender와 receiver 사이에서 메세지를 큐에 쌓거나, 라우팅해주거나, 어떠한 처리를 해주는 역할이라고 보면 된다. 메세지 브로커에 대한 내용이 궁금하다면 여기를 참고하면 좋을 것 같다.

 

ZeroMQ는 이러한 메세지 브로커가 존재하지 않는다. 따라서 메세지 브로커가 존재하는 상황과 대비해서, 보다 단순한 환경에서 사용될 수 있다. 브로커를 추가한다는 것은 장애 지점, 관리 비용이 늘어난다는 것을 의미하는데, ZMQ 공식 Reference에서는 이러한 이유를 들며 ZMQ의 활용성을 제시한다. 물론 관리 비용을 감당할 수 있는 상황이고, 대규모 분산처리 환경에서 메세지 브로커의 도입으로 인한 리턴이 확실한 상황이라면 ZMQ보다는 Apache Kafka와 같은 기술을 선택할 수도 있다.

 

ZeroMQ가 필요한 이유

그렇다면 왜 ZeroMQ가 필요한 것일까? Berkeley Sockets와 같은 소켓 프로그래밍으로도 충분히 네트워크에 접속해 메세지를 보내는 프로그램을 작성할 수 있었는데 말이다. 다만 이렇게 Raw한 4계층 프로토콜을 사용하게 되면 여러 문제가 발생할 수 있는데, 주로 다음과 같은 것들이다.

 

  • 매 요청을 포어그라운드에서 처리할지, 백그라운드로 처리할지에 대한 고려가 필요하다.
  • 클라이언트와 서버의 역할이 명확히 나뉘어져 있기 때문에, 서버는 클라이언트가 요청할 때 항상 존재해야 한다는 책임이 있다. 또한, 서버와 서버가 통신할 때에는 어떤 노드가 reconnect를 할지 기준이 불명확하다.
  • 메세지를 어떻게 표현해야 할지 결정하기 어렵다. 크거나 작은 여러 종류의 데이터(비디오, 텍스트 등)에 대해 적절한 메세지 형태를 고려해야 한다.
  • 메세지 큐를 관리하기 위한 방법이 별도로 마련되어야 한다. 또한, 메세지 큐가 꽉 찼을 때의 방법론도 별도로 마련되어야 한다.
  • 3, 4계층에 대한 변경에 쉽게 대응하기 어렵다. 가령 IPv6을 사용한다면, 많은 코드를 바꿔야 할 것이다.
  • 네트워크 에러에 대한 후속 처리는 어떻게 할 것인지 결정해야 한다.

이러한 것들은 Messaging layer가 없던 시절 문제가 되는 부분들이었다. 이는 네트워크를 사용해 메세지를 보내는 개발자들이 매번 고려해야 하는 부분이 많았음을 의미한다. 그리고 이는 ZeroMQ가 등장한 이유이기도 하다.

 

 

ZeroMQ Patterns

마지막으로 ZeroMQ에서 지원하는 몇가지 패턴들에 대해 알아보도록 하자.

 

Request Reply Pattern

https://zguide.zeromq.org/docs/chapter1/

 

Request-reply, which connects a set of clients to a set of services. This is a remote procedure call and task distribution pattern. - ZMQ Reference

 

가장 처음으로 소개할 패턴은 Request Reply다. 직역하면 알 수 있듯이, 요청을 보내고 응답을 받는 패턴이다. 가장 기본적인 패턴이며, 위 그림에서는 Client가 Hello를 보내면 Server가 World로 응답하는 모습을 보여준다. Client는 Synchronous하게 요청을 보내고 기다리며, Server는 Asynchronous하게 요청을 처리한다는 점에 주의하자.

 

또한, Request - Reply 패턴을 사용하는 경우 단순히 1:1 통신만 지원하는게 아니라 Autonomous하게 1:N 연결도 지원되기에 서버 측에서는 어떠한 소스 코드의 수정도 필요가 없다. 이는 Socket 프로그래밍을 하는 경우, 일일이 비동기 처리, 멀티스레딩을 고려해야 했던 것과 비교하면 대조적이다.

 

Publish/Subscribe Pattern

https://zguide.zeromq.org/docs/chapter1/#Getting-the-Message-Out

Pub-sub, which connects a set of publishers to a set of subscribers. This is a data distribution pattern. - ZMQ Reference

 

그 다음으로는 Publish/Subscribe 패턴인데, 직역하면 '발행/구독' 패턴이다. 말 그대로 메세지를 발행하는 주체가 있고, 이는 구독하는 대상들에게 전송된다. 여기서 중요한 점은, 단방향 메세지 전송으로 Subscriber는 Publisher에게 메세지를 전송할 수 없다. 그리고 이러한 특징으로 인해 날씨 정보 앱 등에서 활용될 수 있다. 날씨 정보에 대한 업데이트가 있을 때마다 Subscriber들에게 메세지가 전송되는 것이다.

 

다만 Subscriber들의 경우, 필요로 하지 않는 메세지도 받을 수 있는데, 이는 ZMQ 라이브러리에 내장된 setsockopt라는 메소드를 통해 ZMQ_SUBSCRIBE 옵션을 지정함으로서 필터를 설정할 수 있다. 아무런 옵션을 지정하지 않으면 모든 메세지를 수신한다. 그리고 Subscriber는 다수의 Publisher와 connect될 수 있음도 참고하자.

 

이러한 ZeroMQ의 Pub/Sub 구조에서는 'slow joiner' 현상이 발생할 수 있다. Publisher가 Subscriber의 가입 여부 등을 신경쓰지 않고 메세지를 발행하기 때문에 소실될 수 있다는 것이다. 이러한 문제를 해결하기 위해서는 Subscriber가 가입할 때까지 Publisher를 기다리게 하거나, 일정 시간을 기다리게 할 수 있다고 한다.

 

Pipeline pattern

https://zguide.zeromq.org/docs/chapter1/#Divide-and-Conquer

Pipeline, which connects nodes in a fan-out/fan-in pattern that can have multiple steps and loops. This is a parallel task distribution and collection pattern. - ZMQ Reference

 

다음으로 소개할 패턴은 Pipeline 패턴이다. 이는 병렬처리와 같은 작업에서 사용할 수 있는 패턴인데, Ventilator가 작업을 분배하고 Worker가 처리하며, Sink가 결과를 수집한다. 해당 패턴에서는 Ventilator와 Sink가 Stable한 부분이고, Worker는 동적으로 변할 수 있는 부분임을 참고하자.

 

이러한 패턴은 multi-node multi-gpu와 같이, 여러 노드에서 각각의 GPU를 사용해서 연산을 처리하는 등의 작업을 진행할 때 사용될 수 있다. 다만 주의해야 할 점은, Pub/Sub과 비슷하게 'slow joiner' 문제가 있어서 Worker가 connected 되기 전에 task를 분배한다거나 하는 경우에는 병렬처리가 제대로 되지 않을 수 있다는 점이다.

 

참고로 Push/Pull이 Pub/Sub과 다른 점은, Push/Pull의 경우 연결된 Pull 소켓들을 대상으로 모두 동일한 메세지를 보내는게 아니라, 라운드 로빈 형식으로 메세지를 보낸다는 점이다. 따라서 각 Worker가 수신하는 메세지가 다르므로 병렬처리에 활용될 수 있는 것이다.

 

Dealer-Router pattern

https://zguide.zeromq.org/docs/chapter3/#The-Asynchronous-Client-Server-Pattern

 

마지막으로 살펴볼 패턴은 Dealer-Router다. Dealer-Router 패턴은 Asynchronous Request/Reply 패턴이라고도 불린다. 클라이언트와 서버 모두 비동기적으로 메세지를 전송하기 때문이다. 

 

Dealer란 비동기적으로 다수의 클라이언트와 양방향 통신하는 역할을 의미하며, Router는 이러한 Dealer에게 메세지를 전달해주는 역할을 한다. 위 그림을 보면 알 수 있듯이, 로드밸런싱과 상당히 닮아 있다.

 

마치며

ZeroMQ의 개념, 특징을 살펴보았고 몇 가지 대표적인 패턴을 알아봤다. 조사해보니 이외에도 수많은 패턴이 존재하는 것을 확인할 수 있었는데, 서비스 요구사항에 맞게 사용하면 될 듯 하다.

 

 

참고 자료

https://zeromq.org/get-started/

https://zguide.zeromq.org/docs/chapter1/#Divide-and-Conquer

https://zguide.zeromq.org/docs/chapter2/#Missing-Message-Problem-Solver

http://api.zeromq.org/4-2:zmq-setsockopt

https://stackoverflow.com/questions/17814436/difference-between-pub-sub-and-push-pull-pattern-in-zeromq

https://www.couchbase.com/blog/distributed-applications/