寫在前面
API網關是系統內部服務暴露在外部的一個訪問入口,類似於代理服務器,就像一個公司的門衛承擔着尋址、限制進入、安全檢查、位置引導等工作,我們可以形象的用下圖來表示: 外部設備需要訪問內部系統服務時必須要通過我們的API Gateway,目的是為了隔離內部服務和外部訪問來做統一的認證授權,限流熔斷,請求聚合,負載均衡,日志記錄,監控預警等 通用功能,就像是我們系統的防火牆一樣,在任何外部請求訪問系統時都必須經過防火牆的驗證。

更多關於網關的信息請參考前面的一篇文章《API網關模式》
Ocelot是什么?
Ocelot是基於.NET Core實現的輕量級API網關,它包括的主要功能有:路由、請求聚合、服務發現、認證、授權、限流熔斷、並內置了LoadBanalce以及集成了Service Fabric、 Consul、Eureka等功能,這些功能只都只需要簡單的配置即可使用。目前騰訊財付通的API Gateway就是基於此做的實現(參考善友兄的這篇文章),下面是詳細信息以及Ocelot如何在微軟官方示例代碼庫 eShopContainers中的使用。
Ocelot在騰訊的使用:https://customers.microsoft.com/en-us/story/tencent-telecommunications-dotnetcore
微軟官方示例:https://github.com/dotnet-architecture/eShopOnContainers

Ocelot的實現機制
簡單的來說它是一堆的asp.net core middleware組成的pipeline,當它拿到請求之后會用一個request builder來構造一個HttpRequestMessage發到下游的真實服務器,等下游的服務 返回response之后再由一個middleware將它返回的HttpResponseMessage映射到HttpResponse上。

代碼示例
我在Github上創建了示例代碼庫僅供參考,我們可以使用下面的步驟來創建示例代碼:(示例代碼)
1.創建BookingApi: dotnet new -n BookingApi
2.創建PassengerApi: dotnet new -n PassengerApi
3.創建ApiGateway: dotnet new -n ApiGateway
4.添加BookingApi和PassengerApi的實現代碼
5.在ApiGateway項目中用Nuget安裝Ocelot依賴包
6.添加configuration.json的配置文件
7.配置路由響應規則
8.啟動服務並通過Api網關訪問服務
啟動BookingApi PassengerApi這兩個服務,我們可以看到他們分別提供了兩個接口


此時再啟動我們的Api Gateway項目,通過Gateway來訪問我們這兩個API

我們可以看到原本我們可以直接訪問的兩個API現在都可以通過Gateway來訪問了,那這一切是怎么做到的呢?
當我們通過Nuget安裝Ocelot的依賴之后,我們需要在項目中添加.json的配置文件,在此項目中我們配置文件命名為configuration.json,內容如下:
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/booking",
"UpstreamPathTemplate": "/api/getbooking",
"UpstreamHttpMethod": [ "Get" ],
"ReRouteIsCaseSensitive": false,
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8001
}
],
"Key": "booking",
"RateLimitOptions": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 15,
"Limit": 1
}
},
{
"DownstreamPathTemplate": "/api/booking/{pnr}",
"UpstreamPathTemplate": "/api/getbooking/{pnr}",
"UpstreamHttpMethod": [ "Get" ],
"ReRouteIsCaseSensitive": false,
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8001
}
]
},
{
"DownstreamPathTemplate": "/api/passenger",
"UpstreamPathTemplate": "/api/getpassenger",
"UpstreamHttpMethod": [ "Get" ],
"ReRouteIsCaseSensitive": false,
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8002
}
],
"Key": "passenger"
},
{
"DownstreamPathTemplate": "/api/passenger/{id}",
"UpstreamPathTemplate": "/api/getpassenger/{id}",
"UpstreamHttpMethod": [ "Get" ],
"ReRouteIsCaseSensitive": false,
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8002
}
]
}
],
"GlobalConfiguration": {
"BaseUrl": "https://localhost:5000"
},
"Aggregates": [
{
"ReRouteKeys": [
"booking",
"passenger"
],
"UpstreamPathTemplate": "/api/getbookingpassengerinfo"
}
]
}
API Gateway幫助我們做的事情是當有請求訪問網關的時候,我們經過認證授權等一系列操作確保此次訪問是被允許的之后便會轉發到它實際需要請求服務上去,請求結束之后再由Gateway統一將結果返回給客戶端,從模板中我們可以看到UpStreamPathTemplate實際上就是我們上游請求的地址,即網關公開給外部調用的地址(此服務名稱和地址我們可以根據需要隨便設置更多的是為了對外界隱藏我們真實的服務地址),而實際的調用地址便是DownStreamPathTemplate中給定的實際地址。為了方便大家理解此配置的含義,我查閱了官方資料,將我們上面用到的配置文件做了注解:(更齊全的參數請參考官方文檔)
{
"ReRoutes": [ //路由是API網關最基本也是最核心的功能、ReRoutes下就是由多個路由節點組成。
{
"DownstreamPathTemplate": "", //下游服務模板
"UpstreamPathTemplate": "", //上游服務模板
"UpstreamHttpMethod": [ "Get" ],//上游方法類型Get,Post,Put
"AddHeadersToRequest": {},//需要在轉發過程中添加到Header的內容
"FileCacheOptions": { //可以對下游請求結果進行緩存,主要依賴於CacheManager實現
"TtlSeconds": 10,
"Region": ""
},
"ReRouteIsCaseSensitive": false,//重寫路由是否區分大小寫
"ServiceName": "",//服務名稱
"DownstreamScheme": "http",//下游服務schema:http, https
"DownstreamHostAndPorts": [ //下游服務端口號和地址
{
"Host": "localhost",
"Port": 8001
}
],
"RateLimitOptions": { //限流設置
"ClientWhitelist": [], //客戶端白名單
"EnableRateLimiting": true,//是否啟用限流設置
"Period": "1s", //每次請求時間間隔
"PeriodTimespan": 15,//恢復的時間間隔
"Limit": 1 //請求數量
},
"QoSOptions": { //服務質量與熔斷,熔斷的意思是停止將請求轉發到下游服務。當下游服務已經出現故障的時候再請求也是無功而返,
並且增加下游服務器和API網關的負擔,這個功能是用的Polly來實現的,我們只需要為路由做一些簡單配置即可
"ExceptionsAllowedBeforeBreaking": 0, //允許多少個異常請求
"DurationOfBreak": 0, //熔斷的時間,單位為秒
"TimeoutValue": 0 //如果下游請求的處理時間超過多少則自如將請求設置為超時
}
}
],
"UseServiceDiscovery": false,//是否啟用服務發現
"Aggregates": [ //請求聚合
{
"ReRouteKeys": [ //設置需要聚合的路由key
"booking",
"passenger"
],
"UpstreamPathTemplate": "/api/getbookingpassengerinfo" //暴露給外部的聚合請求路徑
},
"GlobalConfiguration": { //全局配置節點
"BaseUrl": "https://localhost:5000" //網關基地址
}
}
示例代碼中我們在Aggregates節點下配置了路由聚合,它將兩個請求的結果combine到一起再返回給客戶端,當我們請求/api/getbookingpassengerinfo 時就會返回下面結果:

需要注意的是:
- 聚合服務目前只支持返回json
- 目前只支持Get方式請求下游服務
- 任何下游的response header並會被丟棄
- 如果下游服務返回404,聚合服務只是這個key的value為空,它不會返回404
有木有覺得這里的聚合很類似於GraphQL的功能,但實際上在Ocelot中並不打算實現GraphQL的功能,因為畢竟Ocelot的主要職責是實現網關的功能,聚合只是其中的一個feature,GraphQL提供了一個庫 graphql-dotnet ,我們可以用它來完成需要的功能,而在Ocelot中實現類似認證,授權等這樣它擅長的事情:
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/graphql",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "yourgraphqlhost.com",
"Port": 80
}
],
"UpstreamPathTemplate": "/graphql",
"DelegatingHandlers": [
"GraphQlDelegatingHandler"
]
}
]
}
官方也給出了示例:https://github.com/ThreeMammals/Ocelot/tree/develop/samples/OcelotGraphQL
此框架包含的內容比較多,在此並不一一解釋,下面將談談其他的幾個功能:
請求限流
大家注意到我們在上面例子中通過RateLimitOptions節點配置了限流的相關設置,目前我們配置的是1s鍾之內只允許對booking api訪問一次,否則的話便停止繼續轉發至下游服務,我們通過測試就會發現當在1s內多次訪問的時候,網關便會返回下面的信息:

負載均衡
當我們路由到的下游服務有多個結點的時候,我們可以在DownstreamHostAndPorts中進行配置負載
{
"DownstreamPathTemplate": "/api/posts/{postId}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "10.127.1.10",
"Port": 5001,
},
{
"Host": "10.127.1.11",
"Port": 5002,
}
],
"UpstreamPathTemplate": "/posts/{postId}",
"LoadBalancer": "LeastConnection",
"UpstreamHttpMethod": [ "Put", "Delete" ]
}
LoadBalancer將決定負載均衡的算法,目前支持下面三種方式
- LeastConnection – 將請求發往最空閑的那個服務器
- RoundRobin – 輪流發送
- NoLoadBalance – 總是發往第一個請求或者是服務發現
Prioirty優先級
當我們配置多個請求產生沖突的時候,通過路由設置訪問優化級
{
"UpstreamPathTemplate": "/goods/{catchAll}"
"Priority": 0
}
{
"UpstreamPathTemplate": "/goods/delete"
"Priority": 1
}
萬能模板
如果不希望對請求做任何的處理,則可以使用下面的萬能模板:(萬能模板的優先級最低,只要有其它的路由模板,其它的路由模板則會優先生效)
{
"DownstreamPathTemplate": "/{url}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80,
}
],
"UpstreamPathTemplate": "/{url}",
"UpstreamHttpMethod": [ "Get" ]
}
本篇內容就先介紹到這里,后面將會繼續探究Ocelot的內部實現。個人感覺現在.NET Core的生態越來越好,越來越多的開發人員開始嘗試.NET Core並創建了很多優秀的開源項目,從微軟這幾年的開源策略我們更多的感受到了微軟對於擁抱開源的決心,這也更加的讓我們有信心在Core平台上去構建越來越多的優秀項目。如果你對技術有熱情可以掃碼加入我們的微信群一起探討。
參考資料:
http://ocelot.readthedocs.io/en/latest/index.html
https://www.cnblogs.com/shanyou/p/7787183.html
Polly:
https://github.com/App-vNext/Polly
Consul:
https://github.com/hashicorp/consul
