一種使用 Redis 深度驅動的,為構建輕量級分散式應用程式(Microservices)的工程方案

為少發表於2020-12-10

Hydra 是一個輕量級的 NodeJS 庫,用於構建分散式計算應用程式,比如微服務。我們對輕量級的定義是:輕處理外部複雜性和基礎設施依賴 —— 而不是有限的輕處理。
Hydra 聲稱對基礎設施的依賴很輕,這是因為它唯一的外部依賴是 Redis

Hydra 利用 Redis 豐富的資料結構來實現重要的微服務所需的功能。
presence(線上狀態)、service discovery (服務發現)、load balancing (負載平衡)、messaging(訊息傳遞)、queuing(佇列)等。

Hydra 到底是什麼?

Hydra 是一個 NodeJS 模組,可以將其匯入到 JavaScript Node 應用程式中,以使其具有微服務功能。 Hydra 通過利用 Redis 做到這一點。

在這裡,我們看到三個微服務 - 每個微服務都帶有一個連線到 Redis 的 Hydra 模組。 在這種模式下,大多數服務不會直接與 Redis 通訊。而是底層的 Hydra 模組代理 Redis。

關於此圖的另一點是,Hydra 只是另一個匯入的模組 - 如綠色所示。 Hydra 在底部僅以藍色顯示,以說明其存在和與 Redis 的關係。

Hydra 模組公開一個 JS 類介面,該介面共有36個成員函式。

該快照提供了簡化抽象的感覺。成員函式(例如 findServicesendMessage)非常簡單。

Hydra 如何利用 Redis

這張幻燈片顯示了許多重要的微服務問題。每個都是 non-trivial(非平凡的) 微服務所必需的。我們將詳細研究 Hydra 如何使用 Redis 來實現所有這些功能。

請記住,這裡的目標是展示如何做到這一點 —— 而不是說每種方法都是您應該如何在自己的服務中實現該特性。一個恰當的例子是,雖然你可以在 Redis 中儲存你的微服務配置資料,或者使用 Redis 作為一個 logger —— 但這並不意味著你應該這樣做。至少,除非你確切地知道你在做什麼,以及需要做哪些權衡。

另外,請記住,您也許不需要Hydra。這些功能都是由 Redis 實現的,您當然可以在自己的應用程式中做到這一點。(如:Golang 來一版)

我將向您展示的一個關鍵點是,其中一些特性只有在組合時才能實現。例如,請求(request)和訊息路由(message routing)依賴於狀態(presence)、執行狀況(health)、服務發現(service discovery)和負載平衡(load balancing)。

如您所知,這些特性中的每一個都可以使用各種基礎設施工具來解決。然而,Hydra 的一個關鍵目標是簡化構建微服務,同時最小化外部基礎設施需求。在構建可用於生產的服務時,您需要決定需要哪些 Hydra 特性,以及哪些特性將從其他工具獲得。這不是一個非此即彼的命題,而是一個你想要達到的目標和你能多快開始的問題。

就是說,很有趣的是,僅使用 Redis 和您喜歡的程式語言就可以實現所有這些功能。

Key 空間組織

瞭解 Hydra 如何利用 Redis 的第一步是檢視它如何組織對 Redis key 空間的使用。

Hydra 使用的鍵 —— 由 24 段標籤組成,標籤之間用冒號分隔。段標籤被命名為:字首(Prefix)、服務名稱(Service name)、例項 ID(Instance ID)和型別(type)。

字首段允許過濾 Hydra key 和非 Hydra key。因此,如果你大量使用 Redis,那麼能夠過濾特定的 key 是至關重要的。

服務名稱段幫助過濾特定服務型別的 key。例如授權(authorization)、使用者(user)或影像處理(image processing)服務型別。

例項ID(Instance ID)段允許過濾唯一服務例項的 key。執行微服務時,通常需要執行一個服務型別的多個例項。每個服務例項都分配有唯一的 ID,並且能夠區分它們是有用的。

最後,還有“型別(Type)”部分,用於對 key 的用途進行分類。並非每個 key 中都存在所有段。例如,某些 key 中不需要服務名稱(Service name)和例項ID(instance ID)。

這是使用者服務(user service) key 的示例。我們看到字首 hydra:service 後跟服務名稱,在本例中為 “user-svcs”。接下來,我們看到唯一的例項ID(unique instance ID)。最後,我們看到此 key 的型別為 presence(presence)。 因此,我們說 presence 資訊儲存(presence information)在此 key 地址中。

在前面的描述中,一個令人困惑的地方是,key 由名稱組成,名稱中有24個段標籤,用冒號分隔。然而,在這裡我們看到 hydra:service 也用冒號分隔。當時的想法是,可能還有另一種 hydra:other 型別,service 只是其中之一。
關於訊息傳遞還有另一個不一致的地方,稍後我們將對此進行討論。

我們可以輸入 redis-cli 和輸入 Redis 命令來檢視各種鍵。在接下來的演示中我們會看到一些例子。

一個關於我們將使用的 redis-cli 示例的快速說明:你將看到 keys 命令的使用 —— 這是為了方便 —— Hydra 內部使用 Redis scan 命令。

所以重述一下 —— Hydra 使用的 key 是按段組織的,這使它們更容易查詢。此外,一致的組織使它們更容易擴充套件和維護。隨著我們繼續,我們將看到 key 在每個微服務特性的組織中所扮演的角色。讓我們從檢查 presence(examining presence)開始。

Presence(呈現 type)

在微服務領域中,發現服務、瞭解服務是否正常以及是否可以路由到該服務的能力至關重要。
這些特性依賴於知道某個特定的服務例項確實存在並可供使用。
對於服務發現(service discovery)、路由(routing)和負載平衡(load balancing)等特性,這也是必需的。

每隔一秒鐘,Hydra 就會更新它的服務 key 的生存時間(TTL)。
在三秒鐘的時間內這樣做失敗將導致 key 過期,主機應用程式被視為不可用。

在這裡我們可以看到使用的 Redis 命令是 “get”“setex”,它們設定了一個 key 和一個到期時間。

我們可以使用帶有模式匹配項的 “keys” 命令來查詢 presence key。 注意,存在三個 key。這告訴我們存在 “ asset-svcs” 執行的三個例項。

如果我們嘗試檢索其中一個 key 的內容,我們會看到它包含例項ID(instance ID)。

並對鍵使用 TTL 命令可以向我們顯示,它還有 2 秒鐘的剩餘時間。

所以回顧一下。可以使用自動過期的 key 來管理微服務的存在。Hydra 代表主機服務自動更新金鑰。這意味著這不是開發人員做的事情。在3秒內更新 key 失敗將導致服務被視為不可用。這可能意味著服務不健康。

這將我們帶入下一個主題…

Health(健康 type)

能夠監視微服務的執行狀況是另一個重要功能。Hydra 每 5 秒鐘收集並寫入一個健康資訊快照。

您可以檢查快照以快速檢視單個服務例項的執行狀況。並且,快照可以由監控工具(例如 HydraRouter 儀表板)使用。

這就是健康 key 的樣子。請注意,唯一的新位是標識 key 為關於 health“type” 段。

當我們檢視金鑰的內容時,我們看到它包含一個字串化的 JSON 物件。 在這種情況下,它用於 “project-svcs”

JSON 解串可以更容易地檢視儲存的內容。它包含了很多有用的資訊。

因此,可以按服務例項儲存執行狀況(health)資訊。使用包含字串化的JSON文字的字串 key 進行管理。 而且這些資訊可以通過監視應用程式來使用。

Service Discovery(服務發現)

接下來,讓我們考慮服務發現,這是任何微服務架構的另一個必備功能。

通過名稱發現服務的 IPPORT 位置的能力極大地簡化了通訊。其他好處包括不必管理 DNS 條目或建立固定的路由規則。

服務發現資訊以一種 “nodes” 的形式儲存在 Redis Hash 中。使用 Hash 可以實現快速的查詢。我們使用 Redis 的“hget”“hset”“hgetall” 命令來處理 nodes hash。

以下 Redis 操作可用於實現服務發現。
首先是對特定服務型別的查詢。
第二個是查詢可用例項。第三次查詢,允許Hydra檢索有關特定服務例項的資訊。

我們可以看到有用的資訊,例如服務的版本,instanceID,IP地址和埠,最後還有主機名。在此示例中,主機名也恰好是Docker 容器 ID

我們可以使用 Redis “hgetall” 命令檢索有關所有可用例項的資訊。這就是 Hydra Router 如何檢索要顯示在其儀表板上的服務列表。

讓我們回顧一下。Hydra 使用 servicename key 段進行查詢,以發現有關服務的各種資訊。可以使用 Redis Hash 管理服務詳細資訊,該服務可提供快速的服務發現

接下來,讓我們考慮路由。

Routes(路由)

同時路由 HTTP 和訊息(例如 Web Socket 或 PubSub )- 要求對 routes 進行驗證。微服務可以釋出其 routes 到 Redis。舉個例子,HydraRouter 使用釋出的 routes 來實現動態的服務感知路由。

每個服務以 “service:routes” 型別的 key 釋出其路由。在這裡,我們看到 “asset-svcs” 路由的 key

服務路由儲存在 Set 結構中。非常適合,因為您不想重複輸入路由條目。使用 SADDSMEMBERS 命令。

回到我們的 routes 上。我們可以使用 key 模式拉出路由列表。在這裡我們可以看到許多服務的路由。

我們可以使用 “smembers” 命令檢視特定路由集的內容。順便說一下,括號中的 [get][post][put] 位表示 HTTP REST 端點。對於其他訊息傳遞傳輸,可以省略括號方法的使用。

讓我們回顧一下。每個服務都會向一個 Redis Set 釋出它的路由。訪問一個單獨的路由會顯示該服務的路由條目集合。

路由使用 Set 資料結構儲存在 Redis 中,這避免了重複的路由。釋出的路由可用於實現動態的服務感知路由。接下來,讓我們考慮負載平衡。

Load Balancing(負載平衡)

隨著應用程式的增長,您將需要在可用的服務例項之間平衡請求。這是通過使用我們看到的服務呈現(service presence)和路由(routing)功能完成的。在應用程式級別,使用 Hydra,這與使用 “makeAPIRequest”“sendMessage” 呼叫一樣簡單。當 Hydra 使用路由和 presence 資訊在可用的目標例項中進行選擇時,就會在這些呼叫中進行負載平衡。

一個很好的好處是,在路由過程中,如果某個請求在某個特定例項上失敗,Hydra 可以在出現 HTTP 503 伺服器不可用錯誤之前重試其他可用例項。

如您所見,負載平衡依賴於其他功能,例如 presence,服務發現和路由。

回顧一下,可以使用我們已經看到的 Presence、服務發現(Service Discovery)和路由(Routing)特性來完成服務之間的負載平衡請求。Redis Strings,Hashes 和 Sets 使這成為可能。整體大於部分之和。

Messaging(訊息)

分散式服務強制通過底層網路彼此通訊。
HTTP Rest 呼叫可能是最常見的,但是 socket 訊息傳遞可能更有效。
Hydra 中的訊息傳遞是通過 Redis 的 Pub/Sub 通道完成的,而 Redis 通過 socket 連線實現了 Pub/Sub

這裡有一個例子。Hydra 使用 Redis 的 “subscribe”、“unsubscribe” 和 “publish” 命令。

順便說一句,Hydra router 能夠通過 HTTP 和 WebSocket 接受訊息並將其轉換為 pub/sub 訊息。

要了解其工作原理,請考慮兩個服務,即 “asset-svcs”“project-svcs”
每個服務建立兩個 key,一個使用服務名(service name),另一個使用服務名(service name)和例項ID(instance ID)。
每個服務都監聽兩個 channel

在大多數情況下,您並不關心哪個服務例項處理請求。在這些情況下,將使用沒有特定例項ID的通道。

現在,當您需要向特定例項傳送訊息時,可以使用具有例項ID的通道。 需要特別注意的是,hydra 在負載均衡時會將請求轉換為具有特定例項ID的服務名稱。 這樣可以確保只有一個例項可以處理給定的訊息或請求。

我們可以使用 Redis pub/sub channels 命令檢視 channel key 列表。注意這裡有四個 key。
第一個 key 是 “asset-svcs” 的名稱 —— 由 asset service 的所有例項共享。
接下來,我們將看到三個具有惟一例項id的附加 key。三個服務例項各有一個。

繼續關注訊息傳遞。為了確保微服務之間的互操作性,必須標準化共享的通訊格式。
通用訊息格式是已記錄的基於JSON的格式,其中包括對訊息傳遞,路由和排隊的支援。
這些訊息作為JSON字串文字儲存在Redis中。

繼續關注訊息傳遞。
為了確保微服務之間的互操作性,必須對共享的通訊格式進行標準化。
通用訊息格式是一種文件化的 JSON-based 的格式,包括對訊息傳遞(messaging)、路由(routing)和佇列(queuing)的支援。
這些訊息作為 JSON 字串文字儲存在 Redis 中。

下面是一個示例 UMF 訊息。

“to”“frm”“bdy” 欄位是必填欄位,服務可以自由地在 “body” 物件中包含自己的自定義欄位。

讓我們看看如何在實踐中使用它。

在左邊,“client-svcs”“project-svcs” 傳送訊息。
注意,這隻需要一個 UMF 建立呼叫和一個傳送訊息呼叫,這裡用黃色顯示。

在右邊 —— “project-svcs” 偵聽訊息並根據需要進行處理。這是使用事件訊息偵聽器完成的。

請注意,Hydra 抽象了服務發現(service discovery)、負載平衡(load balancing)、路由和釋出/訂閱(pub/sub)等細節。
傳送和接收訊息只涉及三個成員函式。
在這裡稍作停留是值得的。
花幾秒鐘考慮一下使用您最喜歡的堆疊時這個示例會是什麼樣子。

讓我們仔細看看。Send message 通過解析訊息中的 “to” 欄位來確定目標服務名稱。
有了服務名,下一步是檢查可用的例項。
有了目標例項,訊息就會被字串序列化,並通過 Redis 的 “publish” 命令傳送。

同樣,我們可以列出Redis中的所有釋出/訂閱通道(Pub/Sub Channnel)。
訊息可以通過這些通道傳送,並由偵聽器(listeners)檢索。
因此,只需編寫一些程式設計程式碼,我們就可以使用 Redis 通過組織良好的通道集合(collection of channels)來路由訊息。

總而言之,值得注意的是,由於服務是物理分佈的,因此最終需要進行訊息傳遞。
Redis 使用其釋出/訂閱(pub/sub)功能啟用訊息傳遞。

標準化通訊可以實現服務之間的互操作性。
我們還看到,當我們抽象出底層服務發現(service discover)、負載平衡(load balancing)、
路由(routing)和釋出/訂閱(pub/sub)細節時,
在應用程式級別上的通訊是多麼容易。

接下來,讓我們考慮訊息佇列。

Queuing(佇列)

作業(Job)和訊息佇列(message queues)是許多重要應用程式的另一個重要部分。
Hydra 使用 Redis 為每種服務型別維護動態佇列(dynamic queues)。

然後,服務例項可以讀取其佇列和處理專案。

佇列訊息的內容是UMF訊息,遵循用於訊息傳遞的相同格式。
同樣,互操作性為王!

Hydra 會為每種服務型別自動建立三個佇列。

  • “已接收(received)”佇列
  • “進行中(inprogress)”佇列
  • 還有一個“不完整(incomplete)”佇列。

因為這些是列表,我們使用 Redis 的 “lpush”“rpush”“rpoplpush”“lrem” 命令。

下面的圖表顯示了佇列之間的訊息流。
在 Redis 中,在佇列之間移動專案是一個原子操作。
所以不管你有多少微服務,它都是安全的。

在下一個左邊的示例中,
對訊息進行排隊就像建立一個 UMF 訊息並呼叫 “queueMessage” 來傳送它一樣簡單。
右下角的程式碼顯示了影像處理服務通過呼叫 “getQueuedMessage”
然後在處理完訊息後呼叫 “markQueueMessage”,使訊息脫離佇列。很簡單吧?

因此,回顧一下,有時無法期望立即做出回應。
在這種情況下,我們只需要排隊等待後續處理。
Redis List 資料結構可以用作訊息佇列。
使用原子操作的 “lpush”“rpoplpush” 之類的命令使此操作可行。
再次在這裡,我們看到了使用高階抽象進行基本排隊有多麼容易。

Logging(日誌記錄)

分散式日誌記錄(Distributed logging)是任何微服務體系結構的另一個重要特徵。
但是,如果您知道 Redis,可能會對將其用作分散式記錄器(distributed logger)感到震驚。
您可能會理所當然地擔心。但是,您可以將其用作飛行記錄器(flight recorder)。
僅儲存最嚴重的錯誤,並使用 “lpush”“ltrim” 限制條目的數量。
然後,至少您將有一種快速的方法來檢查微服務可能出了什麼問題。

這是 key 的樣子。注意,key 型別為 health:log

在這裡,我們可以看到 health:log key 型別實際上是一個 “List” 資料結構。
所以我們可以使用 Redis 的 “lrange” 命令來檢視 “imageproc-svcs” 的飛行記錄器(flight recorder)日誌。

總結:使用微服務無法登入數十臺或更糟的數百臺計算機。
分散式日誌記錄絕對是必經之路。
使用 Redis,您可以構建一個輕量級的記錄器(light-weight logger)以用作飛行記錄器(flight recorder)。
使用 Redis List 資料結構以及方便的 “lpush”“ltrim” 命令可以實現此目的。

最後,讓我們考慮配置管理。

Configuration management(配置管理)

管理分散式微服務的配置檔案可能具有挑戰性。然而,你甚至可以使用 Redis 來儲存你服務的配置檔案。
但這並不理想,得遠離,核心缺點是在 Redis 中儲存配置會使 Redis 有狀態。但這是可以做的。

讓我們看看它是如何工作的。
configs key 型別是一個 hash。
該 hash 的 key 由服務版本和設定為該版本配置資料的值組成。

下面是一個配置示例。
在我們的示例中,我們使用名為 “hydra-cli” 的命令列工具,
它允許我們將配置檔案推到特定的服務版本。
所做的一切就是建立一個 hash 條目,其鍵由服務名稱和版本組成,
並將檔案內容字串序列化後(stringified)作為其值。
記住,你也可以使用 shell 指令碼來驅動 redis cli。

我們可以使用 “hget” 命令和配置的版本提取一個特定的版本。

讓我們快速回顧一下,我們瞭解了 Redis 如何用於儲存應用程式配置檔案。
Redis Hash 資料結構允許我們儲存每種服務型別的配置。
每個配置條目均由服務版本標籤索引,並且內容僅指向字串化的 JSON 配置。

總結

這裡分享的是一種大量地使用 JavaScript 和 NodeJS 來利用 Redis 構建分散式應用程式的工程方案。
但是,你完全可以用其他你愛的語言(如:Golang)對 Redis 做同樣的事情。

互相交流

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

謝謝點贊支援???!

一起雲原生

相關文章