hydra-microservice 中文手冊(3W字預警)

為少發表於2020-12-15

Hydras 是什麼?

Hydra 是一個 NodeJS 包(技術棧不是重點,思想!思想!思想!),它有助於構建分散式應用程式,比如微服務。

Hydra 提供服務發現(service discovery)、分散式訊息傳遞(distributed messaging)、
訊息負載平衡(message load balancing)、日誌記錄(logging)、呈現(presence)和執行狀況(health)監視等功能。
Hydra 利用 Redis 的強大力量做到了這一點。

例如,使用 HydrasendMessagemakeAPIRequest 呼叫,
一個應用程式可以按名稱與其他應用程式通訊,而無需指定 IP 或埠資訊。

在執行命名服務的多個例項的常見用例中,
Hydra 將根據可用例項的存在資訊對其請求進行負載平衡。
如果目標例項沒有響應,Hydra 甚至會重新路由請求。

Hydra 專案為 ExpressJS 開發人員提供了 hydra-express 模組
Hydra-express API 路由可以自動註冊並提供給 Hydra 專案的 hydra-router service
它是一種 service 感知的 API 和訊息傳遞路由器。
Hydra-router 甚至將接受 WebSocket 訊息並將其路由到其相應的服務。

為什麼要用 Hydra?

Hydra 提供的大部分功能都可以通過組合使用其他庫、框架和基礎設施工具來實現。
那麼,為什麼要用 Hydra 呢?

我們建立 Hydra 的目標是建立一個單一的、輕量級的 NPM 包,它為微服務提供解決方案,
而不會迫使開發人員以不同的方式來構建微服務。
在很大程度上,我們想讓熟悉 ExpressJS 的開發者也能使用 Hydra
另一個目標是,我們相信開發人員不必是 DevOps 專業人員才能開始使用微服務。
簡而言之,Hydra 降低了構建和部署微服務的門檻。

Hydra 與 Redis

Hydra 專案的主要目標是在不犧牲健壯性和可伸縮性的情況下,
降低構建和部署可伸縮 Node 微服務的障礙。
作為輕量級的庫,hydra-core 已在從小型 IoT 裝置到 Dockerized 雲基礎架構的各種情況下使用。

Hydra 利用 Redis 的強大功能來解決以下微服務問題:

  • 存在(presence)和健康(health)監測
  • 服務發現(service discovery
  • 路由發現(route discovery
  • 服務間訊息傳遞(inter-service messaging

讓我們檢查每一個關注點。

存在(presence)和健康(health)監測

微服務需要傳達其存在狀態,以便可以對其進行監視並通過網路與之可靠地進行通訊。
將訊息傳送到不正常的服務可能會導致級聯的結果,具體取決於應用程式體系結構的健壯性。

應用程式需要在呼叫它們之前瞭解對等微服務的狀態。
這樣路由軟體可以避免不健康的服務,並在問題致命之前將其報告出來。

使用 Hydra 的微服務將其狀態和執行狀況儲存在 Redis 中,
該資訊可供對等服務和監視代理使用。

服務發現(service discovery

分散式應用程式通過相互傳送訊息來協同工作。
大多數情況下,這些訊息採用 HTTP Restful API 的形式。
另一種常見的方法是基於套接字(socket-based)的訊息傳遞。

為了彼此合作,應用程式需要知道對等服務的位置。
也就是說,他們需要知道目標 IP 和埠地址。
使用 DNS 條目或反向代理伺服器(例如 Nginx )可以部分解決此問題。
但是,這種方法的缺點是這些解決方案需要管理。
意思是,沒有自動或動態發現發生。

由 Hydra 提供支援的服務可以使用 Redis 註冊其服務名稱(service name),IP地址和埠。
結合註冊和服務存在資訊,可以使對等服務彼此發現。

路由發現(route discovery

使用 Hydra 的應用程式可以註冊其 Restful API 路由,以便對等應用程式可以發現它們。
Hydra-Router 是一種動態且可感知服務的路由器,
它使用儲存的路由資訊將外部請求定向到雲或叢集環境中的服務。

服務間訊息傳遞(inter-service messaging

Redis 提供訊息傳遞服務,以允許應用程式釋出和訂閱訊息。
這種通訊是基於套接字的(socket-based),並且效能很高。
Hydra 在 Redis 訊息傳遞之上新增了一層,
以通過傳送包含路由資訊(例如命名服務 named services)的JSON訊息,使應用程式彼此通訊。
您無需考慮IP地址或埠,也無需考慮哪個服務例項將收到訊息。

Redis 的靈活性

Redis 是理想的,其原因有很多,Redis 在開發人員中的受歡迎程度持續上升。
線上民意調查中,它的排名也很高。

Redis 可能已經在您的環境中,因為它通常是智慧快取的首選解決方案。

在 Redis 之上構建 Hydra 的一個關鍵原因是因為 Redis 可在 IoT 裝置,
膝上型電腦,雲基礎架構上使用,並受到 RedisLabs 等託管環境的良好支援。
這確實使開發人員能夠將 Node 微服務構建和部署到任何這些環境。

Hydra Express-快速教程

Hydra 是一個 NPM 模組,用於快速構建 Node-based 的微服務。
Hydra-express 是包裝 Hydra 和 ExpressJS 的模組。
在本指南中,我們將著眼於建立一個 hydra-express 應用程式,並瞭解它可以做什麼。

第 1 步-獲取 Redis

Hydra 需要使用 Redis 伺服器。
如果您從未使用過 Redis,我們認為這將是一次改變生活的開發人員經驗,
希望這是最終嘗試它的一個很好的理由!

如果您已經在使用 Redis,那麼恭喜您已經升級了,請隨時跳至第2步!

有很多方法可以訪問 Redis 伺服器。最簡單的方法之一是通過 RedisLabs 等提供商註冊免費套餐。

如果你喜歡 Docker,你可以在幾分鐘內安裝官方的 Redis 映象。對於 PC 使用者來說,這也是一個不錯的選擇。

在 Mac 上,您可以使用一個簡單的命令通過 Homebrew 安裝Redis:brew install redis

如果您不介意從原始碼構建 Redis,請檢視《Redis快速入門指南》

但最簡單的方法是第一種選擇,它不需要安裝——只需登入免費的雲服務提供商。

這裡強烈建議使用 Docker

第 2 步-Hyda CLI 工具

有了 Redis 的訪問許可權,您現在應該安裝 hydra 工具:

確保您使用的是 NodeJS 6.2.1 或更高版本-Hydra 是使用 ES6 構建的!

sudo npm install -g yo generator-fwsp-hydra hydra-cli

這樣就安裝了方便的 Yeoman 和 hydra 生成器以及命令列客戶端。

讓我們配置Hydra命令列客戶端。

$ hydra-cli config
redisUrl: 127.0.0.1
redisPort: 6379
redisDb: 15

上面的例子假設你已經在本地安裝了 redis
如果沒有,只需提供雲服務提供的 redisUrlredisDb 即可。

現在我們都準備好了。 讓我們構建一個微服務!

第 3 步-構建和測試微服務

讓我們構建一個名為 hello的服務。我們將使用方便的 Hydra生成器,大多數情況下選擇預設設定。

$ yo fwsp-hydra
? Name of the service (`-service` will be appended automatically) hello
? Host the service runs on? 
? Port the service runs on? 0
? What does this service do? Says hello
? Does this service need auth? No
? Is this a hydra-express service? Yes
? Set up a view engine? No
? Enable CORS on serverResponses? No
? Run npm install? No
   create hello-service/.editorconfig
   create hello-service/.eslintrc
   create hello-service/.gitattributes
   create hello-service/.nvmrc
   create hello-service/.jscsrc
   create hello-service/specs/test.js
   create hello-service/specs/helpers/chai.js
   create hello-service/.gitignore
   create hello-service/package.json
   create hello-service/README.md
   create hello-service/hello-service.js
   create hello-service/config/sample-config.json
   create hello-service/config/config.json
   create hello-service/routes/hello-v1-routes.js

Done!
'cd hello-service' then 'npm install' and 'npm start'

這是建立的:

.
├── README.md
├── config
│   ├── config.json
│   └── sample-config.json
├── hello-service.js
├── node_modules
├── package.json
├── routes
│   └── hello-v1-routes.js
└── specs
    ├── helpers
    └── test.js

編輯 routes/hello-v1-routes.js,使事情變得更有趣。

將第18行更改為:

result: {}

到:

result: {
  msg: `${hydra.getServiceName()} - ${hydra.getInstanceID()}`
}

按照上述說明,我們可以繼續構建我們的服務。

$ cd hello-service
$ npm install
$ npm start

啟動服務後,我們看到它使用隨機埠啟動。

serviceInfo { serviceName: 'hello-service',
  serviceIP: '10.1.1.163',
  servicePort: 8891 }

您可以通過 curl 訪問該服務:

$ curl 10.1.1.163:8891/v1/hello
{"statusCode":200,"statusMessage":"OK","statusDescription":"Request succeeded without error","result":{"msg":"hello-service - 50bf4346dd492c2036cfd57ad8bd2844"}}

或通過瀏覽器:http://10.1.1.163:8891/v1/hello

我們還可以使用已安裝的 hydrai-cli app 獲取有關我們服務的資訊:

$ hydra-cli nodes
[
  {
    "serviceName": "hello-service",
    "serviceDescription": "Says hello",
    "version": "0.0.1",
    "instanceID": "b1554f404acc3268c1511dc84ae43c50",
    "updatedOn": "2016-11-15T18:18:56.438Z",
    "processID": 20542,
    "ip": "10.1.1.163",
    "port": 8891,
    "elapsed": 4
  }
]
{
  "hello-service": [
    "[GET]/_config/hello-service",
    "[get]/v1/hello/"
  ]
}

此資訊由我們的服務發出,它使服務可以彼此發現並相互傳送訊息。
Hydra-Router 結合使用,您可以構建整個微服務網路。

要了解如何使用新的微服務,請參見 Hydra 方法。

Core

Hydra(core) 是為 Hydra 專案提供動力的 NPM 包。
如果您正在使用 ExpressJS 構建您的服務,
您應該檢檢視 Hydra-Express package 包,
它是專門為利用 ExpressJS 的底層功能而設計的。

本節介紹了核心 Hydra 模組,該模組旨在使微服務的構建和/或使非服務(non-service)應用程式能夠發現和利用微服務。
因此,Hydra 在構建分散式應用程式時可幫助解決各種問題。

雖然 Hydra 是為 NodeJS 實現的,但它支援的功能也可以在其他平臺上實現。

核心服務依賴於共享的 Redis 例項或叢集,比如 Amazon 的 ElasticCache

作為一個 Node module,Hydra 提供了嵌入式(drop-in)功能,旨在解決以下微服務問題:

  • 服務註冊(Service Registration:允許服務在上線時註冊自己併發布它們的 HTTP API 路由。
  • API 可路由性(API Routability:允許將API呼叫路由到微服務。
  • 訊息傳遞通訊(Messaging Communication:通過釋出和訂閱通道以及訊息佇列進行的服務間通訊。
  • 服務負載平衡(Service Load Balancing:基於可用的(現有的)微服務例項自動平衡請求。
  • 服務發現(Service Discovery:在不需要硬編碼其IP地址和埠資訊的情況下定位服務。
  • 執行狀況報告(Health Reporting:自動執行狀況檢查報告,用於回答以下問題:應用程式是否健康?它運作正常嗎?
  • 存在狀態報告(Presence Reporting:服務例項實際可用嗎?

在本文件中,我們將引用服務(services)和服務例項(service instances)。
服務例項和服務節點指的是同一件事。
服務只是賦予一個或多個服務例項的名稱,將其視為服務的一類。
例如,我們可能有一個服務來處理影像大小調整,而我們可以簡單地呼叫該服務 image-resizer
在我們的雲基礎架構中,為了響應高需求,我們可能會執行三個 image-resizer 服務例項。
每個例項都是服務例項或節點。

在 Hydra 中,服務例項僅僅是使用 Hydra 處理微服務問題的過程。

安裝和初始化 Hydra

要從另一個專案中使用 Hydra

$ npm install hydra

匯入 Hydra

const hydra = require('hydra');

初始化

匯入時,會載入 Hydra 模組,但必須先將其初始化才能使用。

hydra.init(initObject);

初始化物件包含以下欄位:

{
  serviceName: 'hydramcp',
  serviceDescription: 'Hydra Master Control Program',
  serviceIP: '',
  servicePort: 0,
  serviceType: 'mcp',
  redis: {
    host: '127.0.0.1',
    port: 6379,
    db: 0
  }
}

所有顯示的欄位都是必需的。
但是,如果您的應用程式不打算作為服務執行,那麼下面的值可以為空並將被忽略。
如果您不打算使用這些值,那麼最好將它們空白。
但是,此時 serviceName 不能為空。

serviceDescription: '',
serviceDNS: '',
serviceIP: '',
servicePort: 0,
serviceType: '',

重要:

  • Hydra 在一個服務中使用時,如果 serviceIP 等於一個空字串(''),那麼將使用該機器的本地IP,否則需要一個四段IP地址(52.9.201.160)。如果 servicePort 等於 0,那麼 Hydra 將選擇一個隨機埠。在需要微服務使用特定埠地址的情況下設定 servicePort
  • hydra.serviceDNS 條目允許您指定服務 DNS 而不是 IP 地址。
    這使您可以將服務放置在外部負載平衡器(例如 NginxDocker Swarm 的內部 DNS)之後。
    存在值時,serviceDNS 條目將忽略 serviceIP 欄位-即使它包含一個值。
  • 對於叢集中的所有網路服務,必須將 hydra.redis.dbvalue 設定為相同的值。
    不這樣做會影響服務的可發現性和監視。在 Hydra 中未對 redis 資料庫值進行硬編碼的原因是,
    不能保證 Redis 例項上存在的資料庫數量在提供商之間是相同的。
    因此,最終服務實現者(您?)需要設定此值的靈活性,從而承擔責任。

在一個實際的生產系統中 Hydra JSON 可能被嵌入到一個更大的配置檔案中,比如 properties.js 檔案:

exports.value = {
  appServiceName: 'hydramcp',
  cluster: false,
  environment: 'development',
  maxSockets: 500,
  logPath: '',
  hydra: {
    serviceName: 'hydramcp',
    serviceDescription: 'Hydra Master Control Program',
    serviceVersion: '1.0.0',
    serviceIP: '',
    serviceDNS: '',
    servicePort: 0,
    serviceType: 'mcp',
    serviceWorker: false,
    redis: {
      host: '127.0.0.1',
      port: 6379,
      db: 0
    }
  }
};

當使用這種方法時,只需在初始化過程中傳遞 hydra 分支:

hydra.init(config.hydra);

如果要在要初始化檔案的同一檔案中使用 hydra
則可以先等待 hydra.init() 返回的 promise,然後再使用其他 hydra 方法。

// index.js
hydra.init({...})
  .then(...);

但是,如果從單獨的檔案匯入 hydra 例項,則需要呼叫 hydra.ready() 方法。

hydra.ready() 返回與 hydra.init() 完全相同的 promise,儘管這樣做無需重新初始化 hydra 例項。

// my-hydra.js
import hydra from 'hydra';

hydra.init({...});
export default hydra;
// service.js
import hydra from './my-hydra.js';

hydra.ready().then(...);

呼叫 hydra.init() 之後,可以隨時使用 hydra.ready() 來等待初始化完成。

Redis 配置

除了 hostportdb,你可以通過 node redis client createClient 方法支援的任何選項。

retry_strategy 是一個例外,它在 redis.createClient 中帶有一個函式引數。
Hydra 提供了 retry_strategy(hydra._redisRetryStrategy),它是通過hydra.redis.retry_strategy 選項配置的,而不是直接傳遞給 redis.createClient

redis: {
  host: "127.0.0.1",
  port: 6379,
  db: 15,
  retry_strategy: {
    maxReconnectionPeriod: 15,
    maxDelayBetweenReconnections: 5
  }
}

您還可以選擇使用 url 引數代替 hostportdbpassword
有關詳細資訊,請參見 IANA registration。以下等效於上面的 host/port/db

redis: {
  url: 'redis://127.0.0.1:6379/15'
}

注意:如果你傳入一些 hostportdbpassword 的組合,url 中的值會被更具體的條目覆蓋:

redis: {
  url: 'redis://127.0.0.1:6379/15',
  db: 10
}

這將連線到資料庫 10,而不是資料庫 15

通過 unix socket 連線

{
   "redis": {
      "path": "/tmp/redis.sock"
   }
}

Hydra 模式

Hydra 可配置為兩種使用模式之一:

  • 服務模式(Service mode)—— 充當服務和其他服務的消費者。
  • 消費者模式(Consumer mode)—— 只能充當服務消費者,而不能成為服務。

服務模式(Service mode

要在 Service mode 下使用 Hydra,您必須先使用以下方式註冊:

hydra.registerService();

注意:如果您的應用程式不需要成為服務,那麼您就不需要執行此註冊。

註冊服務後,hydra 會在生成日誌事件或訊息到達時發出 NodeJS 事件。您可以按以下方式監聽這些事件:

hydra.registerService();
hydra.on('log', function(entry) {});
hydra.on('message', function(message) {});

消費者模式(Consumer mode

如果消費者模式應用程式呼叫與服務模式相關的方法,
將導致異常(exception)或 promise 失敗。
每個呼叫都在本文件的最後被清楚地記錄下來,以幫助避免誤用。
但是始終要確保您的應用程式經過了充分的測試。

Service Discovery(服務發現)

服務(Service)和消費者(Consumer)模式應用程式都可以發現其他服務。
但是請記住,消費者模式應用程式本身無法被發現。只能發現註冊的服務。

使用 findService() 方法發現服務(Services)。
findService() 方法接受服務名稱,並返回一個 promise
promiseresolve 為服務資訊物件;
如果找不到該服務,則返回一個被拒絕的 promise

hydra.findService('imageprocessor')
  .then((service) => {
    console.log(service);
  })
  .catch((err) => {
    console.log('catch err', err);
  });

返回的服務物件可能如下所示:

{
  "serviceName": "imageprocessor",
  "processID": 25246,
  "registeredOn": "2016-03-26T18:26:31.334Z",
  "ip": "10.0.0.4",
  "port": 9001,
  "type": "image:processing"
}

然後,應用程式可以使用 ipport 資訊來呼叫 imageprocessor 服務上的 API

Presence(存活狀態)

僅僅因為可以找到服務並不意味著它當前已線上且處於活動狀態。
在不幸的情況下,所討論的服務可能會失敗和/或暫時不可用。

Hydra 提供了 getServicePresence() 方法來確定服務當前是否可用。
如果可用,則返回這樣的物件:

{
  "updatedOn": "2016-03-28T01:43:45.756Z"
}

如果不可用,則 getservicepresence() 返回一個被拒絕的 promise

健康檢查(Health)與存活狀態(Presence

Hydra 配置為服務模式(service mode)後,
它將自動在指定的 Redis 伺服器中記錄機器和應用程式級別的資訊。
此外,Hydra 還傳送存活狀態(Presence)資訊。
不幸的是,如果主機應用程式崩潰,那麼 Hydra 自然會停止更新存活狀態資訊。

此外,Hydra 會維護一個內部日誌,用於儲存檢測到的問題。
我們可以將其視為黑匣子飛行記錄器(flight recorder)。

儘管所有這些都是自動發生的,
但是您的應用程式可以使用 HydrasendToHealthLog() 方法來擴充儲存的資訊。
您還可以使用 getServiceHealthLog() 方法檢索日誌。

記住,您還可以通過在服務註冊期間註冊日誌事件偵聽器,在這些日誌條目發生時直接接收它們。

使用 Hydra 監視服務

HydraMCP web 應用程式演示瞭如何監視 Hydra 服務。有兩種監測方法:

  • 讀取 hydra 服務寫入 Redis 的資料
  • 使用 Hydra 方法接收聚合服務(aggregate service)資料。

後一種方法被推薦,因為它被認為對未來 Hydra 如何在 Redis 中儲存資料的潛在變化更具彈性。

以下方法有助於服務的自省(introspection)和控制(control)。

Method Description
getServices 檢索已註冊服務的列表。
findService 找到特定的服務。
getServicePresence 檢索特定服務的存活狀態
getServiceHealthAll 檢索所有註冊服務的健康資訊和健康日誌。
makeAPIRequest 向命名服務發出API請求。

有關 Hydra 功能的完整列表,請參閱本文件的最後部分。

messaging(訊息傳遞)

Hydra 通過以下方式支援服務間通訊:

  • 發現並直接使用伺服器的網路資訊(IP和埠)。
  • 通過使用 makeAPIRequest 方法。
  • 使用服務間(inter-service)訊息傳遞。
  • 使用服務訊息佇列(service message queues)。

您使用哪種方法取決於您的應用程式的需求和您願意做的額外工作的數量。
使用 Hydra 的訊息傳遞方法抽象了您可能需要處理的網路層功能。
因此,它提供了一種更簡單、更可靠的與遠端服務互動的方式。

發現和直接使用該服務的網路資訊很簡單:

let apiRoute = '/v1/email/send';
hydra.findService('emailer')
  .then((service) => {
    let url = `http://${service.ip}:${service.port}/${apiRoute}`;
    let options = {
      headers: {
        'content-type': 'application/json',
        'Accept': 'application/json; charset=UTF-8'
      },
      method: 'post'
    };
    options.body = emailObject;
    fetch(url, options)
    :
    :

注意:在使用上述方法之前,應該使用 getServicePresence 方法檢查服務是否可用。
畢竟,我們希望確保服務已註冊,並且當前可用。

在這裡,使用 HydramakeAPIRequest 方法變得更容易且更不容易出錯。
makeAPIRequest 方法接受一個物件,該物件包含服務的名稱以及其他有用但可選的資訊。
該方法自動處理服務可用性檢查,如果該服務暫時不可用,甚至可以將訊息(請求)推送到服務的訊息佇列中。
這是可選行為,並假定這對於傳送方是可接受的,並且遠端服務能夠將請求作為排隊的訊息進行處理。

let message = hydra.createUMFMessage({
  to: 'emailer:/v1/email/send',
  from: 'website:backend',
  body: {
    to: 'user@someplace.com',
    from: 'marketing@company.com',
    emailBody: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium'
    fallbackToQueue: true
  }
});
hydra.makeAPIRequest(message)
  then()
:

服務間的訊息傳遞(Inter-service messaging

使用 Hydra,您可以在服務之間傳送訊息,甚至可以在一系列服務之間路由訊息。
這是 Hydra-Router 提供的功能之一。

內建訊息通道(Built-in message channels

每個 hydra 服務都會自動監聽兩個內建通道,其他服務傳送的訊息會在其中到達。

一個通道監聽傳送到某一型別服務的任何訊息,另一個通道監聽指向特定服務例項的訊息。
因此,傳送到 file-processing 的訊息將被該服務的所有例項接收。而傳送到5585f53bd1171db38eafd79bf16e02f4@file-processing 的訊息只能由 ID5585f53bd1171db38eafd79bf16e02f4 的服務例項處理。

要將訊息傳送到服務,可以使用 sendMessage 呼叫。

let message = hydra.createUMFMessage({
  to: 'test-service:/',
  from: 'blue-service:/',
  body: {
    fileData: '{base64}'
  }
});
hydra.sendMessage(message);

第一個引數是要向其傳送訊息的服務的名稱,第二個引數是包含訊息的 UMF 格式的物件。

使用 sendMessage 時,會將訊息傳送到隨機選擇的可用服務例項。
如果您需要指定特定例項,則可以使用其唯一的服務 ID 來簡單地對服務進行定址。
這顯示在下面的 “to” 訊息欄位中。

let message = hydra.createUMFMessage({
  to: 'cef54f47984626c9efbf070c50bfad1b@test-service:/',
  from: 'blue-service:/',
  body: {
    fileData: '{base64}'
  }
});
hydra.sendMessage(message);

您可以通過 getInstanceID()getServicePresence() 方法獲得服務的唯一ID。

如果也需要,可以使用 sendBroadcastMessage 方法將訊息傳送到服務的所有可用例項。

警告:雖然,您可以使用 sendMessage 傳送和響應訊息 - 建議您在回覆時使用 sendReplyMessage
這樣做的原因是 sendReplyMessage 使用源訊息正確填寫健壯訊息傳遞所需的 UMF 欄位。
這包括使用源 midfortofrom UMF 欄位來制定回覆訊息。

您的服務可以通過將偵聽器新增到已載入的 hydra 例項來接收訊息。
下面的示例演示瞭如何在必要時制定響應。

hydra.registerService();
hydra.on('message', function(message) {
  // message will be a UMF formatted object
  console.log(`Received object message: ${msg.mid}: ${JSON.stringify(msg)}`);

  // to send a reply message here or elsewhere in your service use the `sendReplyMessage` call.
  hydra.sendReplyMessage(message, hydra.createUMFMessage({
    body: {
      // response items
    }
  }));
});

UMF messaging(UMF 訊息傳遞

在前面的示例中,我們使用了一個 UMF 樣式的訊息,它是由 Hydra createUMFMessage 方法建立的。
UMFUniversal Message Format 的首字母縮寫,是為可路由和可排隊的訊息傳遞而設計的輕量級訊息傳遞協議。

UMF 允許您有選擇地指定將一條訊息傳送到一個服務,
然後依次將訊息和/或(and/or)其他結果傳送到另一個服務。
這樣,流程可以跨服務連結在一起。

讓我們通過看看 createUMFMessage 實際上做了什麼來揭開 UMF 的神祕面紗。

首先,該方法接受一個 message 物件。在這個物件中需要三個欄位:

{
  "to":'serviceName',
  "from": 'sending-entity-name',
  "body": {}
}

createUMFMessage 方法採用該物件,並返回一個帶有附加欄位的新物件:

{
  "mid": "02d7e85b-5609-4179-b3af-fee60efc8ef0",
  "timestamp": "2016-03-28T15:40:05.820Z",
  "version": "UMF/1.2",
  "priority": "normal",
  "type": "msg",
  "to": "filewatcher",
  "from": "hydramcp",
  "body": {
    "actions": [
      "restart",
      "processBatch"
    ]
  }
}

附加欄位由 UMF 規範定義,並幫助 Hydra 和其他分散式系統處理訊息。

createUMFMessage 幫助程式方法有助於確保我們從格式正確的 UMF 相容訊息開始,並可以對其進行進一步擴充套件。

例如,在這裡我們可以在將訊息傳遞給 makeAPIRequest 方法之前更改訊息的優先順序(priority)和型別(type)。

message.priority = 'high';
message.type = 'service:control';

需要注意的是,我們可以將優先順序(priority)和型別(type)欄位新增到傳遞給 createUMFMessage 的原始訊息中。
該方法將使用您提供的欄位來覆蓋它在預設情況下建立的欄位。
因此,重要的是不要隨意重寫 midtimestamp

注意:有關 UMF 規範的詳細資訊,請訪問:Universal Messaging Format

Hydra 訊息佇列

當涉及到訊息傳遞和佇列時,重要的是要考慮應用程式需要的底層交付保證的級別。
Hydra 提供了“基本的”訊息傳遞和排隊功能,但並不打算替代 MQTTRabbitKafka 等伺服器。
因此,Hydra 並沒有提供那些系統所具備的許多功能。

因此,接下來是對 Hydra “does” 提供的功能的解釋。

像大多數 Hydra 一樣,Hydra 排隊依賴於內建在 Redis 中的功能。
Hydra 使用了一種文件化的原子訊息佇列模式,這種模式在 Redis 使用者中很流行。
Redis 的 rpushrpoplpushlrem 函式用於管理代表佇列的列表結構中的訊息狀態。
這只是一些背景知識,不必擔心,因為 Hydra 的目標是簡化這些問題。

Hydra 排隊通過將訊息排隊到現有服務的訊息佇列來工作。
這意味著 Hydra 沒有所有微服務都可以使用的共享佇列的概念。
相反,任何排隊的訊息都被放置在特定服務的訊息佇列中。

為了進一步探索這一點,讓我們想象一個建立和傳送電子郵件的 email-service

任何其他想要傳送電子郵件的微服務都可以向 email-service 傳送訊息。

這樣的資訊可能是這樣的:

{
  "to": "email-service:/",
  "mid": "2cae7508-c459-4794-86c6-42eb78f32573",
  "ts": "2018-02-16T13:34:51.540Z",
  "ver": "UMF/1.4.6",
  "bdy": {
    "to": "carlos.justiniano@gmail.com",
    "from": "accouting@xyzcorp.com",
    "htmlBody": "some html markup"
  }
}

該訊息可以從(比方說) accounting service 傳送到 email-service
後者依次將訊息排成佇列等待最終的傳遞。

讓我們根據我們的電子郵件示例來考慮 Hydra 的訊息佇列功能。

queueMessage

accounting-service 將使用 hydra queueMessage 函式在 email-service 佇列中放置一條訊息。
實際的訊息與我們之前看到的訊息類似。

queueMessage 函式接收到 UMF 訊息時,它將使用 to 欄位的值並對其進行解析以提取服務名稱。
在我們這裡的例子中,這就是電子郵件服務。服務名稱在內部用於確定將訊息放入哪個佇列。
hydra 原始碼內部的外觀顯示,訊息位於名為 hydra:service::{serviceName}:mqrecieved 的 Redis 列表中。key 的最後一部分是已接收(mqrecieved)佇列。 以後再說。

  /**
   * @name queueMessage
   * @summary Queue a message
   * @param {object} message - UMF message to queue
   * @return {promise} promise - resolving to the message that was queued or a rejection.
   */
  queueMessage(message)

getQueueMessage

通過將電子郵件放置在電子郵件服務的 mqrecieved 佇列中,該服務現在能夠提取一條訊息並開始對其進行處理。

為此,我們的 email-service 使用服務名稱簡單地呼叫了 hydra getQueuedMessage
現在,這是一個重要的考慮因素。
任何服務都可以呼叫 getQueuedMessage 並提供另一個服務的名稱來幫助該服務處理訊息!
不建議這樣做 - 但是可以的。它是為“知道自己在做什麼”的開發人員設計的。
在我們的例子中,我們的電子郵件服務將僅使用 getQueuedMessage('email-service') 來檢索 accounting service 排隊的訊息。

  /**
   * @name getQueuedMessage
   * @summary retrieve a queued message
   * @param {string} serviceName who's queue might provide a message
   * @return {promise} promise - resolving to the message that was dequeued or a rejection.
   */
  getQueuedMessage(serviceName)

現在,您可能想知道,當我們有多個 email-service 例項時,
每個例項都在檢查電子郵件佇列中是否有排隊的電子郵件,該怎麼辦?
那不會導致重複的訊息處理嗎?

答案是否定的。因為 getQueuedMessage() 是原子的,對它的多次呼叫不會返回相同的訊息。
因此,多個服務例項可以同時嘗試提取訊息,但其中只有一個會接收到給定的訊息。
Hydra使用 Redis rpoplpush 函式實現了這一點。
其工作方式是從 mqrecieved 佇列中讀取一條訊息,並將其放置在 mqinprogress 佇列中。
因此,對 getQueuedMessage 的下一個呼叫將不會在接收到的佇列(received queue)中看到原始訊息,因為它已被移動到程式佇列(process queue)中。
同樣,這只是實現細節,而不是你需要擔心的東西。

因此,一旦我們的電子郵件服務例項(email-service)構造併傳送電子郵件,
它就將排隊的訊息標記為已成功處理。

markQueueMessage

因此,我們的電子郵件服務(email service)呼叫 markQueueMessage(message, completed, reason) 來傳送實際的訊息,後面跟著一個 completedtruefalse)和一個可選的 reason字串。

  /**
   * @name markQueueMessage
   * @summary Mark a queued message as either completed or not
   * @param {object} message - message in question
   * @param {boolean} completed - (true / false)
   * @param {string} reason - if not completed this is the reason processing failed
   * @return {promise} promise - resolving to the message that was dequeued or a rejection.
   */
  markQueueMessage(message, completed, reason)

如果我們的電子郵件服務(email service)無法傳送訊息,
則可以呼叫 markQueueMessage 時,讓引數 completedfalse
這將導致該訊息被重新排隊以嘗試其他服務。

reason 欄位用於指示為什麼訊息被標記為已完成(completed)或未完成(incompleted)。

將訊息標記為已完成(true)將從 mqinprogress 佇列中刪除該訊息。

提示和技巧

如前所述,Hydra 訊息佇列是最基本的功能,
但由於 Redis 的支援,它的功能也非常強大,速度也非常快。

考慮到對 Redis 的依賴,重要的是不要建立大型排隊訊息,
並且 Redis 的效能會受到大規模影響。
解決此問題的一種方法是將一條小訊息排隊,該訊息指向一條資料庫記錄或檔案系統儲存。

我們使用的一個很好的技巧是將一個服務佇列訊息(service queue messages)放入它自己的佇列中。
其用法如下……假設一個服務接收到一個不能或不需要立即處理的請求。
服務可以通過將訊息傳送給自己來對訊息進行排隊,以便稍後進行處理。
因為服務的其他例項可能正在檢查佇列,所以另一個服務將接收訊息並處理它。
這讓我想起了排球比賽,一個地方把球推到空中,讓另一個球員把球猛擊過網。

如果您需要比 Hydra 提供的更多的訊息佇列相關功能,可以考慮使用 Kue
或者是廣泛使用的完善的訊息傳遞佇列系統之一。

Hydra 網路

Hydra 支援許多聯網選項。
本節將探討可用的選項以及您何時要使用它們。
在以下示例中,我們將使用 Hydra-router 例項
中的 config.json 檔案 - 但該配置可能來自任何其他啟用了hydra 的應用程式。

{
  "externalRoutes": {},
  "routerToken": "",
  "disableRouterEndpoint": false,
  "debugLogging": true,
  "queuerDB": 3,
  "requestTimeout": 30,
  "hydra": {
    "serviceName": "hydra-router",
    "serviceDescription": "Service Router",
    "serviceIP": "",
    "servicePort": "80",
    "serviceType": "router",
    "plugins": {
      "logger": {
        "logRequests": false,
        "toConsole": false,
        "noFile": true,
        "redact": [
          "password"
        ],
        "elasticsearch": {
          "host": "",
          "port": 9200,
          "index": "hydra",
          "rotate": "daily"
        }
      }
    },
    "redis": {
      "url": "redis://prod.p45rev.ng.0001.usw2.cache.amazonaws.com:6379/15"
    }
  }
}

在上面的 config.json 檔案中,
我們主要對 hydra.serviceIPhydra.servicePort 欄位感興趣。

servicePort 允許您指定想要 hydra 監聽的 IP 埠。
在上面的示例中,hydraRouter 正在監聽埠 80。
如果您未指定 servicePort(例如,如果 servicePort 為空),
那麼 hydra 將選擇一個大於 1024 的隨機非特權埠。
servicePort 欄位還支援指定埠範圍。

在此示例中,將從 30004000 中選擇一個隨機服務埠。

"servicePort": "3000-4000"

另外,如果 hydra 檢測到某個隨機埠已在使用中,它將嘗試使用指定範圍內的另一個埠。

讓我們關注 serviceIP 欄位,如果該欄位為空,hydra 將選擇它找到的第一個 IPv4 地址。
如果該欄位包含IP地址(例如192.168.1.18),那麼 hydra 將使用該地址。
如果該欄位包含文字,但不是有效的IP地址,則 hydra 假定您已指定 DNS 名稱。

Hydra 啟動時,它將檢視所有可用的網路介面。
啟動 Hydra-router 時,我們可以看到這一點。

 _   _           _             ____             _
| | | |_   _  __| |_ __ __ _  |  _ \ ___  _   _| |_ ___ _ __
| |_| | | | |/ _` | '__/ _` | | |_) / _ \| | | | __/ _ \ '__|
|  _  | |_| | (_| | | | (_| | |  _ < (_) | |_| | ||  __/ |
|_| |_|\__, |\__,_|_|  \__,_| |_| \_\___/ \__,_|\__\___|_|
       |___/

Starting service hydra-router:1.4.18 on 10.255.0.13:80
Detected IPv4 IPs:
* lo: 127.0.0.1 255.0.0.0
* eth0: 10.255.0.13 255.255.0.0
* eth0: 10.255.0.12 255.255.255.255
* eth1: 172.18.0.3 255.255.0.0
* eth2: 10.0.9.3 255.255.255.0
* eth2: 10.0.9.2 255.255.255.255

如果您希望 Hydra 繫結到一個特定的地址,
那麼可以通過 serviceInterface key 告訴 Hydra 應該
使用哪個介面(interface)和網路掩碼(network mask)來標識它應該使用的IP。

您可以在 config.json 檔案中使用 interfaceName/interfaceMask 的值執行此操作:

"serviceInterface": "eth2/255.255.255.0",

Hydra Methods(公開匯出的方法)

以下是 Hydra 公開匯出的方法。

作為一個模組,Hydra 被設計用來隱藏和阻止使用它的內部方法。
這有助於確保 Hydra 在越來越多的服務中按照預期的方式執行。

下面的方法列表由以下各節組織。
並非所有的應用程式和服務都需要使用列出的所有方法。

  • Setup - 模組設定和服務註冊
  • Discovery - 服務發現
  • Presence - 存活狀態檢查
  • Health - 執行狀況(健康)檢查和日誌記錄
  • Messaging - 訊息傳送
  • Routing - 訊息路由

Setup

init

用配置物件初始化 Hydra。

/**
 * @name init
 * @summary Initialize Hydra with config object.
 * @param {object} config - configuration object containing hydra specific keys/values
 * @return {object} promise - resolving if init success or rejecting otherwise
 */
init(config)

ready

返回在初始化完成時解析的 promise。

/**
* @name ready
* @summary returns promise that resolves when initialization is complete
* @return {object} promise - resolving if init success or rejecting otherwise
*/
ready()

shutdown

安全關閉 hydra

/**
* @name _shutdown
* @summary Shutdown hydra safely.
*/
shutdown()

registerService

將機器註冊為 Hydra 例項。

/**
 * @name registerService
 * @summary Registers this machine as a Hydra instance.
 * @description This is an optional call as this module might just be used to monitor and query instances.
 * @return {object} promise - resolving if registration success or rejecting otherwise
 */
registerService()

Discovery

getServiceName

檢索當前例項的服務名稱。

/**
 * @name getServiceName
 * @summary Retrieves the service name of the current instance.
 * @throws Throws an error if this machine isn't a instance.
 * @return {string} serviceName - returns the service name.
 */
getServiceName()

getServiceNodes

檢索服務列表(即使處於非活動狀態)。

/**
 * @name getServiceNodes
 * @summary Retrieve a list of services even if inactive.
 * @return {promise} promise - returns a promise
 */
getServiceNodes()

getServices

檢索可用例項服務的列表。

/**
 * @name getServices
 * @summary Retrieve a list of available instance services.
 * @return {promise} promise - returns a promise which resolves to an array of objects.
 */
getServices()

findService

查詢服務。

/**
 * @name findService
 * @summary Find a service.
 * @param {string} name - service name - note service name is case insensitive
 * @return {promise} promise - which resolves with service
 */
findService(name)

Presence

getServicePresence

檢索服務/例項的狀態資訊。

/**
 * @name getServicePresence
 * @summary Retrieve a service / instance's presence info.
 * @param {string} name - service name - note service name is case insensitive
 * @return {promise} promise - which resolves with service presence
 */
getServicePresence(name)

hasServicePresence

指示服務是否存在,表示該服務至少在一個節點中執行。

/**
 * @name hasServicePresence
 * @summary Indicate if a service has presence.
 * @description Indicates if a service has presence, meaning the 
 *              service is running in at least one node.
 * @param {string} name - service name - note service name is case insensitive
 * @return {promise} promise - which resolves with TRUE if presence is found, FALSE otherwise
*/
hasServicePresence(name)

getInstanceID

返回此程式的例項 id

/**
* @name getInstanceID
* @summary Return the instance id for this process
* @return {number} id - instanceID
*/
getInstanceID()

Health

sendToHealthLog

將訊息記錄到服務的執行狀況日誌佇列中。

/**
 * @name sendToHealthLog
 * @summary Log a message to the service instance's health log queue.
 * @private
 * @throws Throws an error if this machine isn't a instance.
 * @param {string} type - type of message ('error', 'info', 'debug' or user defined)
 * @param {string} message - message to log
 */
sendToHealthLog(type, message)

getServiceHealthLog

獲取此服務的執行狀況日誌。

/**
 * @name getServiceHealthLog
 * @summary Get this service's health log.
 * @throws Throws an error if this machine isn't a instance
 * @param {string} name - name of instance, use getName() if current service is the target.
 *                        note service name is case insensitive.
 * @return {promise} promise - resolves to log entries
 */
getServiceHealthLog(name)

getHealth

檢索服務執行狀況資訊。

/**
 * @name getHealth
 * @summary Retrieve service health info.
 * @private
 * @return {object} obj - object containing service info
 */
getHealth()

getServiceHealthAll

檢索所有例項服務的執行狀況。

/**
 * @name getServiceHealthAll
 * @summary Retrieve the health status of all instance services.
 * @return {promise} promise - resolves with an array of objects containing instance health information.
 */
getServiceHealthAll()

Messaging

createUMFMessage

建立一個 UMF 樣式訊息。

/**
 * @name createUMFMessage
 * @summary Create a UMF style message.
 * @description This is a helper function which helps format a UMF style message.
 *              The caller is responsible for ensuring that required fields such as
 *              "to", "from" and "body" are provided either before or after using
 *              this function.
 * @param {object} message - optional message overrides.
 * @return {object} message - a UMF formatted message.
 */
createUMFMessage(message)

makeAPIRequest

向 hydra 服務發出 API 請求。

/**
 * @name makeAPIRequest
 * @summary Makes an API request to a hydra service.
 * @description If the service isn't present and the message object has its
 *              message.body.fallbackToQueue value set to true, then the
 *              message will be sent to the services message queue.
 * @param {object} message - UMF formatted message
 * @return {promise} promise - response from API in resolved promise or
 *                   error in rejected promise.
 */
makeAPIRequest(message)

sendMessage

向 Hydra 服務的所有當前例項傳送訊息。

/**
 * @name sendMessage
 * @summary Sends a message to all present instances of a  hydra service.
 * @param {string | object} message - Plain string or UMF formatted message object
 * @return {promise} promise - resolved promise if sent or
 *                   error in rejected promise.
 */
sendMessage(message)

sendReplyMessage

根據收到的原始訊息傳送回覆訊息。

/**
 * @name sendReplyMessage
 * @summary Sends a reply message based on the original message received.
 * @param {object} originalMessage - UMF formatted message object
 * @param {object} messageResponse - UMF formatted message object
 * @return {object} promise - resolved promise if sent or
 *                   error in rejected promise.
 */
sendReplyMessage(originalMessage, messageResponse)

Routing

registerRoutes

註冊路由。

/**
* @name registerRoutes
* @summary Register routes
* @note Routes must be formatted as UMF To routes. https://github.com/cjus/umf#%20To%20field%20(routing)
* @param {array} routes - array of routes
* @return {object} Promise - resolving or rejecting
*/
registerRoutes(routes)

getAllServiceRoutes

檢索所有服務路由。

/**
* @name getAllServiceRoutes
* @summary Retrieve all service routes.
* @return {object} Promise - resolving to an object with keys and arrays of routes
*/
getAllServiceRoutes()

matchRoute

將路由路徑匹配到已註冊路由列表。

/**
* @name matchRoute
* @summary Matches a route path to a list of registered routes
* @private
* @param {string} routePath - a URL path to match
* @return {boolean} match - true if match, false if not
*/
matchRoute(routePath)

Message queues

queueMessage

排隊一個訊息

/**
* @name queueMessage
* @summary Queue a message
* @param {object} message - UMF message to queue
* @return {promise} promise - resolving to the message that was queued or a rejection.
*/
queueMessage(message)

getQueuedMessage

檢索排隊的訊息

/**
* @name getQueuedMessage
* @summary Retrieve a queued message
* @param {string} serviceName who's queue might provide a message
* @return {promise} promise - resolving to the message that was dequeued or a rejection.
*/
getQueuedMessage(serviceName)

markQueueMessage

將排隊的訊息標記為已完成或未完成

/**
* @name markQueueMessage
* @summary Mark a queued message as either completed or not
* @param {object} message - message in question
* @param {boolean} completed - (true / false)
* @param {string} reason - if not completed this is the reason processing failed
* @return {promise} promise - resolving to the message that was dequeued or a rejection.
*/
markQueueMessage(message, completed, reason)

Hydra Express

Hydra-Express 包使用 Hydra-core,是專門為利用 ExpressJS 的底層功能而設計的。

我們相信這是 ExpressJS 開發人員構建微服務最快最簡單的方式。

上手指南

安裝

要在另一個專案中安裝和使用:

$ npm install hydra-express

用法

'use strict';

const config = require('./config/properties').value;
const version = require('./package.json').version;
const hydraExpress = require('hydra-express');

function registerRoutesCallback() {
  hydraExpress.registerRoutes({
    '/v1/offers': require('./offers-v1-api')
  });
}

function registerMiddlewareCallback() {
  let app = hydraExpress.getExpressApp();
  app.use((req, res, next) => {
    console.log('req.headers', req.headers);
    next();
  });
}

hydraExpress.init(config, version, registerRoutesCallback, registerMiddlewareCallback)
  .then((serviceInfo) => {
    console.log('serviceInfo', serviceInfo);
  })
  .catch((err) => {
    console.log('err', err);
  });

在上面的示例中,then 語句上的 serviceInfo 返回一個物件,
其中包含 serviceNameservicePort 和其他有用值。

日誌記錄和錯誤報告

HydraExpress 包含一個 log 成員,允許您輸出日誌到控制檯和日誌檔案。

hydraExpress.log('error', message);

log 的第一個引數是日誌訊息的型別:fatalerrordebuginfo
第二個引數是要儲存的字串訊息。
強烈建議您利用這個機會建立描述性很強的日誌訊息,因為此函式不記錄堆疊跟蹤。

此外,將 fatalerror 型別的日誌訊息傳送到 hydra-core
以便在服務執行狀況檢查(health check)日誌中進行日誌記錄。

服務靜態 Web 內容

hydra-express 服務可以服務靜態 Web 內容。
只需建立一個名為 public 的資料夾,然後將網站檔案複製到其中即可。
可以在 demo/webserver 資料夾中找到一個示例。

Hydra Cli

上手指南

首先,您需要安裝 hydra-cli

$ sudo npm install -g hydra-cli

您只需在終端中輸入程式名稱即可檢視 hydra-cli 的所有選項。

$ hydra-cli
hydra-cli version 0.5.7
Usage: hydra-cli command [parameters]
See docs at: https://github.com/flywheelsports/hydra-cli

A command line interface for Hydra services

Commands:
  help                         - this help list
  cfg pull label               - download configuration file
  cfg push label filename      - update configuration file
  cfg list serviceName         - display a list of config versions
  config instanceName          - configure connection to redis
  config list                  - display current configuration
  use instanceName             - name of redis instance to use
  health [serviceName]         - display service health
  healthlog serviceName        - display service health log
  message create               - create a message object
  message send message.json    - send a message
  nodes [serviceName]          - display service instance nodes
  refresh node list            - refresh list of nodes
  rest path [payload.json]     - make an HTTP RESTful call to a service
  routes [serviceName]         - display service API routes
  services [serviceName]       - display list of services
  shell                        - display command to open redis shell

如您所見,hydra-cli 可以做很多事情。

配置 hydra-cli

要使用大多數 hydra-cli 命令,您首先需要對其進行配置,方法是將其指向您正在使用的 Redis 例項。

$ hydra-cli config local

config 命令需要一個你想要關聯到 Redis 連線資訊的名稱。
這允許您為多個環境儲存配置設定。
例如,您可能已經為您的專案 localstagingproduction 儲存了設定。

在儲存的設定之間切換很容易:

$ hydra-cli use staging

您可以使用 config list 命令檢視當前選擇的設定。

$ hydra-cli config list

與 hydra 配置檔案一起工作

Hydra 配置檔案,不要與 hydra-cli 配置設定混淆,服務在初始化 hydra 或 hydra-express 時會使用它們。

這些配置檔案通常在執行時載入,並將其內容傳遞給 Hydra。

在啟動過程中,如果 Hydra 看到 HYDRA_REDIS_URLHYDRA_SERVICE 環境變數,
Hydra 會向指定的 Redis 例項詢問其配置檔案的副本。

應該通過以下方式定義環境變數:

HYDRA_REDIS_URL='redis://10.0.0.2:6379/15'
HYDRA_SERVICE='myservice:0.12.1'

這通常用於 Docker 容器中啟用 hydra 的應用。

Hydra-cli 提供 cfg 命令,用於列出(listing)、載入(loading)和上傳(uploading)配置檔案資料到 Redis。

你可以使用下面的命令來獲取配置列表:

$ hydra-cli cfg list myservice

為了儲存配置,您必須指定由冒號和服務版本分隔的服務名稱。

$ hydra-cli cfg pull myservice:0.12.1

使用上面的 cfg pull 命令,檢索到的配置將顯示在終端中。
要將調出的配置儲存到一個檔案中,你可以使用:

$ hydra-cli cfg pull myservice:0.12.1 
>
 config.json

要上傳一個配置,你可以使用 cfg push 命令:

$ hydra-cli cfg push myservice:0.12.2 config.json

列出配置,檢索一個配置並將其儲存到檔案中——然後在上傳之前修改它,這就是管理服務配置的方法。

列出服務資訊

Hydra 的一個非常好的特性是,
執行 Hydra 的每個應用程式都會發出執行狀況(health)和存活狀態(presence)資訊。
使用 hydra 的任何應用程式都可以檢查這些資訊。

hydra-cli 程式實際上只是一個執行 Hydra 的命令列客戶端——它的大部分功能都是由 Hydra 提供的。

我們可以使用 nodes 命令檢視節點列表:

$ hydra-cli nodes

許多 Hydra 驅動的應用程式匯出 API 路由。我們可以使用以下方法檢視服務路由列表:

hydra-cli routes

您可以使用 health 命令檢索服務的健康狀態。

$ hydra-cli health

如果指定了服務名稱,則只能看到該服務的執行狀況資訊。

$ hydra-cli health myservice

節點列表清理

如果您啟動和停止服務,最終將看到不再處於活動狀態的服務。
這出現在 hydra-cli 節點命令期間。
這個列表沒有被自動清除的關鍵原因是它對於除錯和監視非常有用。
您必須使用 refresh 命令手動清除死服務列表。

$ hydra-cli refresh

快速連線到 Redis

如果需要,您可以要求 hydra-cli 提供與 redis-cli 客戶端一起使用的連線字串。

$ hydra-cli shell

在執行 Mac 或 Linux 的計算機上,您可以發出以下命令來自動呼叫 redis-cli

$(hydra-cli shell)

下一步

hydra-cli。我們發現它是使用 Hydra 應用程式時必不可少的工具。

閱讀專案倉庫中的完整文件

Hydra 生產器

Hydra Generator 是一個命令列工具,可讓您快速構建 Hydra 或 Hydra-Express 應用程式的完整腳手架。生成器依賴於稱為 Yeoman 的工具。

生成器的偉大之處在於,您可以在不到15秒的時間內構建微服務。
然後,您可以繼續自定義生成的程式碼以適合您的特定需求。

快速上手

首先全域性安裝 Yeomangenerator

$ sudo npm install -g yo generator-fwsp-hydra

要使用生成器,只需使用生成器的名稱呼叫 yeoman 即可。
在我們的案例中,hydra-generator 被稱為 fwsp-hydra
您收到的第一個提示要求您為服務命名。

$ yo fwsp-hydra
? Name of the service (`-service` will be appended automatically) hello

在出現許多其他問題(您可以選擇 default )之後,該過程以關於如何構建和啟動新專案的說明結束。

Done!
'cd example-service' then 'npm install' and 'npm start'

請記住 Hydra 服務需要使用 Redis 例項。
所以在你執行你的應用程式之前,你需要 redis 可用。
預設情況下,Hydra 生成器將建立一個配置檔案,該檔案需要一個本地的 Redis 例項。

{
  "environment": "development",
  "hydra": {
    "serviceName": "hello-service",
    "serviceIP": "",
    "servicePort": 5000,
    "serviceType": "hello",
    "serviceDescription": "says hello",
    "redis": {
      "url": "redis://127.0.0.1:6379/15"
    }
  }
}

Hydra-Router

Hydra Router 是一種服務感知路由器,可以將 HTTPWebSocket 訊息請求定向到已註冊的服務。

嘗試 Hydra-router

嘗試使用 hydra-router 的最簡單方法是獲取現成的Docker容器。

$ docker pull flywheelsports/hydra-router

儘管以上命令將起作用,但我們建議您訪問 https://hub.docker.com/r/flywheelsports/hydra-router/tags/ 並提取特定版本,例如:

$ docker pull flywheelsports/hydra-router:1.3.3

執行容器要求您具有正在執行的 Redis 本地例項,
並且需要使用計算機上的 ifconfigipconfig 工具標識計算機的 IP 地址。

$ docker rm -f hydra-router
$ docker run -d -p 5353:5353 --add-host host:10.1.1.175 --name hydra-router flywheelsports/hydra-router:1.3.3

然後,您應該能夠將Web瀏覽器指向 http://10.1.1.175:5353 併發出路由器請求,例如:

http://10.1.1.175:5353/v1/router/version

拉取最新的 Docker 容器

Hydra-router docker 映象被儲存在這裡:https://hub.docker.com/r/flywheelsports/hydra-router/tags/

從原始碼構建

您還可以獲取 hydra-router 原始碼並在本地使用它。

https://github.com/flywheelsports/hydra-router

簡介

使用 HydraRouter 外部客戶端可以連線到服務,而不需要知道它們的IP或埠資訊。HydraRouter 負責服務發現和路由。

此外,HydraRouter 還可以路由到由某個服務託管的網站。
如果使用服務名作為第一個 url 路徑段訪問路由器,並且請求是 HTTP GET 呼叫,
那麼請求將被路由到一個可用的服務例項。

當一種服務型別存在多個服務例項時,通過 HydraRouter 發出的請求將在可用例項之間進行負載平衡。

Hydra 路由器還公開了 RESTful 端點,可用於查詢服務執行狀況(health)和存活狀態(presence)資訊。

使用 Hydra 微服務可以使用 findServicesendServiceMessagemakeAPIRequest 等函式相互定位。
這一切都執行得很好,不需要 DNS 或 service router

但是,當遠端API請求到達雲基礎架構時,確定如何靈活地路由針對上游服務的請求就成為問題。
考慮服務可以使用不同的IP和/或隨機埠啟動。
為了滿足這些要求,一種方法涉及使用DNS,彈性負載均衡器和彈性IP。
仍然必須管理連線到每個負載均衡器的機器,
並且在一臺機器上執行多種服務會使情況進一步複雜化。

這是動態服務註冊和路由器起作用的地方。它們旨在通過感知服務並執行智慧路由來簡化上述要求。

Hydra-Router 使用 Hydra 來實現動態服務登錄檔和路由器。
為此,它使用啟用了 Hydra 的服務在其啟動和初始化階段釋出的路由資訊。
然後,它將傳入的訊息直接路由到服務,而不考慮以下挑戰:

  • 可能有一個或多個服務例項可用於處理特定請求。
  • 服務可以來來去去,每次都以不同的IP地址或埠開始。
  • 隨著服務的新增或改進,服務路由可能會更改(更新或刪除)。
  • 不需要對基礎設施進行任何更改來解決上述問題。

那麼這是如何運作的呢?

如前所述,在啟動期間,Hydra 服務執行自動註冊。
這是在呼叫 hydra.registerService 方法時在後臺完成的。
使用 Hydra-Express 構建服務時,可以在初始化階段自動註冊服務的路由。服務的路由可以在初始化階段自動註冊。

hydraExpress.init(config, version, () => {
  hydraExpress.registerRoutes({
    '/v1/offers': require('./offers-v1-api')
  });
});

然後,HydraRouter 使用生成的服務註冊資訊將訊息路由到特定服務。

服務可以在網路上的任何機器上啟動,無論是否使用隨機 IP 埠。
因為每個服務都是自己註冊的-它可以被一個 HydraRouter 定位。這是動態服務登錄檔位。

但它真的是 router 嗎?是的! Hydra-Router 使用 route-parser — 一種基於 AST 的樹解析器來匹配路由。

當訊息被髮送到 HydraRouter 時,它會檢查請求是否與已註冊的路由匹配。如果是,則將請求訊息路由到註冊了該路由的服務的活動例項。當一個服務存在多個服務例項時,Hydra-Router 將對請求進行負載平衡,以將負載分佈在可用的服務之間。

這裡的一個關鍵要點是,這是自動發生的,不需要更新配置和基礎設施。
這可以使用 Hydra 內建的服務發現和路由功能。

訊息閘道器

除了將普通的 HTTP 訊息路由到它們指定的服務之外,HydraRouter 還為其他傳入訊息公開一個 HTTP 端點。

/v1/router/message

訊息預期採用UMF訊息格式,因此可以路由到網路中的其他微服務。

網站流量透傳

Hydra-router 能夠將站點請求路由到微服務。
因此,除了響應 RESTful API 呼叫和處理訊息外,微服務還可以為網站提供服務。此特性並不適用於高流量使用的場景。
相反,該特性用於管理頁面、狀態頁面和其他低流量的情況。
雖然可以提供影像和其他二進位制資產——建議您使用CDN來解除安裝對公共靜態資產的請求。

使用此特性的好處是,您可以在任意IP上的動態埠上啟動服務,並利用路由器查詢各個服務例項。
通過這種方式,網站請求可以由多個負載均衡的服務例項來處理。

HTTP 代理透傳

HydraRouter 允許您指定到非 hydra 服務的路由。
本質上,這允許外部客戶端通過 hydra 向後端伺服器發出 API 請求。

要啟用此功能,只需在配置檔案中的 externalRoutes 鍵下定義外部路由。
externalRoutes key 由url物件和它們的路由陣列組成。

:
'externalRoutes': {
  'https://someotherservice.com': [
    '[post]/api/v2/token/create',
    '[get]/api/v2/user/self.json'
  ]
},
:

WebSockets

Hydra Router 支援 WebSocket 連線。支援以下方案:

  • 客戶端連線到 hydra-router 並將訊息傳送到後端服務
  • 後端服務可以將非同步訊息傳送回特定客戶端
  • 客戶端可以通過 hydra-router 向彼此傳送訊息

有關構建此類應用程式的更多資訊,請參見 Hydra Router Message Client 文件。

不過,這裡有一個問題——只支援 stringified UMF 格式的訊息。

let ws = new WebSocket('ws://127.0.0.1:5353');
ws.on('open', () => {
  let msg = {
    'to': 'a-hydra-service:/',
    'version': 'UMF/1.4.3',
    'from': 'tester:/',
    'mid': '4736ef3d-fcbb-46aa-80a0-f4f3493e1d74',
    'timestamp': '2017-01-12T20:16:29.157Z',
    'body': {}
  };
  ws.send(JSON.stringify(msg));
});

您可以使用 Hydra UMFMessage helper class 建立 UMF 訊息。

WebSocket 重連線和訊息傳遞

如果客戶端的 WebSocket 連線中斷,Hydra-Router 支援為您的 WebSocket 客戶端提供訊息佇列。
您的客戶端僅需要重新連線併發出重新連線訊息即可開始接收以前的訊息。

初始連線後,您的 WebSocket 客戶端將收到一條類似於以下內容的訊息:

{
  "to": "2945p8eigxz@client:/",
  "frm": "c274b25909aee5cbec2857361f425fa7@hydra-router:/",
  "mid": "dffc2949-0e2a-4417-8f28-46addb5fc716",
  "ts": "2017-01-12T19:31:54.831Z",
  "typ": "connection",
  "ver": "UMF/1.4.3",
  "bdy": {
    "id": "2945p8eigxz"
  }
}

bdy.id 值是 WebSocket session ID
如果您的客戶端死亡並且需要重新連線,它可以發出一條重新連線訊息,例如:

{
  "to": "hydra-router:/",
  "frm": "client:/",
  "mid": "e173a0da-2785-4f83-8b39-0dea954dd91b",
  "typ": "reconnection",
  "ver": "UMF/1.4.3",
  "bdy": {
    "id": "2945p8eigxz"
  }
}

上面有三件事要注意。首先,訊息被髮送到 hydra-router,後者管理實際的 WebSocket 連線。
其次,bdy.id 與客戶端崩潰或失去連線之前所擁有的 WebSocket 會話ID相同。
第三,我們使用“reconnection”的訊息型別(typ)。

收到這樣的訊息後,Hydra-Router 將載入排隊的訊息(如果有)並將其開始傳送到新連線的客戶端。

保護 Websocket 訊息

Hydra-Router 支援使用加密簽名的UMF訊息。
啟用此功能後,僅接受簽名的訊息。
未簽名的訊息將導致底層 socket 連線終止。

簽名的訊息僅確保訊息是由已知客戶端建立的,而其本身並不對訊息內容進行加密。
要加密訊息內容,請考慮使用其他加密方法並轉換為 BASE64。

要啟用簽名訊息,請在 config.json 檔案中新增兩個欄位。
確保 forceMessageSignture 設定為 true,並且 signatureSharedSecret 包含 UUID 或密碼

"forceMessageSignature": true, 
"signatureSharedSecret": "d632dd6d-fb75-44cc-bdbf-ee1364f3716c",

HydraRouter 使用 HMAC SHA-256 通過提供的 signatureSharedSecret 對訊息進行簽名。

crypto
  .createHmac('sha256', signatureSharedSecret)
  .update(JSON.stringify(this.message))
  .digest('hex');

因為 Websocket 訊息是從外部客戶端傳送的,
所以每個客戶端必須能夠使用 HMAC SHA-256,
使用與 HydraRouterconfig.json 檔案中儲存的相同的共享 secretUMF 訊息進行簽名。
由於很難在 Web瀏覽器客戶端中保護 secret,因此不建議將其用於 Web 客戶端。
建議編譯並能夠使用安全儲存的微服務和移動應用程式。

1.4.28 版本開始,Hydra 支援對 UMF 訊息進行簽名,
從而可以輕鬆保護微服務之間的訊息。
IOSAndroid 編寫的客戶端應用程式需要使用加密庫。

在 Hydra 中,您可以使用:

'use strict';

const WebSocket = require('ws');
const hydra = require('hydra');
const signatureSharedSecret = 'd632dd6d-fb75-44cc-bdbf-ee1364f3716c';

let ws = new WebSocket('http://localhost:5353');

ws.on('open', () => {
  let umf = hydra.createUMFMessage({
    'to': 'hydra-router:[GET]/v1/router/list/nodes',
    'from': 'client:/',
    'body': {}
  });
  umf.signMessage('sha256', signatureSharedSecret);
  ws.send(JSON.stringify(umf));
});

ws.on('message', (data, flags) => {
  console.log(data);
});

路由儀表板

Hydra-Router 可以顯示一個儀表板,以顯示其自身和其他服務的狀態。
儀表板每15秒更新一次,紅色顯示有問題的服務。

要訪問路由器,只需將瀏覽器指向 hydra-router 的根目錄即可:

http://localhost:5353/


如果您需要停用或限制對儀表板的訪問,請參閱保護 hydra-router 一節。

可選的 Router API

Hydra-Router 提供了 HTTP API,以公開其正在使用的路由和服務。
這是完全可選的,旨在用於除錯和監視方案。

Router version: /v1/router/version

查詢 Hydra-Router 的版本。

$ curl -X 'GET' 'http://localhost:8000/v1/router/version'

響應:

{
  'status': 200,
  'statusText': 'Success',
  'result': {
    'version': '1.0.0'
  }
}

Listing routes: /v1/router/list/routes

用於顯示已註冊路由的列表。請注意,Hydra-Router 本身是一種服務,它顯示自己的 API

{
  'status': 200,
  'statusText': 'Success',
  'result': [
    {
      'serviceName': 'hydra-router',
      'routes': [
        '/v1/router/version',
        '/v1/router/refresh',
        '/v1/router/list/:thing',
        '/v1/router/message',
        '/v1/router/refresh/:service'
      ]
    },
    {
      'serviceName': 'red-service',
      'routes': [
        '/v1/red/hello',
        '/v1/red/say'
      ]
    },
    {
      'serviceName': 'blue-service',
      'routes': [
        '/v1/blue/hello',
        '/v1/blue/say'
      ]
    }
  ]
}

Listing services: /v1/router/list/services

顯示活動服務例項。
在這裡,我們可以看到服務存活狀態資訊(presence),包括健康(health)和正常執行時間(uptime)等資料點。
如果服務崩潰,它將不再出現在響應中。

{
  'status': 200,
  'statusText': 'Success',
  'result': [
    {
      'serviceName': 'blue-service',
      'instanceID': 'bd579b2384701aba617af40c0ff75580',
      'updatedOn': '2016-05-22T00:21:11.908Z',
      'processID': 51947,
      'ip': '127.0.0.1',
      'port': 3686,
      'sampledOn': '2016-05-22T00:21:11.908Z',
      'architecture': 'x64',
      'platform': 'darwin',
      'nodeVersion': 'v4.2.4',
      'memory': {
        'rss': 28045312,
        'heapTotal': 31148896,
        'heapUsed': 26754472
      },
      'uptime': '2 minutes, 7.358 seconds',
      'usedDiskSpace': '82%',
      'log': []
    },
    {
      'serviceName': 'hydra-router',
      'instanceID': '4d5831c3de6feb69a6b150946753065c',
      'updatedOn': '2016-05-22T00:21:11.103Z',
      'processID': 51755,
      'ip': '127.0.0.1',
      'port': 8000,
      'sampledOn': '2016-05-22T00:21:11.103Z',
      'architecture': 'x64',
      'platform': 'darwin',
      'nodeVersion': 'v4.2.4',
      'memory': {
        'rss': 27168768,
        'heapTotal': 18740576,
        'heapUsed': 17638920
      },
      'uptime': '3 minutes, 2.337 seconds',
      'usedDiskSpace': '82%',
      'log': [
        {
          'ts': '2016-05-22T00:18:10.383Z',
          'serviceName': 'hydra-router',
          'type': 'info',
          'processID': 51755,
          'message': 'Starting hydra-router service hydra-router on port 8000'
        }
      ]
    },
    {
      'serviceName': 'red-service',
      'instanceID': 'a3e9a88912b49238e7254ef3cec2e4cd',
      'updatedOn': '2016-05-22T00:21:09.766Z',
      'processID': 51759,
      'ip': '127.0.0.1',
      'port': 1185,
      'sampledOn': '2016-05-22T00:21:09.767Z',
      'architecture': 'x64',
      'platform': 'darwin',
      'nodeVersion': 'v4.2.4',
      'memory': {
        'rss': 30908416,
        'heapTotal': 31148896,
        'heapUsed': 27060712
      },
      'uptime': '2 minutes, 47.579 seconds',
      'usedDiskSpace': '82%',
      'log': [
      ]
    }
  ]
}

Listing nodes: /v1/router/list/nodes

列表節點請求顯示可能存在也可能不存在的節點(服務的例項)。
此呼叫與 /list/services 呼叫的不同之處在於,將顯示不活動的例項。

$ curl -X 'GET' 'http://localhost:8000/v1/router/nodes'
{
  'statusCode': 200,
  'statusMessage': 'OK',
  'statusDescription': 'Request succeeded without error',
  'result': [
    {
      'serviceName': 'music',
      'serviceDescription': 'Music service',
      'version': '0.0.9',
      'instanceID': '07eb06f8f8b346a78704a5d9e672a780',
      'updatedOn': '2016-07-27T19:38:28.773Z',
      'processID': 2209,
      'ip': '10.1.1.176',
      'port': 5000,
      'elapsed': 2
    },
    {
      'serviceName': 'hydra-router',
      'serviceDescription': 'Service Router',
      'version': '1.1.1',
      'instanceID': 'ecf72192389ff6212bf88da03802adc9',
      'updatedOn': '2016-07-27T19:38:29.705Z',
      'processID': 2864,
      'ip': '10.1.1.176',
      'port': 5353,
      'elapsed': 1
    },
    {
      'serviceName': 'auth-service',
      'serviceDescription': 'Authentication service',
      'version': '0.0.10',
      'instanceID': '5b3ade39a70aba675223edc46d8c710c',
      'updatedOn': '2016-07-27T19:38:13.371Z',
      'processID': 2487,
      'ip': '10.1.1.176',
      'port': 1337,
      'elapsed': 17
    }
  ]
}

Route Refresh: /v1/router/refresh/:service

當基於 hydra 啟用的 web 服務它們線上自動啟用。

示例路由透傳:

通過 Hydra-Router 將訊息傳送到稱為 red-service 的服務的示例:

$ curl -X 'GET' 'http://localhost:8000/v1/red/hello'

響應:

{
  'code': 200,
  'result': {
    'message': 'Hello from red-service'
  }
}

您可能已經注意到上面的響應中有一點不一致。
前面的示例顯示 statusstatusTextresult JSON 欄位。
上面的例子不是這樣的!原因是 Hydra-Router 返回從服務端點傳送的確切的(未翻譯的)伺服器響應。

保護 router

預設情況下,路由器API是公開的。
在生產部署中,您可能會禁用或限制對路由器 API 的訪問許可權。
您可以通過在 config.json 檔案中定義兩個 key 來完成此操作:

"disableRouterEndpoint": false,
"routerToken": "",

如果 disableRouterEndpoint 設定為 true
則將禁用對路由器 API 的訪問,並且呼叫者將收到 HTTP 404 響應。
如果啟用,則對路由器 API 的訪問取決於 routerToken 的值。
如果令牌為空,則允許訪問-如果存在值,則它必須是 UUIDv4 token。

"disableRouterEndpoint": false,
"routerToken": "098ebe18-7e1b-4ddd-ae2a-cc6521e5b641",

破折號和段的大小很重要,並使用正規表示式進行了驗證。

Hydra-Router 將搜尋 token 的存活狀態(presence)作為查詢字串引數。

http://localhost:5353/v1/router/list/routes?token=098ebe18-7e1b-4ddd-ae2a-cc6521e5b641

問題

當服務更改了它的路由時會發生什麼?

好問題! 啟動服務時,除了註冊自身和釋出其路由外,它還會向所有 Hydra-Router 服務廣播一條訊息,
以便它們可以為新更新的服務更新其路線資訊。
這是基於每個服務完成的,因此其他服務路由不會受到影響。

如果服務例項不可用怎麼辦?

如果找不到服務的活動例項,則 Hydra-Router 會回覆並顯示標準 HTTP 503(HTTP_SERVICE_UNAVAILABLE)錯誤。

那麼有什麼問題嗎?

HydraRouter 只能與啟用了 Hydra 的服務一起使用,並且只能路由 JSON 訊息有效負載。 但是,支援最常見的 HTTP 動詞,因此您可以傳送 GET,POST,DELETE 和 PUT 請求。

Hydra Router 訊息客戶端

注意:hrmc 需要 hydra-router 1.6.0 或更高版本

作為一個訊息傳遞閘道器,Hydra Router 支援 WebSocket 連線和訊息路由到連線的客戶端和微服務。
在開發基於訊息的客戶端和服務時,有必要測試訊息流。
這使得我們建立了一個名為 hrmc 的工具 —— Hydra Router Message Client。
HRMC 是基於 NodeJS 的 REPL,您可以在其中互動式地連線到 Hydra Router,以傳送訊息。

$ hrmc
Hydra Router Message Client v.1.0.3
Use client.help() for help.
Use client members or .exit to close repl.

hrmc REPL 匯出一個名為 client 的類,
其中包含用於將訊息連線併傳送到 Hydra Router 例項的成員函式。
例如,要開啟與在本地主機埠 5353 上執行的 hydra router 例項的連線,請執行以下操作:

➤ client.open('ws://localhost:5353')

關閉連線:

➤ client.close()

安裝 hrmc

您可以使用 NPM 安裝 hrmc:

$ npm install -g hrmc

我們應該使用 -g 全域性標誌來安裝它,以確保它可以從您的任何專案中啟動。hrmc 專案託管在github上:https://github.com/cjus/hrmc

使用 hrmc

使用 hrmc 之前,請確保您正在執行 hydra-router 例項。
如前所述,您可以使用客戶端物件的成員函式在 Hydra Router 上開啟和關閉連線。

要獲取可用命令的列表,請使用:

➤ client.help()

可用的客戶成員列表包括:open close reopen createMessagesendMessage

讓我們通過一個示例 session 來了解這一切是如何工作的。
在這個 session 中,我們將連線到 hydra router 並呼叫其內部API。
然後我們將與 user service 對話。

我們首先連線到 hydra-router。在這個例子中,我正在連線 192.168.1.221:5482hydra router

➤ client.open('ws://192.168.1.221:5482')

為了保持一致,所有 hrmc client 函式都需要一個引數-期望該引數為字串!
因為 JSON 使用雙引號,所以我們將使用單引號字元。

返回的響應為:

➤ Connection established: 27ce6oxplm5
{"to":"27ce6oxplm5@client:/","frm":"5d77f8ac3d784bc2946e4d2a2f806805@hydra-router:/","mid":"72ffb790-6634-4e97-8e48-276c223b7b0f","ts":"2018-02-19T14:43:37.407Z","typ":"connection","ver":"UMF/1.4.6","bdy":{"id":"27ce6oxplm5","ip":"::ffff:192.168.1.221"}}

上面的程式碼可能有點難以閱讀,所以您可以使用 client.jsonPrint() 函式來漂亮地列印 JSON。

{
  "to": "27ce6oxplm5@client:/",
  "frm": "5d77f8ac3d784bc2946e4d2a2f806805@hydra-router:/",
  "mid": "72ffb790-6634-4e97-8e48-276c223b7b0f",
  "ts": "2018-02-19T14:43:37.407Z",
  "typ": "connection",
  "ver": "UMF/1.4.6",
  "bdy": {
    "id": "27ce6oxplm5",
    "ip": "::ffff:192.168.1.221"
  }
}

這就是 hydra-router 的響應。
JSON 文件採用一種名為 UMF 的格式,hydra 使用該格式進行訊息傳遞。

有關更多資訊,請參見 hydra訊息傳遞

to 欄位中,我們看到訊息被視為來自 27ce6oxplm5@client:/ 您將注意到來自 @clientID
這是我們連線的 hrmc 的唯一客戶端 ID。它與我們在上面建立連線時返回的 ID 相同。

frm 欄位告訴我們,以上訊息是從具有唯一服務ID 5d77f8ac3d784bc2946e4d2a2f806805 的 hydra-router 傳送的,該訊息的 bdy 正文部分特別重要,因為它為客戶端分配了唯一的ID 27ce6oxplm5

讓我們繼續前進。 接下來,我們將建立一條新訊息以傳送到 Hydra Router。

➤ client.createMessage()
{"to":"hydra-router:/","frm":"27ce6oxplm5@client:/","mid":"c677bf50-80be-4d2e-87e1-4d048c372b47","ts":"2018-02-19T15:26:35.835Z","ver":"UMF/1.4.6","bdy":{}}

這將建立一條基本訊息,我們可以將JSON複製到編輯器並對其進行自定義。
更新 to 欄位到 hydra-router:[get]/v1/router/version

當我們使用 client.jsonPrint() 函式獲取響應並檢視它時,我們看到:

{
  "to": "27ce6oxplm5@client:/",
  "frm": "5d77f8ac3d784bc2946e4d2a2f806805@hydra-router:/",
  "mid": "312da70c-8b72-48a9-a481-77d32653e867",
  "ts": "2018-02-19T15:35:34.114Z",
  "ver": "UMF/1.4.6",
  "bdy": {
    "version": "1.6.0-experimental"
  }
}

在撰寫本文時,我使用的是 Hydra-Router 的實驗版本 1.6.0。

現在,讓我們談談 user service。

我們可以使用 client.createMessage 建立另一個訊息,然後將 to 欄位更改為:user-service:[get]/v1/user/health

因此,在我們要傳送給 Hydra Router 的新訊息中,
我們將要求它將其傳遞給名為 user-service 的服務,
並請求 v1/user/health 端點。
使用 client.jsonPrint() 函式檢視時,響應類似於:

{
  "to": "27ce6oxplm5@client:/",
  "frm": "user-servcie:[get]/v1/user/health",
  "mid": "1a92789c-e6e4-43e8-b4ad-c08999c26141",
  "rmid": "c677bf50-80be-4d2e-87e1-4d048c372b47",
  "ts": "2018-02-19T15:58:25.440Z",
  "ver": "UMF/1.4.6",
  "bdy": {
    "result": {
      "serviceName": "user-svcs",
      "instanceID": "a125395c70f643dfb6743dc5aba32e17",
      "hostName": "dac3865f9fd0",
      "sampledOn": "2018-02-19T15:58:25.454Z",
      "processID": 1,
      "architecture": "x64",
      "platform": "linux",
      "nodeVersion": "v8.0.0",
      "memory": {
        "rss": 54370304,
        "heapTotal": 28356608,
        "heapUsed": 25279784,
        "external": 66409
      },
      "uptimeSeconds": 287.767
    }
  }
}

更高階的例子

現在,如果我們希望後端服務將訊息非同步傳送回連線的客戶端,該怎麼辦?

後端服務只需要知道客戶端ID即可路由訊息!這是訊息的格式。

{
  "to": "hydra-router:/",
  "frm": "abackend-service:/",
  "fwd": "27ce6oxplm55@client:/",
  "mid": "5cc4e3f5-ad72-41f3-a23f-212bdabc331f",
  "ts": "2018-02-17T16:02:48.902Z",
  "ver": "UMF/1.4.6",
  "bdy": {
    "msg": "Hello from a backend service"
  }
}

後端服務只需在 to 欄位中指定網路中任何可用的 hydra-router 例項。
然後,您的服務將在 frm 欄位中標識自己。
然後它將使用 fwd(轉發)欄位指定應接收訊息的客戶端例項。
訊息的 bdy 將包括您的服務要傳送的任何 JSON 負載。
接收 hydra-router 例項將確保將訊息路由到適當的客戶端。

這種路由機制也允許客戶端互相傳送訊息:

{
  "to": "hydra-router:/",
  "frm": "1objkd63kfd@client:/",
  "fwd": "27ce6oxplm55@client:/",
  "mid": "5cc4e3f5-ad72-41f3-a23f-212bdabc331f",
  "ts": "2018-02-17T16:02:48.902Z",
  "ver": "UMF/1.4.6",
  "bdy": {
    "msg": "Hello from 1objkd63kfd"
  }
}

因此,客戶端又會通過 hydra-router 傳送打算髮送給另一個客戶端的訊息。

重要的是要注意,客戶端不是直接與對方通話,而是通過 hydra router。

由於 Hydra-Router 旨在與其他 hydra-routers 進行通訊,
因此可以將訊息路由到客戶端和服務,無論它們連線到哪個 hydra-router
自然地,所討論的 hydra-routers 和服務需要在同一網路或可訪問的網路上。

因此,在此示例中,儘管客戶端 1objkd63kfd27ce6oxplm55
沒有連線到相同的 Hydra-Router,但上面的相同訊息仍將被路由。

支援以下方案:

  • 客戶端連線到 hydra-router 並將訊息傳送到後端服務
  • 後端服務可以將非同步訊息傳送回特定客戶端
  • 客戶端可以通過 hydra-router 向彼此傳送訊息

在 Docker 上使用 Hydra

Hydra 和 HydraExpress 應用程式需要使用 Redis 伺服器。
如果您在 Docker 容器中執行啟用了 hydra 的微服務,則需要確保您的服務可以訪問 Redis。

如:hello-service

用作 docker 容器時,您需要更新服務的配置檔案,因為正在執行的容器將具有與主機不同的IP地址。
這樣就不會在容器內找到 Redis!

有幾種方法可以解決此問題。

方法1:在容器生成上使用硬編碼 config.json

第一種方法是簡單地用 Redis 伺服器的硬編碼配置條目構建容器。

方法2:使用 DNS 條目

另一個選擇是在配置檔案中指定一個 DNS 條目,它對映到你的 Redis 伺服器。參見下面的 redislocation 條目。

{
  "environment": "development",
  "hydra": {
    "serviceName": "hello-service",
    "serviceIP": "",
    "servicePort": 5000,
    "serviceType": "hello",
    "serviceDescription": "says hello",
    "redis": {
      "url": "redis://redislocation:6379/15"
    }
  }
}

接下來,使用上面的配置重建容器,然後可以使用以下命令執行它:

$ docker run -it -d -p 5000:5000 \
  --name hello-service \
  --add-host redislocation:192.168.1.186 \
  --workdir=/usr/src/app \
  cjus/hello-service:0.0.7

然後,您可以使用以下方法測試訪問容器的服務:

$ curl localhost:5000/v1/hello/test
{"statusCode":200,"statusMessage":"OK","statusDescription":"Request succeeded without error","result":{"msg":"hello from hello-service - da3a2e99becc03abed949080d8fa3185"}}

方法3:對映虛擬資料夾

然而,另一種方法是執行帶有對映卷的容器。在本例中,本地專案資料夾 ~/dev/hello-service/config 對映到容器的內建 /usr/src/app/config 資料夾上。因此,執行中的容器將使用您在專案資料夾中指定的配置檔案。這樣一來,您就可以將 config.json 檔案保留在容器中,並在容器外部對其進行覆蓋。

$ docker run -it -d -p 5000:5000 \
  --name hello-service \
  --workdir=/usr/src/app \
  -v ~/dev/hello-service/config:/usr/src/app/config \
  cjus/hello-service:0.0.7

資源

示例

文章

hydra-microservice 手冊,持續修正。

我是為少。微信:uuhells123。公眾號:黑客下午茶。

謝謝點贊支援???!

相關文章