WebSocket을 통한 Pub/Sub 메시지 스트림


이 가이드에서는 Google Cloud를 사용할 때 프런트엔드 앱(이 경우 웹페이지)이 많은 양의 수신 데이터를 처리하는 방법을 보여줍니다. 이 가이드에서는 대용량 스트림의 몇 가지 문제를 설명합니다. 이 가이드에는 WebSockets를 사용하여 Pub/Sub 주제에 게시된 고밀도 메시지 스트림을 시각화하여 시의적절하게 고성능 프런트엔드를 유지하는 방법으로 처리하는 예시 앱이 제공됩니다.

이 가이드는 HTML, CSS, 자바스크립트를 사용하는 프런트엔드 앱 작성과 HTTP를 통한 브라우저에서 서버 간 통신에 익숙한 개발자를 대상으로 하며, 사용자가 Google Cloud에 대한 경험이 있고 Linux 명령줄 도구에 익숙하다고 가정합니다.

목표

  • Pub/Sub 구독의 페이로드를 브라우저 클라이언트에 스트림하는 데 필요한 구성요소를 이용하여 가상 머신(VM) 인스턴스를 만들고 구성합니다.
  • VM에서 Pub/Sub 주제를 구독하고 개별 메시지를 로그에 출력하는 프로세스를 구성합니다.
  • 웹 서버를 설치하여 정적 콘텐츠를 제공하고, 셸 명령어 결과를 WebSocket 클라이언트에 스트림합니다.
  • HTML, CSS, 자바스크립트를 사용하여 브라우저에서 WebSocket 스트림 집계 및 개별 메시지 샘플을 시각화합니다.

비용

이 문서에서는 비용이 청구될 수 있는 다음과 같은 Google Cloud 구성요소를 사용합니다.

프로젝트 사용량을 기준으로 예상 비용을 산출하려면 가격 계산기를 사용하세요. Google Cloud를 처음 사용하는 사용자는 무료 체험판을 사용할 수 있습니다.

시작하기 전에

  1. Google Cloud 계정에 로그인합니다. Google Cloud를 처음 사용하는 경우 계정을 만들고 Google 제품의 실제 성능을 평가해 보세요. 신규 고객에게는 워크로드를 실행, 테스트, 배포하는 데 사용할 수 있는 $300의 무료 크레딧이 제공됩니다.
  2. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

  3. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다.

  4. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

  5. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다.

  6. Cloud Shell을 열고 이 가이드에 나열된 명령어를 실행합니다.

    Cloud Shell로 이동

    Cloud Shell에서 이 가이드의 모든 터미널 명령어를 실행합니다.

  7. Compute Engine API 및 Pub/Sub API를 사용 설정합니다.
    gcloud services enable compute pubsub

이 가이드를 마치면 만든 리소스를 삭제하여 비용이 계속 청구되지 않도록 할 수 있습니다. 자세한 내용은 삭제를 참조하세요.

소개

이벤트 기반 모델을 도입하는 앱이 많을수록 프런트엔드 앱은 이러한 아키텍처의 토대가 되는 메시지 서비스에 간단하면서도 편리하게 연결할 수 있어야 합니다.

웹브라우저 클라이언트에 데이터를 스트림하기 위한 몇 가지 옵션이 있는데, 이 중 가장 일반적인 것은 WebSocket입니다. 이 가이드에서는 Pub/Sub 주제에 게시되는 메시지 스트림을 구독하는 프로세스를 설치하고 웹 서버를 통해 이러한 메시지를 WebSockets에 연결된 클라이언트로 라우팅하는 방법을 설명합니다.

이 가이드에서는 NYC Taxi Tycoon Google Dataflow CodeLab에서 사용되는 공개적으로 사용 가능한 Pub/Sub 주제를 다룹니다. 이 주제에서는 택시 및 리무진 위원회의 운행 기록 데이터 세트에서 뉴욕시의 과거 탑승 데이터를 기반으로 시뮬레이션된 택시 원격 분석을 실시간으로 제공합니다.

아키텍처

다음 다이어그램은 이 가이드에서 빌드한 가이드의 아키텍처를 보여줍니다.

가이드의 아키텍처

이 다이어그램은 Compute Engine 리소스가 포함된 프로젝트 외부의 메시지 게시자를 보여주며, 게시자는 Pub/Sub 주제에 메시지를 보냅니다. Compute Engine 인스턴스는 WebSockets를 통해 HTML5 및 자바스크립트를 기반으로 대시보드를 실행하는 브라우저에 메시지를 제공합니다.

이 가이드에서는 도구 조합을 사용하여 Pub/Sub 및 Websocket을 연결합니다.

  • pulltop은 이 가이드의 일환으로 설치하는 Node.js 프로그램입니다. 이 도구는 Pub/Sub 주제를 구독하고 수신된 메시지를 표준 출력에 스트림합니다.
  • websocketd는 기존 명령줄 인터페이스 프로그램을 래핑하고 WebSocket을 사용하여 액세스할 수 있도록 하는 작은 명령줄 도구입니다.

pulltopwebsocketd를 결합하면 Pub/Sub 주제에서 수신된 메시지를 WebSockets를 사용하여 브라우저에 스트림할 수 있습니다.

Pub/Sub 주제 처리량 조정

NYC Taxi Tycoon의 공개 Pub/Sub 주제는 초당 2,000~2,500개의 시뮬레이션된 택시 이동 업데이트를 생성합니다(초당 최대 8MB 또는 그 이상). Pub/Sub에서 확인되지 않은 메시지의 큐가 증가함을 감지하는 경우 Pub/Sub의 기본 제공 흐름 제어 기능이 자동으로 구독자의 메시지 속도를 늦춥니다. 따라서 다양한 워크스테이션, 네트워크 연결, 프런트엔드 처리 코드에서 높은 메시지 속도 변동이 발생할 수 있습니다.

효과적인 브라우저 메시지 처리

WebSocket 스트림을 통해 전송되는 메시지의 양이 많으므로, 이 스트림을 처리하는 프런트엔드 코드를 작성할 때 주의해야 합니다. 예를 들어 각 메시지에 대해 HTML 요소를 동적으로 만들 수 있습니다. 하지만 예상 메시지 속도로 각 메시지의 페이지를 업데이트하면 브라우저 창이 잠길 수 있습니다. HTML 요소를 동적으로 생성하여 자주 발생하는 메모리 할당도 가비지 컬렉션 기간을 연장하여 사용자 환경을 저하시킵니다. 요약하자면 초당 도착하는 약 2,000개의 메시지마다 document.createElement()를 호출하는 것은 권장하지 않는다는 의미입니다.

메시지에서 이러한 고밀도 스트림을 관리하는 방법은 다음과 같습니다.

  • 스트림 측정항목 집합을 실시간으로 계산하고 지속적으로 업데이트하여 관찰된 메시지에 대한 대부분의 정보를 집계 값으로 표시합니다.
  • 브라우저 기반 대시보드를 사용하여 사전 정의된 일정에 따라 개별 메시지의 작은 샘플을 시각화하여 실시간으로 하차 및 승차 이벤트를 표시합니다.

다음 그림은 이 가이드의 일부로 생성된 대시보드를 보여줍니다.

이 가이드의 코드를 사용하여 웹페이지에 생성된 대시보드

이 그림은 초당 약 2,100개의 메시지 속도로 24밀리초의 마지막 메시지 지연 시간을 보여 줍니다. 각 개별 메시지를 처리하기 위한 중요한 코드 경로가 시간 내에 완료되지 않으면 마지막 메시지 지연 시간이 증가함에 따라 초당 관찰된 메시지 수가 줄어듭니다. 탑승 샘플링은 3초마다 한 번씩 자바스크립트 setInterval API를 사용하여 설정되므로 프런트엔드가 전체 기간 동안 수많은 DOM 요소를 생성하지 못합니다. (대부분의 경우 실질적으로 10초 이상의 속도로 관찰할 수 없습니다.)

대시보드는 스트림 중간에 이벤트를 처리하기 때문에 이미 진행 중인 탑승은 이미 관찰된 경우가 아니라면 대시보드에서 새로운 것으로 인식됩니다. 이 코드는 연관 배열을 사용하여 관찰된 각 탑승을 저장하고, ride_id 값으로 색인을 생성하고, 승객이 하차했을 때 특정 탑승에 대한 참조를 삭제합니다. '운행 중' 또는 '승차' 상태의 차량은 이전에 탑승이 관찰되지 않는 한 해당 배열에 대한 참조를 추가합니다.

WebSocket 서버 설치 및 구성

시작하려면 WebSocket 서버로 사용할 Compute Engine 인스턴스를 만듭니다. 인스턴스를 만든 후 나중에 필요한 도구를 설치합니다.

  1. Cloud Shell에서 기본 Compute Engine 영역을 설정합니다. 다음 예시에서는 us-central1-a를 사용하고 있으나 원하는 영역을 자유롭게 사용할 수 있습니다.

    gcloud config set compute/zone us-central1-a
    
  2. 기본 영역에 websocket-server라는 Compute Engine 인스턴스를 만듭니다.

    gcloud compute instances create websocket-server --tags wss
    
  3. 포트 8000의 TCP 트래픽을 wss 태그가 지정된 인스턴스에 허용하는 방화벽 규칙을 추가합니다.

    gcloud compute firewall-rules create websocket \
        --direction=IN \
        --allow=tcp:8000 \
        --target-tags=wss
    
  4. 기존 프로젝트를 사용하는 경우 인스턴스에 대한 SSH 연결을 허용하도록 TCP 포트 22가 열려 있는지 확인합니다.

    기본 네트워크에서 default-allow-ssh 방화벽 규칙이 기본적으로 사용 설정되어 있습니다. 하지만 사용자 또는 관리자가 기존 프로젝트에서 기본 규칙을 삭제한 경우 TCP 포트 22가 열려 있지 않을 수 있습니다. 이 튜토리얼용으로 새 프로젝트를 만든 경우 규칙이 기본적으로 사용 설정되며 사용자는 아무것도 할 필요가 없습니다.

    포트 22의 TCP 트래픽을 wss 태그가 지정된 인스턴스에 허용하는 방화벽 규칙을 추가합니다.

    gcloud compute firewall-rules create wss-ssh \
        --direction=IN \
        --allow=tcp:22 \
        --target-tags=wss
    
  5. SSH를 사용하여 인스턴스에 연결합니다.

    gcloud compute ssh websocket-server
    
  6. 인스턴스의 터미널 명령어에서 소프트웨어를 설치할 수 있도록 계정을 root로 전환합니다.

    sudo -s
    
  7. gitunzip 도구를 설치합니다.

    apt-get install -y unzip git
    
  8. 인스턴스에 websocketd 바이너리를 설치합니다.

    cd /var/tmp/
    wget \
    https://github.com/joewalnes/websocketd/releases/download/v0.3.0/websocketd-0.3.0-linux_386.zip
    unzip websocketd-0.3.0-linux_386.zip
    mv websocketd /usr/bin
    

Node.js 및 가이드 코드 설치

  1. 인스턴스의 터미널에 Node.js를 설치합니다.

    curl -sL https://deb.nodesource.com/setup_10.x | bash -
    apt-get install -y nodejs
    
  2. 가이드 소스 저장소를 다운로드합니다.

    exit
    cd ~
    git clone https://github.com/GoogleCloudPlatform/solutions-pubsub-websockets.git
    
  3. pulltop의 권한을 변경하여 실행을 허용합니다.

    cd solutions-pubsub-websockets
    chmod 755 pulltop/pulltop.js
    
  4. pulltop 종속 항목을 설치합니다.

    cd pulltop
    npm install
    sudo npm link
    

pulltop이 메시지를 읽을 수 있는지 테스트

  1. 인스턴스에서 공개 주제에 대해 pulltop을 실행합니다.

    pulltop projects/pubsub-public-data/topics/taxirides-realtime
    

    pulltop이 작동하면 다음과 같은 결과 스트림이 표시됩니다.

    {"ride_id":"9729a68d-fcde-484b-bc32-bf29f5188628","point_idx":328,"latitude"
    :40.757360000000006,"longitude":-73.98228,"timestamp":"2019-03-22T20:03:51.6
    593-04:00","meter_reading":11.069151,"meter_increment":0.033747412,"ride_stat
    us":"enroute","passenger_count":1}
  2. 스트림을 중지하려면 Ctrl+C를 누릅니다.

websocketd로 메시지 흐름 설정

pulltop이 Pub/Sub 주제를 읽을 수 있도록 설정했으므로, 이제 websocketd 프로세스를 시작하여 브라우저에 메시지를 보낼 수 있습니다.

주제 메시지를 로컬 파일로 캡처

이 가이드에서는 pulltop에서 수신한 메시지 스트림을 캡처하여 로컬 파일에 작성합니다. 로컬 파일에 메시지 트래픽을 캡처하면 스토리지 요구사항이 추가되지만, 스트리밍 Pub/Sub 주제 메시지에서 websocketd 프로세스의 작업이 분리됩니다. 정보를 로컬에서 캡처하면 Pub/Sub 스트리밍을 일시적으로 중지할 수 있지만(흐름 제어 매개변수를 조정하기 위함일 수 있음) 현재 연결된 WebSocket 클라이언트를 강제로 재설정할 수는 없습니다. 메시지 스트림이 다시 설정되면 websocketd가 클라이언트에 메시지 스트리밍을 자동으로 재개합니다.

  1. 인스턴스에서 공개 주제에 대해 pulltop을 실행하고 메시지 출력을 로컬 taxi.json 파일로 리디렉션합니다. 로그아웃하거나 터미널을 닫을 때 nohup 명령어는 OS에서 pulltop 프로세스를 계속 실행하도록 지시합니다.

    nohup pulltop \
      projects/pubsub-public-data/topics/taxirides-realtime > \
      /var/tmp/taxi.json &
    
  2. JSON 메시지가 파일에 기록되고 있는지 확인합니다.

    tail /var/tmp/taxi.json
    

    메시지가 taxi.json 파일에 기록되는 경우 출력은 다음과 비슷합니다.

    {"ride_id":"9729a68d-fcde-484b-bc32-bf29f5188628","point_idx":328,"latitude"
    :40.757360000000006,"longitude":-73.98228,"timestamp":"2019-03-22T20:03:51.6
    593-04:00","meter_reading":11.069151,"meter_increment":0.033747412,"ride_sta
    tus":"enroute","passenger_count":1}
  3. 앱의 웹 폴더로 변경합니다.

    cd ../web
    
  4. websocketd를 시작하여 WebSockets를 통해 로컬 파일의 콘텐츠 스트리밍을 시작합니다.

    nohup websocketd --port=8000 --staticdir=. tail -f /var/tmp/taxi.json &
    

    그러면 백그라운드에서 websocketd 명령어가 실행됩니다. websocketd 도구는 tail 명령어의 출력을 사용하고 각 요소를 WebSocket 메시지로 스트림합니다.

  5. nohup.out의 콘텐츠를 확인하여 서버가 올바르게 시작되었는지 확인합니다.

    tail nohup.out
    

    모두 올바르게 작동하는 경우 출력은 다음과 비슷합니다.

    Mon, 25 Mar 2019 14:03:53 -0400 | INFO   | server     |  | Serving using application   : /usr/bin/tail -f /var/tmp/taxi.json
    Mon, 25 Mar 2019 14:03:53 -0400 | INFO   | server     |  | Serving static content from : .
    

메시지 시각화

Pub/Sub 주제에 게시된 개별 탑승 메시지의 구조는 다음과 같습니다.

{
  "ride_id": "562127d7-acc4-4af9-8fdd-4eedd92b6e69",
  "point_idx": 248,
  "latitude": 40.74644000000001,
  "longitude": -73.97144,
  "timestamp": "2019-03-24T00:46:08.49094-04:00",
  "meter_reading": 8.40615,
  "meter_increment": 0.033895764,
  "ride_status": "enroute",
  "passenger_count": 1
}

이 값을 기반으로 대시보드의 헤더에 대한 여러 측정항목을 계산합니다. 계산은 인바운드 탑승 이벤트당 한 번 실행됩니다. 값에는 다음이 포함됩니다.

  • 마지막 메시지 지연 시간. 마지막으로 관찰된 탑승 이벤트 타임스탬프의 타임스탬프와 현재 시간(웹브라우저를 호스팅하는 시스템의 시계에서 추출) 사이의 시간(초)입니다.
  • 활성 탑승. 현재 진행 중인 탑승 횟수입니다. 이 숫자는 빠르게 증가할 수 있으며 dropoffride_status 값이 관찰되면 숫자가 감소합니다.
  • 메시지 속도. 초당 처리되는 평균 탑승 이벤트 수입니다.
  • 총 측정 금액. 모든 활성 탑승의 측정 합계입니다. 이 수치는 탑승이 중단될 때 감소합니다.
  • 총 승객 수. 모든 탑승의 승객 수입니다. 이 횟수는 탑승이 완료되면 감소합니다.
  • 차량당 평균 승객 수. 총 탑승 횟수를 총 승객 수로 나눈 값입니다.
  • 승객당 평균 측정 금액. 측정된 총 금액을 총 승객 수로 나눈 값입니다.

측정항목과 개별 탑승 샘플 외에도 승객이 승차하거나 하차하면 대시보드에 탑승 샘플의 그리드 위에 알림이 표시됩니다.

  1. 현재 인스턴스의 외부 IP 주소를 가져옵니다.

    curl -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip; echo
    
    
  2. IP 주소를 복사합니다.

  3. 로컬 머신에서 새 웹브라우저를 열고 URL을 입력합니다.

    http://$ip-address:8000.

    이 가이드의 대시보드를 보여 주는 페이지가 표시됩니다.

    이 가이드의 코드로 생성된 대시보드이며 데이터가 표시되기 전에 환영 메시지와 함께 시작됩니다.

  4. 상단의 택시 아이콘을 클릭하여 스트림 연결을 열고 메시지 처리를 시작합니다.

    개별 차량은 3초마다 렌더링되는 9개의 활성 탑승 샘플로 시각화됩니다.

    활성 탑승을 보여주는 대시보드

    언제든지 택시 아이콘을 클릭하여 WebSocket 스트림을 시작하거나 중지할 수 있습니다. WebSocket 연결이 끊어지면 아이콘이 빨간색으로 변하고 측정항목 및 개별 탑승에 대한 업데이트가 중단됩니다. 다시 연결하려면 택시 아이콘을 다시 클릭합니다.

성능

다음 스크린샷은 브라우저 탭이 초당 약 2,100개의 메시지를 처리하는 동안 Chrome 개발자 도구 성능 모니터를 보여줍니다.

CPU 사용량, 힙 크기, DOM 노드, 초당 스타일 재계산을 보여주는 브라우저 성능 모니터 창 값은 비교적 평면적입니다.

약 30ms의 지연 시간으로 메시지 전달이 발생하므로 CPU 사용률은 약 80 %입니다. 메모리 사용량은 최소 29MB로 표시되며 총 57MB가 할당되고 자유롭게 증가 및 축소됩니다.

삭제

방화벽 규칙 삭제

이 가이드의 기존 프로젝트를 사용한 경우 생성한 방화벽 규칙을 삭제할 수 있습니다. 열린 포트를 최소화하는 것이 좋습니다.

  1. 포트 8000에서 TCP를 허용하도록 만든 방화벽 규칙을 삭제합니다.

    gcloud compute firewall-rules delete websocket
    
  2. SSH 연결을 허용하는 방화벽 규칙도 만든 경우 포트 22에서 TCP를 허용하는 방화벽 규칙을 삭제합니다.

    gcloud compute firewall-rules delete wss-ssh
    

프로젝트 삭제

이 프로젝트를 다시 사용하지 않으려면 프로젝트를 삭제할 수 있습니다.

  1. Google Cloud 콘솔에서 리소스 관리 페이지로 이동합니다.

    리소스 관리로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 후 종료를 클릭하여 프로젝트를 삭제합니다.

다음 단계