hydra-microservice 中文手冊(3W字預警)
- 2020 年 12 月 15 日
- 筆記
Hydras 是什麼?
Hydra 是一個 NodeJS 包(技術棧不是重點,思想!思想!思想!
),它有助於構建分佈式應用程序,比如微服務。
Hydra 提供服務發現(service discovery
)、分佈式消息傳遞(distributed messaging
)、
消息負載平衡(message load balancing
)、日誌記錄(logging
)、呈現(presence
)和運行狀況(health
)監視等功能。
Hydra 利用 Redis 的強大力量做到了這一點。
例如,使用 Hydra
的 sendMessage
和 makeAPIRequest
調用,
一個應用程序可以按名稱與其他應用程序通信,而無需指定 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
。
如果沒有,只需提供雲服務提供的 redisUrl
和 redisDb
即可。
現在我們都準備好了。 讓我們構建一個微服務!
第 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"}}
或通過瀏覽器://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 地址。
這使您可以將服務放置在外部負載平衡器(例如Nginx
或Docker 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 配置
除了 host
、port
和 db
,你可以通過 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
參數代替 host
,port
,db
和 password
。
有關詳細信息,請參見 IANA registration。以下等效於上面的 host/port/db
:
redis: {
url: 'redis://127.0.0.1:6379/15'
}
注意:如果你傳入一些 host
、port
、db
和 password
的組合,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
,
該 promise
將 resolve
為服務信息對象;
如果找不到該服務,則返回一個被拒絕的 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"
}
然後,應用程序可以使用 ip
和 port
信息來調用 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
)。
儘管所有這些都是自動發生的,
但是您的應用程序可以使用 Hydra
的 sendToHealthLog()
方法來擴充存儲的信息。
您還可以使用 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 = `//${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 方法檢查服務是否可用。
畢竟,我們希望確保服務已註冊,並且當前可用。
在這裡,使用 Hydra
的 makeAPIRequest
方法變得更容易且更不容易出錯。
makeAPIRequest
方法接受一個對象,該對象包含服務的名稱以及其他有用但可選的信息。
該方法自動處理服務可用性檢查,如果該服務暫時不可用,甚至可以將消息(請求)推送到服務的消息隊列中。
這是可選行為,並假定這對於發送方是可接受的,並且遠程服務能夠將請求作為排隊的消息進行處理。
let message = hydra.createUMFMessage({
to: 'emailer:/v1/email/send',
from: 'website:backend',
body: {
to: '[email protected]',
from: '[email protected]',
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
的消息只能由 ID
為5585f53bd1171db38eafd79bf16e02f4
的服務實例處理。
要將消息發送到服務,可以使用 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
字段。
這包括使用源 mid
、for
、to
、from 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
方法創建的。
UMF
是 Universal 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
的原始消息中。
該方法將使用您提供的字段來覆蓋它在默認情況下創建的字段。
因此,重要的是不要隨意重寫 mid
或 timestamp
。
注意:有關 UMF
規範的詳細信息,請訪問:Universal Messaging Format
Hydra 消息隊列
當涉及到消息傳遞和隊列時,重要的是要考慮應用程序需要的底層交付保證的級別。
Hydra 提供了「基本的」消息傳遞和排隊功能,但並不打算替代 MQTT
、Rabbit
和 Kafka
等服務器。
因此,Hydra
並沒有提供那些系統所具備的許多功能。
因此,接下來是對 Hydra 「does」 提供的功能的解釋。
像大多數 Hydra 一樣,Hydra 排隊依賴於內置在 Redis 中的功能。
Hydra 使用了一種文檔化的原子消息隊列模式,這種模式在 Redis 用戶中很流行。
Redis 的 rpush
、rpoplpush
和 lrem
函數用於管理代表隊列的列表結構中的消息狀態。
這只是一些背景知識,不必擔心,因為 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": "[email protected]",
"from": "[email protected]",
"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)
來發送實際的消息,後面跟着一個 completed
(true
或false
)和一個可選的 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
時,讓參數 completed
為 false
。
這將導致該消息被重新排隊以嘗試其他服務。
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.serviceIP
和 hydra.servicePort
字段感興趣。
servicePort
允許您指定想要 hydra 監聽的 IP 端口。
在上面的示例中,hydraRouter
正在監聽端口 80。
如果您未指定 servicePort
(例如,如果 servicePort
為空),
那麼 hydra 將選擇一個大於 1024 的隨機非特權端口。
servicePort
字段還支持指定端口範圍。
在此示例中,將從 3000
到 4000
中選擇一個隨機服務端口。
"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. //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
返回一個對象,
其中包含 serviceName
,servicePort
和其他有用值。
日誌記錄和錯誤報告
HydraExpress 包含一個 log
成員,允許您輸出日誌到控制台和日誌文件。
hydraExpress.log('error', message);
log
的第一個參數是日誌消息的類型:fatal
、error
、debug
或 info
。
第二個參數是要存儲的字符串消息。
強烈建議您利用這個機會創建描述性很強的日誌消息,因為此函數不記錄堆棧跟蹤。
此外,將 fatal
或 error
類型的日誌消息發送到 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: //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 連接信息的名稱。
這允許您為多個環境存儲配置設置。
例如,您可能已經為您的項目 local
、staging
和 production
存儲了設置。
在存儲的設置之間切換很容易:
$ hydra-cli use staging
您可以使用 config list
命令查看當前選擇的設置。
$ hydra-cli config list
與 hydra 配置文件一起工作
Hydra 配置文件,不要與 hydra-cli 配置設置混淆,服務在初始化 hydra 或 hydra-express 時會使用它們。
這些配置文件通常在運行時加載,並將其內容傳遞給 Hydra。
在啟動過程中,如果 Hydra 看到 HYDRA_REDIS_URL
和 HYDRA_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秒的時間內構建微服務。
然後,您可以繼續自定義生成的代碼以適合您的特定需求。
快速上手
首先全局安裝 Yeoman
和 generator
:
$ 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 是一種服務感知路由器,可以將 HTTP
和 WebSocket
消息請求定向到已註冊的服務。
嘗試 Hydra-router
嘗試使用 hydra-router
的最簡單方法是獲取現成的Docker容器。
$ docker pull flywheelsports/hydra-router
儘管以上命令將起作用,但我們建議您訪問 //hub.docker.com/r/flywheelsports/hydra-router/tags/ 並提取特定版本,例如:
$ docker pull flywheelsports/hydra-router:1.3.3
運行容器要求您具有正在運行的 Redis
本地實例,
並且需要使用計算機上的 ifconfig
或 ipconfig
工具標識計算機的 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瀏覽器指向 //10.1.1.175:5353
並發出路由器請求,例如:
//10.1.1.175:5353/v1/router/version
拉取最新的 Docker 容器
Hydra-router docker 鏡像被存儲在這裡://hub.docker.com/r/flywheelsports/hydra-router/tags/
從源碼構建
您還可以獲取 hydra-router
源代碼並在本地使用它。
//github.com/flywheelsports/hydra-router
簡介
使用 HydraRouter
外部客戶端可以連接到服務,而不需要知道它們的IP或端口信息。HydraRouter 負責服務發現和路由。
此外,HydraRouter
還可以路由到由某個服務託管的網站。
如果使用服務名作為第一個 url
路徑段訪問路由器,並且請求是 HTTP GET
調用,
那麼請求將被路由到一個可用的服務實例。
當一種服務類型存在多個服務實例時,通過 HydraRouter
發出的請求將在可用實例之間進行負載平衡。
Hydra
路由器還公開了 RESTful
端點,可用於查詢服務運行狀況(health
)和存活狀態(presence
)信息。
使用 Hydra 微服務可以使用 findService
、sendServiceMessage
和 makeAPIRequest
等函數相互定位。
這一切都運行得很好,不需要 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': {
'//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,
使用與 HydraRouter
的 config.json
文件中存儲的相同的共享 secret
對 UMF
消息進行簽名。
由於很難在 Web瀏覽器客戶端中保護 secret
,因此不建議將其用於 Web 客戶端。
建議編譯並能夠使用安全存儲的微服務和移動應用程序。
從 1.4.28
版本開始,Hydra
支持對 UMF 消息進行簽名,
從而可以輕鬆保護微服務之間的消息。
為 IOS
或 Android
編寫的客戶端應用程序需要使用加密庫。
在 Hydra 中,您可以使用:
'use strict';
const WebSocket = require('ws');
const hydra = require('hydra');
const signatureSharedSecret = 'd632dd6d-fb75-44cc-bdbf-ee1364f3716c';
let ws = new WebSocket('//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
的根目錄即可:
//localhost:5353/
如果您需要停用或限制對儀錶板的訪問,請參閱保護 hydra-router 一節。
可選的 Router API
Hydra-Router
提供了 HTTP API
,以公開其正在使用的路由和服務。
這是完全可選的,旨在用於調試和監視方案。
Router version: /v1/router/version
查詢 Hydra-Router
的版本。
$ curl -X 'GET' '//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' '//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' '//localhost:8000/v1/red/hello'
響應:
{
'code': 200,
'result': {
'message': 'Hello from red-service'
}
}
您可能已經注意到上面的響應中有一點不一致。
前面的示例顯示 status
、statusText
和 result
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
)作為查詢字符串參數。
//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上://github.com/cjus/hrmc
使用 hrmc
使用 hrmc
之前,請確保您正在運行 hydra-router
實例。
如前所述,您可以使用客戶端對象的成員函數在 Hydra Router
上打開和關閉連接。
要獲取可用命令的列表,請使用:
➤ client.help()
可用的客戶成員列表包括:open
close
reopen
createMessage
和 sendMessage
讓我們通過一個示例 session 來了解這一切是如何工作的。
在這個 session 中,我們將連接到 hydra router 並調用其內部API。
然後我們將與 user service 對話。
我們首先連接到 hydra-router。在這個例子中,我正在連接 192.168.1.221:5482
的 hydra 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:/
您將注意到來自 @client
的 ID
。
這是我們連接的 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
和服務需要在同一網絡或可訪問的網絡上。
因此,在此示例中,儘管客戶端 1objkd63kfd
和 27ce6oxplm55
沒有連接到相同的 Hydra-Router
,但上面的相同消息仍將被路由。
支持以下方案:
- 客戶端連接到
hydra-router
並將消息發送到後端服務 - 後端服務可以將異步消息發送回特定客戶端
- 客戶端可以通過
hydra-router
向彼此發送消息
在 Docker 上使用 Hydra
Hydra 和 HydraExpress 應用程序需要使用 Redis 服務器。
如果您在 Docker 容器中運行啟用了 hydra 的微服務,則需要確保您的服務可以訪問 Redis。
用作 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
資源
示例
- A sample hello-service project
- Hydra Hot Potato Service – an example of distributed messaging
- Hydra Message Relay – processing WebSocket calls via HydraRouter
文章
- Tutorial: Building ExpressJS-based microservices using Hydra
- Building a Microservices Example Game with Distributed Messaging
- Deploying Node.js Microservices to AWS using Docker
- Using Docker Swarm for Deploying Node.js Microservices
hydra-microservice 手冊,持續修正。
我是為少。微信:uuhells123。公眾號:黑客下午茶。
謝謝點贊支持👍👍👍!