歡迎光臨
每天分享高質量文章

.NET Core微服務之路:基於Ocelot的API閘道器實現–http/https協議篇

前言

最近一直在忙公司和私下的兼職,白天十個小時,晚上四個小時,感覺每天都是打了雞血似的,精神滿滿的,連自己那已經學打醬油的娃都很少關心,也有很長一段時間沒有更新部落格了,特別抱歉,小夥伴們都等得想取關了吧!哈哈,開個玩笑,這裡十分感謝小夥伴們一直以來的關註和支援。

還有不到半個月的時間,豬年就要到來,在這裡先提前祝大家豬年吉祥,願君身體健康,福壽綿長,吉祥如意,財源滾滾,心想事成,萬事順利,新年快樂,好運平安!

你看這小豬多可愛,有點像麥兜!甜品先到這兒,我們一起來看本節(也是第三部分的主要思想)重點。你可能會問,怎麼突然一下畫風全變了,而且還多了這麼多的框框和一些看不懂的圖示,不急,本來筆者想直接就中間那一部分單獨拎出來講解,但確實無法讓部分朋友理解,索性直接將整個微服務架構全部展現了出來。

什麼是閘道器?

上一篇我們透過DotNetty構建的遠端RPC框架,已經實現了遠端客戶端的呼叫,使用的體驗是:跟在本地呼叫介面一樣沒有任何的區別。但是,這呼叫是沒有任何限制的,任何人、任何客戶端、只要知道了服務節點地址,並透過TCP實現RPC呼叫,便可大大方方的、肆無憚忌的呼叫這些服務節點,如果就這樣部署在生產環境上,是非常危險的。因此,我們需要引入“閘道器”這樣的中間服務,來限制和管理流入的請求合法性和合規性。

當然,這裡我們提到的閘道器,並非物理機上的物體閘道器交換機(如下圖所示):

而是將一臺伺服器的部分元件,透過軟體技術,實現網路管理,比如網絡卡(筆者曾見過一臺DELL伺服器上裝了11塊網絡卡),透過OSI模型進行管理,實現比如流量限制,路由轉發、驗證、簽權等等一些列功能,所以,我們一般稱之為API閘道器。我們看看網上的統一解釋:

API閘道器是一個伺服器,是系統的唯一入口。從面向物件設計的角度看,它與外觀樣式類似。API閘道器封裝了系統內部架構,為每個客戶端提供一個定製的API。它可能還具有其它職責,如身份驗證、監控、負載均衡、快取、請求分片與管理、靜態響應處理。 –百度

通常情況下, API 閘道器要做很多工作,它作為一個系統的後端總入口,承載著所有服務的組合路由轉換等工作,除此之外,我們一般也會把安全,限流,快取,日誌,監控,重試,熔斷等放到 API 閘道器來做,那麼可以試想在高併發的情況下,這裡可能會出現一個效能瓶頸。

另外,如果沒有開源專案的支撐前提下,自己來做這樣一套東西,是非常大的一個工作量,而且還要做 API 閘道器本身的高可用等,如果一旦做不好,有可能最先掛掉的不是你的服務節點,而就是這個API閘道器。

這個時候,通常我們會去找一些開源的 API 閘道器專案,目前社群關於 API Gataway 的專案有以下這些:

  • Tyk:Tyk是一個開放原始碼的API閘道器,它是快速、可擴充套件和現代的。Tyk提供了一個API管理平臺,其中包括API閘道器、API分析、開發人員門戶和API管理面板。Try 是一個基於Go實現的閘道器服務。
  • Kong:Kong是一個可擴充套件的開放原始碼API Layer(也稱為API閘道器或API中介軟體)。Kong 在任何RESTful API的前面執行,透過外掛擴充套件,它提供了超越核心平臺的額外功能和服務。
  • Orange:和Kong類似也是基於OpenResty的一個API閘道器程式,是由國人開發的,學姐也是貢獻者之一。
  • Netflix zuul:Zuul是一種提供動態路由、監視、彈性、安全性等功能的邊緣服務。Zuul是Netflix出品的一個基於JVM路由和服務端的負載均衡器。
  • apiaxle: Nodejs 實現的一個 API 閘道器。
  • api-umbrella: Ruby 實現的一個 API 閘道器。
  • ocelot: .Net平臺下實現的一個API閘道器,其中我們的張隊(張善友)也參與了此專案的開發。

本系列單從Net Core入手,所以我們只討論Ocelot閘道器的作用和使用。

什麼是Ocelot:

Ocelot是一個用.NET Core實現並且開源的API閘道器,它功能強大,包括了:路由、請求聚合、服務發現、認證、鑒權、限流熔斷、並內建了負載均衡器與Service Fabric、Consul整合,並且這些功能都只需要簡單的配置即可完成        。

簡單的說,Ocelot是一堆的asp.net middleware組成的一個管道。當有收到請求後會用一個RequestBuilder去建立一個HttpRequestMessage傳送(或請求)到下游伺服器,等下游伺服器傳回Response後再由一個Middleware將HttpRequestMessage對映到當前請求Context中的Response上,並傳回給請求者。

這裡從張隊這邊借用一張圖,如想瞭解更多有關Ocelot的原理剖析,可在張隊的部落格中瞭解到更多:https://www.cnblogs.com/shanyou/p/7787183.html。當然,筆者也推薦檢視官方原始API檔案:https://ocelot.readthedocs.io

Ocelot的使用結構圖:

用一臺web api來作為Ocelot的宿主,在這裡有一個json配置檔案,裡面設定了所有對當前這個閘道器的配置。它會接收所有的客戶端請求,並路由到對應的下游伺服器進行處理,再將請求結果傳回。而這個上下游請求的對應關係也被稱之為路由。

一起來看看官方給出的基礎結構圖:

在公共網路上,無論是客戶端a還是客戶端b,還是其他任何智慧裝置,透過http/https進行訪問,都將經過Ocelot進行一次過濾,而這些過濾將透過Ocelot的配置檔案及其簡單的配置便可實現下游路由轉發,然後,在透過指定的下游路由配置,請求到對映的指定服務節點上。當然,這是最只是Ocelot基礎的路由轉發。

結合IdentityServer:

私有網路中,不做驗證的暢通訪問是極不可取的、非常危險的,因此,Ocelot為我們提供了私有網路身份驗證解決方案,我們可以透過跟IdentityServer進行結合,實現私有網路身份驗證,當閘道器需要認證資訊的時候會與IdentityServer伺服器進行互動來完成。

閘道器的叢集:

只有一個閘道器是很非常危險的,也就是我們通常所講的單點,一旦只要它掛了,所有的服務全部掛掉,這顯然無法達到高可用的目的,所以我們也可以部署多臺閘道器,當然,這個時候在多臺閘道器前,你還需要一臺負載均衡器,用於平衡請求到閘道器的負載的平衡。

Consul服務發現:

在Ocelot已經支援簡單的負載功能,也就是當下游服務存在多個結點的時候,Ocelot能夠承擔起負載均衡的作用。但是它不提供健康檢查,服務的註冊也只能透過手動在配置檔案裡面新增完成,這不夠靈活並且在一定程度下會有風險,這個時候,我們就可以用Consul來做服務發現,它能與Ocelot完美的結合。

Ocelot的使用:

在ASP.NET Core中整合Ocelot閘道器

既然Ocelot是透過Asp.net中介軟體進行閘道器管理,那麼我們肯定就需要一個Asp.net作為宿主,為了演示DEMO,筆者建立了三個模板為Web API的Asp.net core專案,在其中一個asp.net core裡透過nuget即可完成安裝和整合Ocelot,或者命令列dotnet add package Ocelot以及透過vs2017 package manager新增Ocelot nuget取用都可以,甚至你還可以跟筆者一樣喜歡全家桶系列(VS固然非常強大,甚至宇宙第一,但筆者更喜歡三大平臺都一模一樣的Jetbrains全家桶),用Rider的Nuget管理來安裝Ocelot也可以。

我們把這個閘道器專案取名為ApiGatway,然後在這個專案的Startup中新增依賴註入和中介軟體,即可完成Ocelot安裝和註入

新增配置

我們需要新增一個.json的檔案用來新增Ocelot的配置,以下是最基本的配置資訊。

要特別註意一下BaseUrl是我們外部暴露的Url,比如我們的Ocelot執行在http://127.0.0.1的一個地址上(或一個埠上),但是前面有一個Nginx系結了域名https://api.mybusiness.com,那這裡我們的BaseUrl就應該是 https://api.mybusiness.com。

將配置檔案加入ASP.NET Core Configuration

我們需要透過IWebHostBuilder將我們新增的json檔案新增進Asp.net Core中

Ocelot的功能配置介紹

透過配置檔案可以完成對Ocelot的功能配置:路由、服務聚合、服務發現、認證、鑒權、限流、熔斷、快取、Header頭傳遞等。在配置檔案中包含兩個根節點:ReRoutes和GlobalConfiguration。ReRoutes是一個陣列,其中的每一個元素代表了一個路由,我們可以針對每一個路由進行以上功能配置。下麵是一個較完整的配置檔案,根據筆者的理解,並加上了詳細的註釋,方便初學者理解。官方路徑戳這兒:https://ocelot.readthedocs.io/en/latest/features/configuration.html

{

“ReRoutes”: [

// 路由規則配置節點,陣列形式

// 可配置多個路由協議和規則,實現路由、服務聚合、服務發現、認證、鑒權、限流、熔斷、快取、Header頭傳遞等

{

/*

下游服務配置配置,閘道器出口,具體指向的伺服器

/api/values – 使用限定規則的方式配置下游PATH規則

/{url} – 使用泛型(萬用)規則方式配置下游PATH規則

*/

“DownstreamPathTemplate”: “/{url}”,

“DownstreamScheme”: “http”,

“DownstreamHostAndPorts”: [

/*

下游主機資訊

可以配置多個主機資訊,已提供Ocelot路由負載均衡樣式,需配合LoadBalancer節點進行路由負載均衡。

*/

{

“Host”: “127.0.0.1”,

“Port”: 5000

},

{

“Host”: “127.0.0.1”,

“Port”: 5001

}

],

/*

上游服務配置配置,請求和閘道器的入口。

/api/values – 使用限定規則的方式配置上游PATH規則

/{url} – 使用泛型(萬用)規則方式配置上游PATH規則

*/

“UpstreamPathTemplate”: “/{url}”,

// 上游支援的http請求方法

“UpstreamHttpMethod”: [

“Get”,

“Post”,

“Delete”,

“Update”

],

// 上游域名主機

// “UpstreamHost”: “domain.com”,

// 當前路由節點的優先順序

“Priority”: 99,

/*

路由負載均衡:

LeastConnection – 將請求發往最空閑的那個伺服器

RoundRobin – 輪流傳送

NoLoadBalance – 總是發往第一個請求或者是服務發現

*/

“LoadBalancer”: “LeastConnection”,

“Key”: “Route1”,

}

],

// 限流配置(請求限流)

// 對請求進行限流可以防止下游伺服器因為訪問過載而崩潰

“RateLimitOptions”: {

// ClientWhitelist – 白名單串列

“ClientWhitelist”: [],

// EnableRateLimiting – 是否啟用限流

“EnableRateLimiting”: true,

// Period – 統計時間段 1s, 5m, 1h, 1d

“Period”: “1s”,

// PeriodTimespan – 多少秒之後客戶端可以重試

“PeriodTimespan”: 1,

// Limit – 在統計時間段內允許的最大請求數量

“Limit”: 1,

// Http頭 X-Rate-Limit 和 Retry-After 是否禁用

// X-Rate-Limit: 為防止濫用,你應該考慮對您的 API 限流。 例如,您可以限制每個使用者 10 分鐘內最多呼叫 API 100 次。

// Retry-After: 響應的 HTTP 報頭指示所述使用者代理應該多長時間使一個後續請求之前等待

“DisableRateLimitHeaders”: false,

// QuotaExceededMessage – 當請求過載被截斷時傳回的訊息

“QuotaExceededMessage”: “Customize Tips!”,

// HttpStatusCode – 當請求過載被截斷時傳回的http status

“HttpStatusCode”: 999,

// ClientIdHeader – 用來識別客戶端的請求頭,預設是 ClientId

“ClientIdHeader”: “Test”

},

// 熔斷的意思是停止將請求轉發到下游服務。

// 當下游服務已經出現故障的時候再請求也是無功而返,並且增加下游伺服器和API閘道器的負擔。

// 這個功能是用的Pollly來實現的,我們只需要為路由做一些簡單配置即可

“QoSOptions”: {

// ExceptionsAllowedBeforeBreaking – 允許多少個異常請求

“ExceptionsAllowedBeforeBreaking”: 3,

// DurationOfBreak – 熔斷的時間,單位為秒

“DurationOfBreak”: 5,

// TimeoutValue – 如果下游請求的處理時間超過多少則自如將請求設定為超時

“TimeoutValue”: 5000

},

// 本地配置

// 可配置多個路由協議和規則,實現服務聚合、服務發現、認證、鑒權、限流、熔斷、快取、Header頭傳遞等

“GlobalConfiguration”: {

// 全域性基礎路徑

“BaseUrl”: “http://127.0.0.1:8080”

}

}

  • Downstream:是下游服務配置
  • UpStream:是上游服務配置
  • Aggregates:服務聚合配置
  • ServiceName, LoadBalancer, UseServiceDiscovery:配置服務發現
  • AuthenticationOptions:配置服務認證
  • RouteClaimsRequirement:配置Claims鑒權
  • RateLimitOptions:為限流配置
  • FileCacheOptions:快取配置
  • QosOptions:服務質量與熔斷
  • DownstreamHeaderTransform:頭資訊轉發

路由:

ocelot的主要功能是接收傳入的HTTP請求並將其轉發到下游服務,不過目前只支援HTTP請求的形式(將來可能是任何傳輸機制,暗中竊喜,默默關註和等待吧)。ocelot將一個請求路由到另一個請求描述為路由,為了讓任何請求在ocelot中工作,我們需要在配置中設定一個路由。

{    ReRoutes: [
]
}

下麵這個配置資訊就是將使用者的請求 /post/1 轉發到 localhost/api/post/1

  • DownstreamPathTemplate:下游服務的路徑模板,支援RESTful模板路徑。
  • DownstreamScheme:下游服務協議,支援http和https。
  • DownstreamHostAndPorts:下游服務的地址和集合,用於定義要將請求轉發到的任何下游服務的主機和埠,通常,這隻包含一個條目,但有時您可能希望向下游服務載入負載均衡。
  • UpstreamPathTemplate: 上游也就是使用者輸入的請求Url模板,支援RESTful模板路徑,或者設定一個空串列以允許其中任何一個方法。
  • UpstreamHttpMethod: 上游請求http方法,可使用陣列。

捕獲所有(通用模板):

通用模板即所有請求全部轉發,UpstreamPathTemplate與DownstreamPathTemplate設定為 “/{url}”

萬能模板的優先順序最低,只要有其它的路由模板,其它的路由模板則會優先生效。

上游Host:

上游Host也是路由用來判斷的條件之一,由客戶端訪問時的Host來進行區別。比如當a.jessetalk.cn/users/{userid}和b.jessetalk.cn/users/{userid}兩個請求的時候都可以進行區別對待。

優先順序:

對多個產生衝突的路由設定最佳化級,可透過priority屬性來定義我們希望路由與上游HttpRequest的匹配順序。

比如你有同樣兩個路由,當請求/goods/delete的時候,則下麵那個會生效,也就是說Prority數值越大的會被優先匹配。

請求聚合:

ocelot允許我們指定組成多個正常路由的聚合的重路由,並將它們的響應對映到一個下游物件中,通常情況下,當你有一個客戶機向一個伺服器發出多個請求時,它可能只是一個伺服器,這個特性允許您使用ocelot開始實現前端型別體系結構到後端,還可以減少對後端服務節點的重覆處理負載。

{

“ReRoutes”: [

{

“DownstreamPathTemplate”: “/”,

“UpstreamPathTemplate”: “/laura”,

“UpstreamHttpMethod”: [

“Get”

],

“DownstreamScheme”: “http”,

“DownstreamHostAndPorts”: [

{

“Host”: “localhost”,

“Port”: 51881

}

],

“Key”: “Laura”

},

{

“DownstreamPathTemplate”: “/”,

“UpstreamPathTemplate”: “/tom”,

“UpstreamHttpMethod”: [

“Get”

],

“DownstreamScheme”: “http”,

“DownstreamHostAndPorts”: [

{

“Host”: “localhost”,

“Port”: 51882

}

],

“Key”: “Tom”

}

],

“Aggregates”: [

{

“ReRouteKeys”: [

“Tom”,

“Laura”

],

“UpstreamPathTemplate”: “/”,

“Aggregator”: “FakeDefinedAggregator”

}

]

}

在Startup中新增AddSingletonDefinedAggregator來統一處理該路由聚合服務。

services.AddOcelot()
.AddTransientDefinedAggregator
();

而FakeDefinedAggregator需要繼承於IDefinedAggregator,這樣下游服務的統一處理將經過該Aggreage後傳回到Response中。

有了這個特性,您幾乎可以做任何您想做的事情,因為下游響應包含內容、頭和狀態程式碼,請註意,如果httpclient在向聚合中的重新路由發出請求時丟擲異常,那麼您將不會得到它的下游響應,但會得到任何成功的響應,如果它確實引發了異常,則會記錄此異常。

如果我們設定  /tom 和 /laura 控制器下的傳回值分別是  {“Age”: 19} 和 {“Age”: 25},那麼我們請求端將收到如下一個Response資訊

{Tom:{Age: 19},Laura:{Age: 25}}

需要註意的是:

  • 聚合服務目前只支援傳回json
  • 目前只支援Get方式請求下游服務
  • 任何下游的response essay-header並會被丟棄
  • 如果下游服務傳回404,聚合服務只是這個key的value為空,它不會傳回404
  • 做一些像 GraphQL的處理對下游服務傳回結果進行處理

關於GraphQL的功能支援,Ocelot並無原生自帶GraphQL動態API查詢陳述句,如果需要整合GraphQL,Ocelot官方有自帶示例:https://github.com/ThreeMammals/Ocelot/tree/develop/samples/OcelotGraphQL 

路由負載均衡

當下游服務有多個結點的時候,我們可以在DownstreamHostAndPorts中進行配置。

LoadBalancer將決定負載均衡的演演算法

  • LeastConnection – 將請求發往最空閑的那個伺服器
  • RoundRobin – 輪流傳送
  • NoLoadBalance – 總是發往第一個請求或者是服務發現 

限流

對請求進行限流可以防止下游伺服器因為訪問過載而崩潰,這個功能就是我們的張隊新增進去的,Ocelot支援上游請求的速率限制,這樣您的下游服務就不會過載。

  • ClientWihteList 白名單
  • EnableRateLimiting 是否啟用限流
  • Period 統計時間段:1s, 5m, 1h, 1d
  • PeroidTimeSpan 多少秒之後客戶端可以重試
  • Limit 在統計時間段內允許的最大請求數量

在 GlobalConfiguration下我們還可以進行以下配置

  • Http頭  X-Rate-Limit 和 Retry-After 是否禁用
  • QuotaExceedMessage 當請求過載被截斷時傳回的訊息
  • HttpStatusCode 當請求過載被截斷時傳回的http status
  • ClientIdHeader 用來識別客戶端的請求頭,預設是 ClientId

服務質量和熔斷

熔斷的意思是停止將請求轉發到下游服務。當下游服務已經出現故障的時候再請求也是功而返,並且增加下游伺服器和API閘道器的負擔。這個功能是用的Pollly來實現的,我們只需要為路由做一些簡單配置即可。關於Polly的使用,我會在下一個章節中介紹。

  • ExceptionsAllowedBeforeBreaking 允許多少個異常請求
  • DurationOfBreak 熔斷的時間,單位為秒
  • TimeoutValue 如果下游請求的處理時間超過多少則自如將請求設定為超時 

快取

Ocelot支援一些非常基本的快取功能,他是基於CacheManager實現的,當然,我們在使用的過程中,也需要安裝CacheManager這個lib包,然後透過Ocelot Cache manager擴充套件方法來新增CacheManager實現。

FileCacheOptions: { TtlSeconds: 15, Region: somename }

在這個例子中,ttl設定為15秒,那麼快取所存在的時長就只有15秒。當然,你也可以透過新增你自定義快取介面來註入自定義快取服務。

1 services.AddSingleton, MyCache>()

認證

如果我們需要對下游API進行認證以及鑒權服務的,則首先Ocelot 閘道器這裡需要新增認證服務。這和我們給一個單獨的API或者ASP.NET Core Mvc新增認證服務沒有什麼區別。

然後在ReRoutes的路由模板中的AuthenticationOptions進行配置,只需要我們的AuthenticationProviderKey一致即可。

簽權

我們透過認證中的AllowedScopes 拿到 claims之後,如果要進行許可權的鑒別需要新增以下配置。

RouteClaimsRequirement: {    UserType: registered}

當前請求背景關係的token中所帶的claims如果沒有 name=”UserType” 並且 value=”registered” 的話將無法訪問下游服務。

一個簡單的例子

上面我們簡單介紹了一下Ocelot的部分功能,要需完整功能介紹,可參考官方檔案進行https://ocelot.readthedocs.io,接下來筆者做了一個簡單的路由轉發的示例,來演示一下Ocelot基於http/https協議的強大而又簡單的功能。

基於上面介紹的三個專案,我們只介紹了作為閘道器能使用到的功能,另外我們還需要一個上游作為請求客戶端(當然,筆者更喜歡將客戶端做成一個Console控制檯,方便,快捷),一個下游作為服務端,專案名稱任意。

當下游服務端ASP.NET的預設模板被建立後,預設會建立一個ValueController,為了演示和獲取當前路由轉發的結果,我們只需要對其中一個介面稍作修改,設定預設啟動埠為5000。

而上游服務中,可以用HttpClient來模擬一個請求。

此處8080作為ApiGateway服務端,預設使用通用路由模板(上游和下游直接路由通用匹配{url}),程式碼不再貼出。啟動8080閘道器和5000服務端,透過Console控制檯直接訪問8080所配置(對映)出來的公開地址(實際就是5000上的api/values),可看到如下的結果。

如需檢視更多的demo示例和原始碼,可參考筆者的原始碼https://github.com/steveleeCN87/doteasy.rpc/tree/master/src/doteasy.rpc.demo

總結

Ocelot能實現的功能遠遠不止這些,更多內容可以參考Ocelot官方API或張隊的解說。

補個插曲

對了,關註DotEasy.Rpc小夥伴們,該框架已經更新到1.0.3,主要增加和修改了以下內容:

1. 介面註冊改用Autofac,實現統一批次介面註入,而非每次手動一個一個的去註入介面。

2. 透過代理生成,將原有的“兔子耳朵”取消,增加了客戶端非非同步遠端呼叫方式,避免每次呼叫均實現Task非阻塞方式來實現介面呼叫。

3. 透過代理生成,呼叫端將自動釋放介面實體資源,也就是IDisposable介面的實現,而客戶端不用實現。

4. 透過代理生成,如服務節點中不存在閘道器服務(非微服務,而是直接的RPC呼叫),客戶端對服務端的訪問可使用Token進行身份驗證。

現在的客戶端程式碼是越來越簡單,功能越來越豐富了:

下一步將研究和實現閘道器中http到rpc協議自動(或手動)轉換,也許會走彎路,喜歡的小夥伴請繼續關註,也將在下一篇介紹。

感謝閱讀!

贊(0)

分享創造快樂