首先,讓我們簡單了解下什么是API網關?
API網關是一個服務器,是系統的唯一入口。從面向對象設計的角度看,它與外觀模式類似。API網關封裝了系統內部架構,為每個客戶端提供一個定制的API。它可能還具有其它職責,如身份驗證、監控、負載均衡、緩存、請求分片與管理、靜態響應處理。
API網關方式的核心要點是,所有的客戶端和消費端都通過統一的網關接入微服務,在網關層處理所有的非業務功能。通常,網關也是提供REST/HTTP的訪問API。服務端通過API-GW注冊和管理服務。
其次,我們了解下Ocelot框架
Ocelot的目標是使用.NET運行微服務/面向服務架構,我們需要一個統一的入口進入我們的服務,提供監控、鑒權、負載均衡等機制,也可以通過編寫中間件的形式,來擴展Ocelot的功能。 Ocelot是一堆特定順序的中間件。
Ocelot框架內部集成了IdentityServer和Consul(服務注冊發現),還引入了Polly來處理進行故障處理,關於Polly,可以在這篇博客中了解更多《已被.NET基金會認可的彈性和瞬態故障處理庫Polly介紹》
Ocelot開源地址:https://github.com/TomPallister/Ocelot
接下來,我們就針對Ocelot的具體用法展開介紹。
這里使用的Ocelot版本為2.0,.Net Core版本2.0
1、搭建Asp.net Core WebApi項目,引用Ocelot.dll。
Nuget控制台,執行Ocelot安裝。
PM>Install-Package Ocelot
GET https://api.nuget.org/v3/registration3-gz-semver2/ocelot/index.json OK https://api.nuget.org/v3/registration3-gz-semver2/ocelot/index.json 104 毫秒 GET https://api.nuget.org/v3/registration3-gz-semver2/ocelot/page/1.5.0-unstable0134/2.0.0.json OK https://api.nuget.org/v3/registration3-gz-semver2/ocelot/page/1.5.0-unstable0134/2.0.0.json 108 毫秒 正在還原 J:\Demo\RichCodeBox\APIGatewayApp\APIGatewayApp.csproj 的包... GET https://api.nuget.org/v3-flatcontainer/ocelot/index.json OK https://api.nuget.org/v3-flatcontainer/ocelot/index.json 476 毫秒 GET https://api.nuget.org/v3-flatcontainer/ocelot/2.0.0/ocelot.2.0.0.nupkg OK https://api.nuget.org/v3-flatcontainer/ocelot/2.0.0/ocelot.2.0.0.nupkg 125 毫秒 GET https://api.nuget.org/v3-flatcontainer/identityserver4.accesstokenvalidation/index.json GET https://api.nuget.org/v3-flatcontainer/cachemanager.core/index.json GET https://api.nuget.org/v3-flatcontainer/cachemanager.microsoft.extensions.configuration/index.json GET https://api.nuget.org/v3-flatcontainer/cachemanager.microsoft.extensions.logging/index.json GET https://api.nuget.org/v3-flatcontainer/consul/index.json GET https://api.nuget.org/v3-flatcontainer/polly/index.json GET https://api.nuget.org/v3-flatcontainer/identityserver4/index.json OK https://api.nuget.org/v3-flatcontainer/identityserver4.accesstokenvalidation/index.json 133 毫秒 GET https://api.nuget.org/v3-flatcontainer/identityserver4.accesstokenvalidation/2.1.0/identityserver4.accesstokenvalidation.2.1.0.nupkg OK https://api.nuget.org/v3-flatcontainer/cachemanager.microsoft.extensions.logging/index.json 286 毫秒 OK https://api.nuget.org/v3-flatcontainer/polly/index.json 287 毫秒 OK https://api.nuget.org/v3-flatcontainer/identityserver4.accesstokenvalidation/2.1.0/identityserver4.accesstokenvalidation.2.1.0.nupkg 160 毫秒 GET https://api.nuget.org/v3-flatcontainer/cachemanager.microsoft.extensions.logging/1.1.1/cachemanager.microsoft.extensions.logging.1.1.1.nupkg
2、修改Startup程序。
/// <summary> /// /// </summary> /// <param name="environment"></param> public Startup(IHostingEnvironment environment) { var builder = new Microsoft.Extensions.Configuration.ConfigurationBuilder(); builder.SetBasePath(environment.ContentRootPath) .AddJsonFile("appsettings.json", false, reloadOnChange: true) .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", optional: false, reloadOnChange: true) .AddJsonFile("configuration.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); Configuration = builder.Build(); }
/// <summary> ///modified:配置 /// </summary> public IConfigurationRoot Configuration { get; } /// <summary> /// 配置服務 /// </summary> /// <param name="services"></param> public void ConfigureServices(IServiceCollection services) { Action<ConfigurationBuilderCachePart> settings = (x) => { x.WithMicrosoftLogging(log => { log.AddConsole(LogLevel.Debug); }).WithDictionaryHandle(); }; services.AddOcelot(Configuration, settings); //services.AddMvc(); } /// <summary> /// 配置Ocelot /// </summary> /// <param name="app"></param> /// <param name="env"></param> public async void Configure(IApplicationBuilder app, IHostingEnvironment env) { //if (env.IsDevelopment()) //{ // app.UseDeveloperExceptionPage(); //} await app.UseOcelot(); //app.UseMvc(); } /// <summary> /// 入口程序 /// </summary> /// <param name="args"></param> public static void Main(string[] args) { IWebHostBuilder builder = new WebHostBuilder(); builder.ConfigureServices(s => { s.AddSingleton(builder); }); builder.UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .UseApplicationInsights(); var host = builder.Build(); host.Run(); }
3、配置Ocelot。
我們新建一個名為configuration的json文件,配置如下:
{ "ReRoutes": [ { "DownstreamPathTemplate": "/api/values", "DownstreamScheme": "http", "DownstreamHost": "localhost", "DownstreamPort": 8801, "UpstreamPathTemplate": "/api/values", "UpstreamHttpMethod": [ "Get" ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 }, "HttpHandlerOptions": { "AllowAutoRedirect": false, "UseCookieContainer": false }, "AuthenticationOptions": { } }, { "DownstreamPathTemplate": "/api/product", "DownstreamScheme": "http", "DownstreamPort": 8802, "DownstreamHost": "localhost", "UpstreamPathTemplate": "/api/product", "UpstreamHttpMethod": [ "Get" ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 }, "AuthenticationOptions": { } } ], "GlobalConfiguration": { "RequestIdKey": "OcRequestId", "AdministrationPath": "/admin" } }
在這里,我們配置了兩個服務,端口分別為8801和8802的。
Ocelot支持負載均衡(提供輪詢、最少訪問)。Ocelot大部分功能,都可以通過中間件來完成,也可以實現和重寫中間件。
Ocelot原理非常簡單,這里的配置文件,體現了上游請求和下游服務間的映射關系,你可以理解為,上游是客戶端直接調用的URL ,下游,則是對應我們開發的服務。
4、新增兩個WebApi項目,分別為APIUserServiec和APIProductService。
API服務 | 端口(Port) |
APIUserServiec | 8801 |
APIProductService | 8802 |
解決方案如下:
5、配置VS啟動端口:
依次類推,分別設置端口。
6、啟動項目。
配置多個項目啟動。
F5啟動項目。
再通過API網關,訪問商品服務:http://localhost:5000/api/product。
常見問題:
首次在啟動API網關時,觸發以下錯誤。
Sequence contains no matching element
根據錯誤詳細信息,可知原因是由於系統調用AddIdentityServer方法時,觸發異常。
剛開始,懷疑是配置文件configuration.json文件配置導致的,Ocelot2.0版,采用官方配置仍然觸發該異常,由此排除這種可能。接下來,直接從github上克隆源代碼,查看。
找到觸發錯誤的地方,
private static void AddIdentityServer(this IServiceCollection services, IIdentityServerConfiguration identityServerConfiguration, IConfigurationRoot configurationRoot) { services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration); services.TryAddSingleton<IHashMatcher, HashMatcher>(); var identityServerBuilder = services .AddIdentityServer(o => { o.IssuerUri = "Ocelot"; }) .AddInMemoryApiResources(Resources(identityServerConfiguration)) .AddInMemoryClients(Client(identityServerConfiguration)) .AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>(); //todo - refactor a method so we know why this is happening var whb = services.First(x => x.ServiceType == typeof(IWebHostBuilder));//這個地方觸發了錯誤 var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance); var baseSchemeUrlAndPort = urlFinder.Find(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(o => { var adminPath = configurationRoot.GetValue("GlobalConfiguration:AdministrationPath", string.Empty); o.Authority = baseSchemeUrlAndPort + adminPath; o.ApiName = identityServerConfiguration.ApiName; o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; o.SupportedTokens = SupportedTokens.Both; o.ApiSecret = identityServerConfiguration.ApiSecret; }); //todo - refactor naming.. if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) { identityServerBuilder.AddDeveloperSigningCredential(); } else { //todo - refactor so calls method? var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); identityServerBuilder.AddSigningCredential(cert); }
var whb = services.First(x => x.ServiceType == typeof(IWebHostBuilder));
這就代碼觸發了錯誤,是由於表達式條件不成立,導致First發生異常,這就簡單了,我們修改Main函數,
把這句代碼:
var builder = new WebHostBuilder();
改成:
IWebHostBuilder builder = new WebHostBuilder();
這樣,就解決了問題,API網關啟動成功。另外,官方例子https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Program.cs中,寫法是正確的,大家自己寫的時候,注意把IWebHostBuilder換成var會引發錯誤。
這樣,使用Ocelot框架搭建API網關的工作已經完成,嘗試通過訪問API網關,來訪問下游的服務。
此實例源碼:https://gitee.com/lichaoqiang/RichCodeBox.git
歡迎大家一起研究探討,開啟你的微服務之路。