1. 程式人生 > >手動造輪子——為Ocelot整合Nacos註冊中心

手動造輪子——為Ocelot整合Nacos註冊中心

### 前言     近期在看部落格的時候或者在群裡看聊天的時候,發現很多都提到了Ocelot閘道器的問題。我之前也研究過一點,閘道器本身是一種通用的解決方案,主要的工作就是攔截請求統一處理,比如認證、授權、熔斷、限流、註冊發現、負載均衡等等。隨著服務化的不斷盛行,服務拆分,負載均衡等已成為當今軟體行業隨處可談的名詞了,因此註冊中心也隨之流行了起來。Ocelot作為閘道器自然可以整合許多註冊中心,官方文件給出了整合Eureka和Consul的解決方案,Eureka可能有的人不是很熟悉,它是Spring Cloud的核心元件之一,其功能就是服務註冊發現。隨著.Net Core的不斷成熟,不知道為啥Consul突然成了.Net Core註冊中心和配置中心的主要選擇,甚至可以說是首選了,可能是因為功能比較強大,而且是基於GO開發的。Nacos作為後起之秀,功能也非常強大。那天無意中翻了一下發現網上居然沒有Ocelot整合到Nacos註冊中心的元件,由於我個人非常喜歡通用解決方案,於是決定自己擴充套件一個Ocelot.Provider.Nacos,程式碼已經放到了我的GitHub上[https://github.com/softlgl/Ocelot.Provider.Nacos](https://github.com/softlgl/Ocelot.Provider.Nacos),有興趣的可自行查閱。 ### 概念介紹 #### Ocelot     Ocelot是一個用.NET Core實現並且開源的API閘道器,它具備了許多強大實用的功能,包括了:路由、請求聚合、服務發現、認證、鑑權、限流熔斷、並內建了負載均衡器與Service Fabric、Butterfly Tracing整合。它是由asp.net core middleware組成的一個管道。當獲取請求之後會用request builder來構造一個HttpRequestMessage轉發到下游的真實伺服器,等下游的服務返回response之後再由一個middleware將它返回的HttpResponseMessage對映到HttpResponse上。 + 官方文件地址 [https://ocelot.readthedocs.io/en/latest/](https://ocelot.readthedocs.io/en/latest/) + 官方GitHub地址 [https://github.com/ThreeMammals/Ocelot](https://github.com/ThreeMammals/Ocelot) #### Nacos     關於Nacos我之前的文章[搭建一套ASP.NET Core+Nacos+Spring Cloud Gateway專案](https://www.cnblogs.com/wucy/p/13230453.html)已經有過介紹了。Nacos是阿里巴巴開源的致力於服務發現、配置和管理微服務的框架。提供了一組簡單易用的特性集,幫助您快速實現動態服務發現、服務配置、服務元資料及流量管理。一般用到的最多的就是當做配置中心和註冊中心。 + 中文官網地址:[https://nacos.io/zh-cn/](https://nacos.io/zh-cn/) + 官方GitHub地址:[https://github.com/alibaba/nacos](https://github.com/alibaba/nacos) ### Ocelot.Provider.Nacos #### 開發環境 + 基於.Net Core 3.1,這個是必須的,因為最新穩定版的Ocelot是在.Net Core 3.1上構建的,而我恰好選擇的是這個版本的Ocelot + Ocelot選擇的是目前最新的穩定版本 v16.0.1 + Nacos訪問元件實用的是[nacos-sdk-csharp](https://github.com/catcherwong/nacos-sdk-csharp),具體引用的是 ``` ``` 它其實是有另一個nacos-sdk-csharp-unofficial.AspNetCore版本的這個是針對Asp.Net Core程式整合Nacos使用的,Ocelot也是基於Asp.Net Core搭建起來的,但是我沒有選用nacos-sdk-csharp-unofficial.AspNetCore,主要是因為雖然AspNetCore版本的整合起來更方便,但是為了讓它能更好的適配到Ocelot服務註冊和發現上,我需要自己改造一下原本的使用方式。 #### 整合到Ocelot 將Ocelot.Provider.Nacos的方式非常簡單基本上和Ocelot.Provider.Eureka和Ocelot.Provider.Consul是一致的,首先新建一個已經集成了Ocelot的Asp.Net Core專案,這裡就不演示如何搭建的了,如果有不熟悉的可以檢視我的demo示例[https://github.com/softlgl/Ocelot.Provider.Nacos/tree/master/demo/ApiGatewayDemo](https://github.com/softlgl/Ocelot.Provider.Nacos/tree/master/demo/ApiGatewayDemo),專案搭建好之後引入Ocelot.Provider.Nacos包 ```
``` 然後在ConfigureServices中添加註冊方法AddNacosDiscovery ```cs public void ConfigureServices(IServiceCollection services) { //註冊服務發現 services.AddOcelot().AddNacosDiscovery(); } ``` 程式碼上做這麼多就可以了,其他的主要工作就在配置上了,近期新版本的Ocelot相對於之前老版本有些地方改動還是非常大的,網上很多示例都是老版本的,所以參考官方文件搭建還是比較靠譜的。接下來我們開啟Ocelot的配置檔案配置註冊中心相關的 ```json { "Routes": [ { // 用於服務發現的名稱,也就是註冊到nacos上的名稱 "ServiceName": "productservice", "DownstreamScheme": "http", "DownstreamPathTemplate": "/productapi/{everything}", "UpstreamPathTemplate": "/productapi/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" }, // 使用服務發現 "UseServiceDiscovery": true } ], "GlobalConfiguration": { "ServiceDiscoveryProvider": { // 這裡是重點 "Type": "Nacos" } } } ``` 這裡只是為Ocelot配置使用配置中心,接下來我們要配置Nacos訪問地址相關的,開啟appsettings.json,當然你自定義的配置檔案也可以,只要程式可以載入得到 ```json "nacos": { "ServerAddresses": [ "http://localhost:8848" ], "DefaultTimeOut": 15000, "Namespace": "", "ListenInterval": 1000, // 網關注冊的服務名稱 "ServiceName": "apigateway" } ``` 具體如何配置保留了和nacos-sdk-csharp一致的方式,可以到nacos-sdk-csharp專案原始碼中去檢視,nacos-sdk-csharp文件不是特別詳細,但是原始碼註釋非常給力。所以直接去配置類原始碼裡檢視就好了。 在這之前如果你已經啟動了Nacos,然後就可以直接執行Ocelot閘道器專案,啟動完成後開啟Nacos如果出現如圖所示,說明已經註冊成功![](https://img2020.cnblogs.com/blog/2042116/202007/2042116-20200721111927110-1748447730.png)你也可以直接執行我提供的demo需要啟動ApiGatewayDemo和ProductApi兩個專案,一個是OcelotDemo一個是服務Demo,輸入閘道器地址和需要轉發的url後可展示如下資料![](https://img2020.cnblogs.com/blog/2042116/202007/2042116-20200721112353600-1473459341.png) ### 自定義擴充套件程式碼 程式碼基本上我是參考著Ocelot.Provider.Eureka和Ocelot.Provider.Consul相關程式碼寫的。其中入口類就一個是針對IOcelotBuilder的擴充套件類。 首先,將自定義的服務發現相關的服務註冊進來。只展示Ocelot適配相關的,我自己寫的關於服務註冊發現相關的就不做展示了,有興趣的可自行查閱 ```cs public static IOcelotBuilder AddNacosDiscovery(this IOcelotBuilder builder) { //添加註冊自定義的NacosDiscovery相關的服務 builder.Services.AddNacosDiscovery(builder.Configuration); //新增自定義服務發現代理 builder.Services.AddSingleton(NacosProviderFactory.Get); //根據Ocelot配置檔案相關內容設定處理服務發現相關 builder.Services.AddSingleton(NacosMiddlewareConfigurationProvider.Get); return builder; } ``` 其次,將自定義的註冊發現相關操作提供出來,其實主要就是獲取服務地址 ```cs public static class NacosProviderFactory { public static ServiceDiscoveryFinderDelegate Get = (provider, config, route) => { //Nacos相關服務類 var client = provider.GetService(); //判斷型別是否為nacos if (config.Type?.ToLower() == "nacos" && client != null) { //返回自定義服務提供操作,route.ServiceName是命中的Route的配置的服務發現的名稱 return new Nacos(route.ServiceName, client); } return null; }; } public class Nacos : IServiceDiscoveryProvider { private readonly INacosServerManager _client; private readonly string _serviceName; public Nacos(string serviceName, INacosServerManager client) { _client = client; _serviceName = serviceName; } public async Task> Get() { var services = new List(); var instances = await _client.GetServerAsync(_serviceName); if (instances != null && instances.Any()) { //將發現的地址組裝成Service集合即可 services.AddRange(instances.Select(i => new Service(i.InstanceId, new ServiceHostAndPort(i.Ip, i.Port), "", "", new List()))); } return await Task.FromResult(services); } } ``` 最後,根據Ocelot配置相關的資訊判斷是否啟動Nacos相關服務 ```cs public class NacosMiddlewareConfigurationProvider { public static OcelotMiddlewareConfigurationDelegate Get = builder => { var internalConfigRepo = builder.ApplicationServices.GetService(); var config = internalConfigRepo.Get(); var hostLifetime = builder.ApplicationServices.GetService(); //判斷服務註冊型別是否為nacos if (UsingNacosServiceDiscoveryProvider(config.Data)) { //啟動nacos相關服務 builder.UseNacosDiscovery(hostLifetime).GetAwaiter().GetResult(); } return Task.CompletedTask; }; private static bool UsingNacosServiceDiscoveryProvider(IInternalConfiguration configuration) { //判斷配置的服務發現型別是否為nacos return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "nacos"; } } ``` 涉及到的相關程式碼並不多,而且比較清晰,瞭解到相關規則後還是比較簡單的。 ### 開發中遇到困難 Ocelot.Provider.Nacos一共大概有十個類左右,整合到Ocleot本身相關類有四個吧,其他的類都是整合Nacos相關的。 + 首先我找錯程式碼了倉庫,剛開始我是順著Nuget包上的地址找到了[https://github.com/ThreeMammals/Ocelot.Provider.Eureka](https://github.com/ThreeMammals/Ocelot.Provider.Eureka),但是其實這是一個廢棄的老倉庫,它最後更改的日期是18年9月,我當時還很好奇,這麼長時間居然沒有改動過,但是當我仿著這個寫的時候遇到了轉發相關路由的問題,後來我又到了nuget上看了一下,發現Ocelot.Provider.Eureka還一直在更新,後來我想會不會把程式碼移植到了Ocelot倉庫中,果然不出所料在[https://github.com/ThreeMammals/Ocelot/tree/master/src/Ocelot/ServiceDiscovery](https://github.com/ThreeMammals/Ocelot/tree/master/src/Ocelot/ServiceDiscovery)發現了這個資料夾,後來仿著這個繼續改造。 + 後來寫的差不多的時候準備除錯輸入具體轉發路徑的時候一直報UnableToFindServiceDiscoveryProviderError:Unable to find service discovery provider for type: nacos,剛開始我一直沒有找到原因,明明我已經給IServiceDiscoveryProvider註冊了NacosDiscovery例項它卻還是說找不到,找了老長時間後來我想到了去Ocelot原始碼查到UnableToFindServiceDiscoveryProviderError的引用,最後在[ServiceDiscoveryProviderFactory類](https://github.com/ThreeMammals/Ocelot/blob/3439be89271bb37b170429d69ca74978c941432e/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs),看到了如下程式碼,Ocelot的規則就是註冊的Type型別要和自定義的IServiceDiscoveryProvider實現類名稱一致才可以 ```cs private Response GetServiceDiscoveryProvider(ServiceProviderConfiguration config, DownstreamRoute route) { if (config.Type?.ToLower() == "servicefabric") { var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, route.ServiceName); return new OkResponse(new ServiceFabricServiceDiscoveryProvider(sfConfig)); } if (_delegates != null) { var provider = _delegates?.Invoke(_provider, config, route); //1.自定義的IServiceDiscoveryProvider實現類名稱要和ServiceDiscoveryProvider裡的Type名稱一致 if (provider.GetType().Name.ToLower() == config.Type.ToLower()) { return new OkResponse(provider); } } //2.否則就會返回這異常資訊 return new ErrorResponse(new UnableToFindServiceDiscoveryProviderError($"Unable to find service discovery provider for type: {config.Type}")); } ``` 後來把我的實現類名稱改成了Nacos果然可以了,當時出這個異常的時候非常困惑,還好我沒放棄。 ### 總結     總之,通過本次擴充套件踩了很多坑,也加深了相應的瞭解,希望能為有自定義擴充套件註冊中心的同學提供一點幫助。Ocelot本身文件非常詳細,但是對於擴充套件這一塊沒有過多的介紹,只能通過檢視相關原始碼去了解。我覺得許多東西既然開源了,需要有自定義需求的可以通過檢視原始碼解決問題。雖然檢視原始碼其實並不容易,如果有針對性的話,相對來說還不是很複雜。重點是遇到問題你怎麼解決,能否扛得住這種無助的心情,只能靠自己,說不定堅持一下就找到思路了。