Node.js 및 Redis를 사용하여 사내에서 Pub/Sub 서비스 구축

게시 됨: 2022-03-10
빠른 요약 ↬ 시스템의 각 메시지에 대한 데이터 크기가 몇 바이트에서 최대 100MB까지 다르기 때문에 다양한 시나리오를 지원할 수 있는 확장 가능한 솔루션이 필요했습니다. 이 기사에서 Dhimil Gosalia는 사내 Pub/Sub 서비스 구축도 고려해야 하는 이유를 설명합니다.

오늘날의 세상은 실시간으로 작동합니다. 주식 거래이든 음식 주문이든 오늘날 소비자는 즉각적인 결과를 기대합니다. 마찬가지로, 우리 모두는 뉴스든 스포츠든 무엇이든 즉시 알 수 있기를 기대합니다. 즉, 제로가 새로운 영웅이다.

이것은 소프트웨어 개발자에게도 적용됩니다. 아마도 가장 참을성이 없는 사람들일 것입니다! BrowserStack의 이야기에 뛰어들기 전에 Pub/Sub에 대한 배경 지식을 제공하지 않는 것이 아쉽습니다. 기본 사항에 익숙하신 분들은 다음 두 단락을 건너뛰셔도 됩니다.

오늘날 많은 애플리케이션이 실시간 데이터 전송에 의존합니다. 소셜 네트워크의 예를 자세히 살펴보겠습니다. Facebook 및 Twitter와 같은 사용자는 관련 피드를 생성 하고 귀하(해당 앱을 통해)는 이를 사용하고 친구를 염탐합니다. 사용자가 데이터를 생성하면 다른 사람들이 눈 깜짝할 사이에 소비할 수 있도록 게시되는 메시징 기능으로 이를 수행합니다. 상당한 지연이 발생하면 사용자가 불평하고 사용량이 줄어들고 지속되면 이탈합니다. 위험도가 높으며 사용자의 기대치도 높습니다. 그렇다면 WhatsApp, Facebook, TD Ameritrade, Wall Street Journal 및 GrubHub와 같은 서비스는 어떻게 대량의 실시간 데이터 전송을 지원합니까?

점프 후 더! 아래에서 계속 읽기 ↓

이들 모두는 일반적으로 Pub/Sub라고 하는 "Publish-Subscribe" 모델이라고 하는 상위 수준에서 유사한 소프트웨어 아키텍처를 사용합니다.

"소프트웨어 아키텍처에서 발행-구독은 발행자라고 하는 메시지 발신자가 메시지를 구독자라고 하는 특정 수신자에게 직접 보내도록 프로그래밍하지 않고 대신 발행된 메시지를 어떤 구독자인지 알지 못하는 클래스로 분류하는 메시징 패턴입니다. 얼마든지 있을 수 있습니다. 마찬가지로 구독자는 하나 이상의 클래스에 관심을 표명하고 어떤 게시자가 있는지 알지 못한 채 관심 있는 메시지만 받습니다."

— 위키피디아

정의가 지루합니까? 우리의 이야기로 돌아갑니다.

BrowserStack의 모든 제품은 자동화 테스트 로그, 새로 구운 브라우저 스크린샷 또는 15fps 모바일 스트리밍 등 상당한 실시간 종속성 구성 요소가 있는 소프트웨어를 (어떤 방식으로든) 지원합니다.

이러한 경우 메시지 하나가 떨어지면 고객은 버그를 방지하는 데 중요한 정보를 잃을 수 있습니다 . 따라서 다양한 데이터 크기 요구 사항에 맞게 확장해야 했습니다. 예를 들어, 특정 시점에 장치 로거 서비스를 사용하면 단일 메시지에 50MB의 데이터가 생성될 수 있습니다. 이와 같은 크기는 브라우저를 충돌시킬 수 있습니다. BrowserStack의 시스템은 향후 추가 제품에 맞게 확장되어야 한다는 점은 말할 것도 없습니다.

각 메시지의 데이터 크기는 몇 바이트에서 최대 100MB까지 다르기 때문에 다양한 시나리오를 지원할 수 있는 확장 가능한 솔루션이 필요했습니다. 즉, 우리는 모든 케이크를 자를 수 있는 검을 찾았습니다. 이 기사에서는 사내에서 Pub/Sub 서비스를 구축하는 이유, 방법 및 결과에 대해 설명합니다.

BrowserStack의 실제 문제의 렌즈를 통해 고유한 Pub/Sub를 구축하는 데 필요한 요구 사항과 프로세스를 더 깊이 이해할 수 있습니다 .

Pub/Sub 서비스에 대한 필요성

BrowserStack에는 약 1억 개 이상의 메시지가 있으며 각 메시지는 약 2바이트에서 100MB 이상입니다. 이들은 서로 다른 인터넷 속도로 언제든지 전 세계에 전달됩니다.

메시지 크기별로 이러한 메시지의 가장 큰 생성기는 BrowserStack Automate 제품입니다. 둘 다 사용자 테스트의 각 명령에 대한 모든 요청과 응답을 표시하는 실시간 대시보드가 ​​있습니다. 따라서 누군가 평균 요청-응답 크기가 10바이트인 100개의 요청으로 테스트를 실행하면 1×100×10 = 1000바이트가 전송됩니다.

이제 더 큰 그림을 생각해 봅시다. 물론 — 우리는 하루에 한 번만 테스트를 실행하지 않습니다. 매일 약 850,000개 이상의 BrowserStack Automate 및 App Automate 테스트가 BrowserStack으로 실행됩니다. 예, 테스트당 평균 약 235개의 요청-응답이 있습니다. 사용자는 Selenium에서 스크린샷을 찍거나 페이지 소스를 요청할 수 있으므로 평균 요청-응답 크기는 약 220바이트입니다.

계산기로 돌아가서:

850,000×235×220 = 43,945,000,000바이트(약) 또는 하루에 단 43.945GB

이제 BrowserStack Live와 App Live에 대해 이야기해 보겠습니다. 확실히 우리는 데이터 크기의 형태로 우리의 승자로 Automate를 가지고 있습니다. 그러나 전달된 메시지 수에 관해서는 Live 제품이 선두를 달리고 있습니다. 모든 실시간 테스트에 대해 1분마다 약 20개의 메시지가 전달됩니다. 우리는 약 100,000개의 라이브 테스트를 실행하며 각 테스트의 평균은 약 12분입니다.

100,000×12×20 = 하루 24,000,000개의 메시지

이제 훌륭하고 놀라운 부분을 살펴보겠습니다. 우리는 6개의 t1.micro ec2 인스턴스를 사용하여 pusher라는 애플리케이션을 빌드, 실행 및 유지 관리합니다. 서비스 운영 비용은? 한 달 에 약 $70.

구축 선택 vs. 구매

가장 먼저 해야 할 일: 대부분의 다른 사람들과 마찬가지로 스타트업으로서 우리는 항상 사내에서 무언가를 구축하는 것에 흥분했습니다. 그러나 우리는 여전히 몇 가지 서비스를 평가했습니다. 우리가 가진 기본 요구 사항은 다음과 같습니다.

  1. 신뢰성과 안정성,
  2. 고성능 및
  3. 비용 효율성.

한 달에 70달러 미만의 비용이 드는 외부 서비스가 생각나지 않기 때문에 비용 효율성 기준은 생략하겠습니다. 그래서 거기에 대한 우리의 대답은 분명합니다.

안정성과 안정성 면에서 99.9% 이상의 가동 시간 SLA로 Pub/Sub를 서비스로 제공하는 회사를 찾았지만 T&C가 많이 붙어 있었습니다. 문제는 생각만큼 간단하지 않습니다. 특히 시스템과 클라이언트 사이에 있는 광대한 개방형 인터넷 영역을 고려할 때 그렇습니다. 인터넷 인프라에 익숙한 사람은 안정적인 연결이 가장 큰 문제라는 것을 알고 있습니다. 또한 전송되는 데이터의 양은 트래픽에 따라 다릅니다. 예를 들어, 1분 동안 0인 데이터 파이프는 다음 시간 동안 파열될 수 있습니다. 이러한 폭발적인 순간에 적절한 안정성을 제공하는 서비스는 드뭅니다(Google 및 Amazon).

우리 프로젝트의 성능 은 거의 0에 가까운 대기 시간으로 모든 수신 노드에 데이터를 가져오고 보내는 것을 의미합니다. BrowserStack에서는 코로케이션 호스팅과 함께 클라우드 서비스(AWS)를 활용합니다. 그러나 게시자 및/또는 구독자는 어디에나 배치될 수 있습니다. 예를 들어, 꼭 필요한 로그 데이터를 생성하는 AWS 애플리케이션 서버 또는 터미널(사용자가 테스트를 위해 안전하게 연결할 수 있는 시스템)이 포함될 수 있습니다. 공개 인터넷 문제로 다시 돌아가서 위험을 줄이려면 Pub/Sub가 최고의 호스트 서비스와 AWS를 활용하도록 해야 합니다.

또 다른 필수 요구 사항은 모든 유형의 데이터(바이트, 텍스트, 이상한 미디어 데이터 등)를 전송할 수 있는 능력이었습니다. 모든 것을 고려할 때 당사 제품을 지원하기 위해 타사 솔루션에 의존하는 것은 의미가 없습니다. 결과적으로 우리는 우리 자신의 솔루션을 코딩하기 위해 소매를 걷어붙이고 스타트업 정신을 되살리기로 결정했습니다.

솔루션 구축

Pub/Sub 설계는 데이터를 생성하고 전송하는 게시자와 데이터를 수락하고 처리하는 구독자가 있음을 의미합니다. 이것은 라디오와 유사합니다. 라디오 채널은 범위 내의 모든 곳에서 콘텐츠를 방송(게시)합니다. 구독자는 해당 채널에 맞춰 청취할지(또는 라디오를 완전히 끌지) 결정할 수 있습니다.

데이터가 모두에게 무료이고 누구나 수신을 결정할 수 있는 라디오 비유와 달리, 디지털 시나리오에서는 게시자가 생성한 데이터가 단일 특정 클라이언트 또는 가입자만을 위한 것일 수 있음을 의미하는 인증이 필요합니다.

Pub/Sub 기본 작업
Pub/Sub 기본 작업(큰 미리보기)

위는 다음이 포함된 우수한 Pub/Sub의 예를 제공하는 다이어그램입니다.

  • 발행인
    여기에 미리 정의된 논리를 기반으로 메시지를 생성하는 두 게시자가 있습니다. 라디오 비유에서 이들은 콘텐츠를 만드는 라디오 기수입니다.
  • 주제
    여기에 두 가지가 있습니다. 즉, 두 가지 유형의 데이터가 있습니다. 이것이 우리의 라디오 채널 1과 2라고 말할 수 있습니다.
  • 구독자
    우리는 각각 특정 주제에 대한 데이터를 읽는 세 가지가 있습니다. 한 가지 주목해야 할 점은 구독자 2가 여러 주제를 읽고 있다는 것입니다. 우리의 라디오 비유에서 이들은 라디오 채널에 맞춰진 사람들입니다.

서비스에 필요한 요구 사항을 이해하기 시작하겠습니다.

  1. 이벤트 구성 요소
    이것은 시작해야 할 것이 있을 때만 시작됩니다.
  2. 임시 저장
    이렇게 하면 짧은 기간 동안 데이터가 유지되므로 구독자가 느린 경우에도 데이터를 사용할 수 있는 창이 있습니다.
  3. 대기 시간 줄이기
    최소 홉과 거리로 네트워크를 통해 두 엔티티를 연결합니다.

우리는 위의 요구 사항을 충족하는 기술 스택을 선택했습니다.

  1. 노드.js
    왜 안되지? 많은 양의 데이터 처리가 필요하지 않을 뿐만 아니라 온보딩도 쉽습니다.
  2. 레디스
    완벽하게 단기 데이터를 지원합니다. 시작, 업데이트 및 자동 만료를 위한 모든 기능이 있습니다. 또한 애플리케이션에 부하를 덜 줍니다.

비즈니스 로직 연결을 위한 Node.js

Node.js는 IO 및 이벤트를 통합하는 코드를 작성할 때 거의 완벽한 언어입니다. 우리의 특정한 주어진 문제는 이 두 가지를 모두 가지고 있어서 이 옵션을 우리의 필요에 가장 실용적으로 만들었습니다.

확실히 Java와 같은 다른 언어가 더 최적화되거나 Python과 같은 언어가 확장성을 제공할 수 있습니다. 그러나 이러한 언어로 시작하는 비용이 너무 높아 개발자가 동일한 기간에 Node에서 코드 작성을 완료할 수 있습니다.

솔직히 말해서 서비스에 더 복잡한 기능을 추가할 기회가 있었다면 다른 언어나 완성된 스택을 볼 수 있었을 것입니다. 그러나 여기 하늘에서 이루어진 결혼이 있습니다. 다음은 package.json 입니다.

 { "name": "Pusher", "version": "1.0.0", "dependencies": { "bstack-analytics": "*****", // Hidden for BrowserStack reasons. :) "ioredis": "^2.5.0", "socket.io": "^1.4.4" }, "devDependencies": {}, "scripts": { "start": "node server.js" } }

간단히 말해서, 우리는 특히 코드 작성과 관련하여 미니멀리즘을 믿습니다. 반면에 Express와 같은 라이브러리를 사용하여 이 프로젝트를 위한 확장 가능한 코드를 작성할 수도 있습니다. 그러나 우리의 시작 본능은 이것을 전달하고 다음 프로젝트를 위해 저장하기로 결정했습니다. 우리가 사용한 추가 도구:

  • 이오레디스
    이것은 Alibaba를 포함한 회사에서 사용하는 Node.js와의 Redis 연결을 위해 가장 많이 지원되는 라이브러리 중 하나입니다.
  • 소켓.io
    WebSocket 및 HTTP를 통한 원활한 연결 및 폴백을 위한 최고의 라이브러리입니다.

임시 스토리지용 Redis

Redis as a Service는 매우 안정적이고 구성 가능합니다. 또한 AWS를 포함하여 Redis를 위한 신뢰할 수 있는 관리형 서비스 공급자가 많이 있습니다. 공급자를 사용하고 싶지 않더라도 Redis는 쉽게 시작할 수 있습니다.

구성 가능한 부분을 분해해 보겠습니다. 우리는 일반적인 마스터-슬레이브 구성으로 시작했지만 Redis에는 클러스터 또는 센티넬 모드도 함께 제공됩니다. 모든 모드에는 고유한 장점이 있습니다.

어떤 식으로든 데이터를 공유할 수 있다면 Redis 클러스터가 최선의 선택이 될 것입니다. 그러나 휴리스틱으로 데이터를 공유하면 휴리스틱을 따라야 하므로 유연성이 떨어집니다 . 더 적은 규칙, 더 많은 통제가 평생 동안 좋습니다!

Redis Sentinel은 데이터 조회가 데이터가 샤딩되지 않은 동안 특정 시점에 연결되는 단 하나의 노드에서 수행되기 때문에 가장 잘 작동합니다. 이는 또한 여러 노드가 손실되더라도 데이터가 여전히 분산되어 다른 노드에 존재함을 의미합니다. 따라서 더 많은 HA가 있고 손실 가능성이 적습니다. 물론 이것은 클러스터가 있는 전문가를 제거했지만 우리의 사용 사례는 다릅니다.

30000피트 높이의 건축물

아래 다이어그램은 Automate 및 App Automate 대시보드가 ​​작동하는 방식에 대한 매우 높은 수준의 그림을 제공합니다. 이전 섹션에서 사용했던 실시간 시스템을 기억하십니까?

BrowserStack의 실시간 Automate 및 App Automate 대시보드.
BrowserStack의 실시간 Automate 및 App Automate 대시보드(큰 미리보기)

다이어그램에서 기본 워크플로는 더 두꺼운 테두리로 강조 표시됩니다. "자동화" 섹션은 다음으로 구성됩니다.

  1. 터미널
    BrowserStack에서 테스트하는 동안 얻은 Windows, OSX, Android 또는 iOS의 원시 버전으로 구성됩니다.
  2. 바퀴통
    BrowserStack을 사용한 모든 Selenium 및 Appium 테스트를 위한 접점입니다.

여기의 "사용자 서비스" 섹션은 데이터가 올바른 개인에게 전송되고 저장되도록 하는 게이트키퍼입니다. 그것은 또한 우리의 보안 지킴이입니다. "푸셔" 섹션에는 이 기사에서 논의한 내용의 핵심이 포함되어 있습니다. 다음을 포함한 일반적인 용의자로 구성됩니다.

  1. 레디스
    우리의 경우 자동화 로그가 임시로 저장되는 메시지에 대한 임시 저장소입니다.
  2. 발행자
    이것은 기본적으로 허브에서 데이터를 가져오는 엔터티입니다. 모든 요청 응답은 session_id 를 채널로 사용하여 Redis에 쓰는 이 구성 요소에 의해 캡처됩니다.
  3. 구독자
    이것은 session_id 에 대해 생성된 Redis에서 데이터를 읽습니다. 클라이언트가 WebSocket(또는 HTTP)을 통해 연결하여 데이터를 가져온 다음 인증된 클라이언트에 보내는 웹 서버이기도 합니다.

마지막으로 session_id 로그가 전송되도록 인증된 WebSocket 연결을 나타내는 사용자의 브라우저 섹션이 있습니다. 이를 통해 프런트 엔드 JS는 사용자를 위해 이를 구문 분석하고 아름답게 할 수 있습니다.

로그 서비스와 유사하게 다른 제품 통합에 사용되는 푸셔가 있습니다. session_id 대신 다른 형식의 ID를 사용하여 해당 채널을 나타냅니다. 이것은 모두 푸셔에서 작동합니다!

결론(TLDR)

Pub/Sub를 구축하는 데 상당한 성공을 거두었습니다. 사내 구축 이유를 요약하자면 다음과 같습니다.

  1. 우리의 필요에 맞게 더 잘 확장됩니다.
  2. 아웃소싱 서비스보다 저렴합니다.
  3. 전체 아키텍처에 대한 완전한 제어.

JS가 이러한 종류의 시나리오에 가장 적합하다는 것은 말할 것도 없습니다. 이벤트 루프와 엄청난 양의 IO가 문제에 필요한 것입니다! JavaScript는 단일 의사 스레드의 마술입니다.

하나의 소스에서 데이터를 얻고 Redis를 통해 다른 소스로 푸시할 수 있으므로 이벤트 및 Redis를 시스템으로 사용하면 개발자가 작업을 간단하게 수행할 수 있습니다. 그래서 우리는 그것을 만들었습니다.

사용법이 시스템에 맞는 경우 동일한 작업을 수행하는 것이 좋습니다!