本地使用 Docker Compose 與 Nestjs 快速構建基於 Dapr 的 Redis 釋出/訂閱分散式應用

為少發表於2022-07-08

image

Dapr(分散式應用程式執行時)介紹

Dapr 是一個可移植的、事件驅動的執行時,它使任何開發人員能夠輕鬆構建出彈性的、無狀態和有狀態的應用程式,並可執行在雲平臺或邊緣計算中,它同時也支援多種程式語言和開發框架。

Dapr 官網:https://dapr.io/

實戰 Dapr 的 Redis 釋出/訂閱應用

1. 建立專案

首先,我們將建立我們的專案根資料夾來託管我們將在後續步驟中建立的所有服務。

mkdir dapr-nestjs-redis-pub-sub

2. 建立 Dapr Placement 服務

由於我們將建立多個服務,我們將使用 docker-compose 來執行這些服務。

讓我們在專案的根資料夾中建立 docker-compose.yml 檔案

cd dapr-nestjs-redis-pub-sub
touch docker-compose.yml
version: "3.5"

services:
  dapr-placement:
    image: "daprio/dapr"
    command: ["./placement", "-port", "50006"]

Dapr placement 服務將負責管理 Dapr actors(我們的服務)之間的所有通訊。

簡單來說,它負責將所有通訊路由到假設接收通訊的相應 actor。它充當 message broker(訊息代理)。

3. 建立 Redis Publish 服務

讓我們繼續通過新增我們的 Redis 服務來修改我們的 docker-compose.yml 檔案。

將以下程式碼新增到 docker-compose.yml 的服務部分:

  redis-publisher: 
    image: redis
    restart: always
    depends_on:
      - dapr-placement

4. 建立 Dapr Pub-Sub 元件

建立一個 dapr/components 資料夾。然後建立元件檔案 redis-pubsub.yaml

mkdir -p dapr/components
cd dapr/components
touch redis-pubsub.yaml

然後開啟檔案並插入我們的 Dapr pub/sub 元件的詳細資訊

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: redis-pubsub
  namespace: default
spec:
  type: pubsub.redis
  version: v1
  metadata:
    - name: redisHost
      value: redis-publisher:6379
    - name: redisPassword
      value: ""

redisHost 是我們的 Redis 服務 redis-pub 的名稱,預設 Redis 埠為 6379

5. 建立 Redis Dapr Sidecar

正如前面部分反覆提到的,服務直接與 Dapr 通訊,而不是直接與其他服務通訊。Dapr 充當所有服務的中間人。

服務通過它們自己的 Dapr sidecar 直接與 Dapr 通訊,Dapr sidecar 將通訊傳遞給 Dapr placement,該 placement 再次將其傳遞給假設接收通訊的服務的 Dapr sidecar

redis-dapr-sidecar 服務新增到我們的 docker-compose.yml

  redis-dapr-sidecar:
    image: "daprio/daprd:edge"
    command: [
        "./daprd",
        "-app-id",
        "redis-publisher",
        "-app-port",
        "6379",
        "-dapr-http-port",
        "5000",
        "-components-path",
        "/components",
        "-placement-host-address",
        "dapr-placement:50006"
      ]
    volumes: 
      - "./dapr/components/:/components"
    depends_on:
      - redis-publisher
    network_mode: "service:redis-publisher"

在這裡,我們使用 app-idDapr sidecar 分配給 redis-publisher,同時我們使用 redis6379

我們還必須將 dapr/components(redis-pubsub.yaml) 資料夾掛載到 docker 容器中。

不要忘記宣告 dapr-http-port。這是我們的 Dapr sidecarapi,允許我們呼叫各種 HTTP 方法。

定義您的 dapr-http-port 很重要,因為您將在此處呼叫各種 HTTP 呼叫/方法/請求

最後,注意將 redis-dapr-sidecar 附加到 redis-publisher 網路名稱空間。

6. 建立 NestJS Server

我們將使用 NestJS 作為我們的 node server 作為我們的 Redis subscriber(訂閱者)

進入到專案資料夾

cd dapr-nestjs-redis-pub-sub

然後執行以下命令設定一個 NestJS node server:

npm i -g @nestjs/cli
nest new nest-subscriber

對於這個專案,我們將選擇 yarn 作為包管理器。

接下來,我們將設定一個 post API 端點。
Dapr 將呼叫這個端點,一旦它收到我們的 Redis 服務釋出,它就被呼叫。

轉到 nest-subscriber/src/app.controller.ts

將此檔案中的程式碼替換為以下內容:

import { Controller, Post, Body } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post('/redis-publisher')
  async postRedisPub(@Body() reqBody) {
    console.log(`Redis 釋出了 ${JSON.stringify(reqBody)} `);

    return `NestJS 訂閱者收到的 ${reqBody} 釋出`;
  }
}

7. 為 NestJS 訂閱伺服器建立 Dockerfile

我們將 NestJS 伺服器作為 Docker 容器執行。需要建立一個 Dockerfile

cd nest-subscriber
touch Dockerfile

然後開啟檔案並貼上以下程式碼:

FROM node:16.13.0-alpine

WORKDIR "/app"
COPY ./nest-subscriber/package.json ./
RUN yarn install
COPY ./nest-subscriber .
RUN yarn run build

EXPOSE 3000
CMD ["yarn","start:prod"]

構建映象:

docker build -f ./nest-subscriber/Dockerfile -t nest-subscriber:latest . --no-cache

8. 將 NestJS 訂閱服務新增到 docker-compose 檔案

在建立了我們的 NestJS 伺服器和 Dockerfile 之後,我們建立了 nest-subscriber docker 服務。

將以下內容新增到 docker-compose.yml

  nest-subscriber:
    image: "nest-subscriber:latest"
    depends_on:
      - redis-publisher 
      - dapr-placement 
    restart: always

9. 建立 Dapr 訂閱

我們將為我們的 pub/sub 訂閱定義配置。

建立一個 dapr/subscriptions 資料夾。然後建立元件檔案 redis-subscription.yaml

mkdir -p dapr/subscriptions
cd dapr/subscriptions
touch redis-subscription.yaml

然後開啟檔案並插入我們的 Dapr 訂閱元件的詳細資訊

apiVersion: dapr.io/v1alpha1
kind: Subscription
metadata:
  name: nest-redis-sub
spec:
  topic: nest-redis-pub-topic
  route: /redis-publisher
  pubsubname: redis-pubsub
scopes:
  - nest-subscriber

路由是釋出 topicDapr 將呼叫的 API

scope 是訂閱該 topic 的服務。

pubsubnameredis-pubsub,它等於我們的 redis-pubsub.yaml 檔案中定義的後設資料名稱。

在這個專案中,如果釋出了一個 topic nest-redis-pub-topic,Dapr 將在我們的 nest-subscriber 服務中呼叫 API /redis-publisher

10. 建立 NestJS 伺服器 Dapr Sidecar

我們需要為我們的 NestJS 服務建立一個 sidecar,就像 redis-publisher 服務一樣。

nest-subscriber-dapr-sidecar 服務新增到我們的 docker-compose.yml

  nest-subscriber-dapr-sidecar:
    image: "daprio/daprd:edge"
    command: [
        "./daprd",
        "-app-id",
        "nest-subscriber",
        "-app-port",
        "3000",      
        "-components-path",
        "/components",
        "-placement-host-address",
        "dapr-placement:50006", 
      ]
    volumes:
      - "./dapr/components/:/components" 
    depends_on:
      - nest-subscriber
    network_mode: "service:nest-subscriber"

11. 測試它是否有效

通常 Dapr Docker 容器會在 Docker 網路中進行通訊。

但是為了我們做測試,我們將開啟對映暴露埠 5000 到我們的本地機器 5001

  redis-publisher: 
    image: redis
    depends_on:
      - dapr-placement
    restart: always
    ports:
      - 5001:5000

然後在您的終端中執行以下命令:

curl --location --request POST 'http://localhost:5001/v1.0/publish/redis-pubsub/nest-redis-pub-topic' \
--header 'Content-Type: application/json' \
--data-raw '{
    "hello": "world"
}'

Dapr 的優點之一是它遵循特定的 URL 格式。這裡我們只使用 Dapr sidecar HTTP 埠(5001),然後是版本號(v1.0),然後是 action(publish)。然後是我們 redis-pubsub.yaml 配置檔案中定義的 pubsubnameredis-pubsub)和 topicnest-redis-pub-topic)。

一旦發出 HTTP post 請求。我們的 NestJS 伺服器應該在 /redis-publisher 收到一個 post 請求,這將導致以下日誌:

image

我們可以看到它正在通過 Dapr 接收 Redis 釋出。但是我們的 NestJS 伺服器無法正確處理訊息。

只有 {} 被髮布,而不是我們釋出的訊息。

我們將在下一步中解決這個問題。

注意:我們通過 redis-dapr-sidecardapr-http-port 呼叫釋出服務。通常會有一個單獨的 Docker 服務(例如另一個伺服器),它有自己的 Dapr sidecar,它將呼叫 redis 釋出服務。 在這種情況下,我們將使用該 Docker 服務的 Dapr sidecar http-port。該請求將由 sidecar 傳送到 Dapr placement 服務,然後該服務將確定將請求轉發到的正確 Dapr sidecar

12. 允許 NestJS 解析 application/cloudevents+json

我們的 nest-subscriber-dapr-sidecar 向我們的 nest-subscriber 伺服器發出的 post 請求的 Content-Type 將是 application/cloudevents+json 而不是 application/json

目前我們的 NestJS 伺服器無法解析 application/cloudevents+json

為了解決這個問題,我們首先需要安裝 body-parser

cd nest-subscriber
yarn add body-parser

接下來我們需要修改我們的 NestJS 伺服器 main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as bodyParser from 'body-parser';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(bodyParser.json({ type: 'application/cloudevents+json' }));
  app.use(bodyParser.json());
  await app.listen(3000);
}

bootstrap();

當我們再次傳送 post 請求時,我們的 NestJS 伺服器將能夠處理請求正文並顯示以下日誌:

好了,我們現在有一個基於 Dapr 工作的 Redis Pub/Sub 分散式應用。

13. 完整 docker-compose.yaml

version: "3.5"

services:
  dapr-placement:
    image: "daprio/dapr"
    command: ["./placement", "-port", "50006"]
  
  redis-publisher: 
    image: redis
    depends_on:
      - dapr-placement
    restart: always
    ports:
      - 5001:5000

  redis-dapr-sidecar:
    image: "daprio/daprd:edge"
    command: [
        "./daprd",
        "-app-id",
        "redis-publisher",
        "-app-port",
        "6379",
        "-dapr-http-port",
        "5000",
        "-components-path",
        "/components",
        "-placement-host-address",
        "dapr-placement:50006"
      ]
    volumes: 
      - "./dapr/components/:/components"
    depends_on:
      - redis-publisher
    network_mode: "service:redis-publisher"
  
  nest-subscriber:
    image: "nest-subscriber:latest"
    depends_on:
      - redis-publisher 
      - dapr-placement 
    restart: always
  
  nest-subscriber-dapr-sidecar:
    image: "daprio/daprd:edge"
    command: [
        "./daprd",
        "-app-id",
        "nest-subscriber",
        "-app-port",
        "3000",      
        "-components-path",
        "/components",
        "-placement-host-address",
        "dapr-placement:50006", 
      ]
    volumes:
      - "./dapr/components/:/components" 
    depends_on:
      - nest-subscriber
    network_mode: "service:nest-subscriber"

14. 原始碼

https://github.com/Hacker-Linner/dapr-nestjs-redis-pub-sub.git

更多

使用 Dapr JS SDK 讓 Nest.js 整合 Dapr

相關文章