本篇僅做引導,內容較多,若是閱讀不方便,可使用電腦打開咱們的文檔官網進行閱讀。以下圖所示:html
文檔官網地址:https://docs.xin-lai.com/node
整體介紹git
基於Ocelot搭建一個簡單的微服務架構 github
定義API資源 web
定義身份資源算法
定義測試客服端 docker
Cap 發佈 shell
Cap 訂閱(接收) 數據庫
最後——附上整體代碼 編程
整體介紹
隨着業務需求的快速發展變化,需求不斷增加,迫切須要一種更加快速高效的軟件交付方式。微服務能夠彌補單體應用不足,是一種更加快速高效軟件架構風格。單體應用被分解成多個更小的服務,每一個服務有本身的獨立模塊,單獨部署,而後共同組成一個應用程序。把範圍限定到單個獨立業務模塊功能。分佈式部署在各臺服務器上。本篇咱們將介紹如何使用.NET Core打造本身的微服務架構。
注意:微服務架構不是萬能藥,本篇僅供參考和探討。對於大部分小項目來講,請不要爲了微服務而微服務。畢竟技術不是萬能的,技術是爲業務服務的。
微服務架構的好處
微服務架構的不足(這個時候就須要用到服務發現)
傳統模式
Ocelot(網關)模式
集成IdentityService(認證)
集成consul(服務發現)
基於Ocelot搭建一個簡單的微服務架構
Ocelot
Ocelot 是一個僅適用於 .Net Core 的網關組件。Ocelot
中間件使用很是簡單,難的點在於如何去配置。它的功能包括了:路由、請求聚合、服務發現、認證、鑑權、限流熔斷、並內置了負載均衡器等的集成,而這些功能都是經過配置實現。
Ocelot的開源地址:https://github.com/ThreeMammals/Ocelot
Ocelot官網地址:https://ocelot.readthedocs.io/en/latest/index.html
基本集成
添加Ocelot
新建一個 .Net core 2.2 web 項目(ApiGateway),添加如下Nuget包:
在項目根目錄添加ocelot.json,名字能夠自取。
前面說了,全部功能都是經過配置實現的,因此配置也相對複雜。配置有兩個部分。一組ReRoutes和一個GlobalConfiguration。ReRoutes是告訴Ocelot如何處理上游請求的對象。GlobalConfiguration顧名思義是全局配置,具體配置請查看官網。下面列舉簡單配置
{ "GlobalConfiguration": { //外部訪問路徑 "BaseUrl": "http://localhost:13000", //限速配置 "RateLimitOptions": { //白名單 "ClientWhitelist": [], "EnableRateLimiting": true, //限制時間段,例如1s,5m,1h,1d "Period": "1s", //重試等待的時間間隔(秒) "PeriodTimespan": 1, //限制 "Limit": 1, //自定義消息 "QuotaExceededMessage": "單位時間內請求次數超過限制!", "HttpStatusCode": 999 }, //熔斷配置 "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 5, //超時值(毫秒) "TimeoutValue": 5000 } }, "ReRoutes": [] }
配置文件初始化好以後,須要在Program.cs
文件中加載JSON配置,Ocelot支持根據環境變量使用配置文件。
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => new WebHostBuilder() .UseKestrel((context, opt) => { opt.AddServerHeader = false; ////從配置文件讀取配置 //opt.Configure(context.Configuration.GetSection("Kestrel")); }) .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; //根據環境變量加載不一樣的JSON配置 config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); //從環境變量添加配置 }) .UseIISIntegration() .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); //添加控制檯日誌,Docker環境下請務必啓用 logging.AddConsole(); //添加調試日誌 logging.AddDebug(); }) .UseStartup<Startup>(); }
而後在Startup.cs
文件ConfigureServices方法中註冊服務時使用AddOcelot(),Configure
方法中使用app.UseOcelot().Wait(); 這樣網關的配置就完成了。
services.AddOcelot(Configuration) app.UseOcelot().Wait();
添加測試API項目
新建兩個 .Net core 2.2 web項目(vs 自建的那種就OK),並使用Swagger來作接口說明。
Nuget 添加 Swashbuckle.AspNetCore 和
Microsoft.Extensions.PlatformAbstractions 實現Swagger ui,代碼以下
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSwaggerGen(options => { options.SwaggerDoc("SwaggerAPI1", new Info { Title = "API1", Version = "v1" }); var basePath = PlatformServices.Default.Application.ApplicationBasePath; var xmlPath = Path.Combine(basePath, "Services.Test1.xml"); options.IncludeXmlComments(xmlPath); }); //服務註冊 //services.Configure<ServiceRegistrationOptions> } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseSwagger(c => { c.RouteTemplate = "{documentName}/swagger.json"; }); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/SwaggerAPI1/swagger.json", "API1"); }); app.UseMvc(); }
項目.csproj文件中設置XML文檔輸出路徑
Services.Test1 和 Services.Test2
同樣的配置,略過。編譯啓動,頁面以下,接口配置完成。
配置項目的上游請求對象(ocelot.json)
"ReRoutes": [ //API1項目配置 { "UpstreamPathTemplate": "/gateway/1/{url}", "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put" ], "DownstreamPathTemplate": "/api1/{url}", "DownstreamScheme": "http", "ServiceName": "API1", "UseServiceDiscovery": true, "LoadBalancer": "RoundRobin", "DownstreamHostAndPorts": [ { "Host": "119.29.50.115", "Port": 80 }, { "Host": "localhost", "Port": 13001 } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 } //"AuthenticationOptions": { // "AuthenticationProviderKey": "Bearer", // "AllowedScopes": [ // ] //} }, //API2項目配置 { "UpstreamPathTemplate": "/gateway/2/{url}", "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put" ], "DownstreamPathTemplate": "/api2/{url}", "DownstreamScheme": "http", "ServiceName": "API2", "UseServiceDiscovery": true, "LoadBalancer": "RoundRobin", "DownstreamHostAndPorts": [ { "Host": "111.230.160.62", "Port": 80 }, { "Host": "localhost", "Port": 13002 } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 } //"AuthenticationOptions": { // "AuthenticationProviderKey": "Bearer", // "AllowedScopes": [ // ] //} }, ]
ReRoutes API對象模板配置節點解釋以下:
UpstreamPathTemplate |
上游路徑模板 |
UpstreamHttpMethod |
上游HTTP請求方法 |
DownstreamPathTemplate |
下游路徑模板 |
DownstreamScheme |
下游協議Https/Http |
DownstreamHostAndPorts |
下游主機和端口號,容許配置多個 |
UseServiceDiscovery |
是否使用服務發現(True/False) |
ServiceName |
服務名稱(結合服務發現使用) |
LoadBalancer |
指定一個負載均衡算法: RoundRobin:輪詢 LeastConnection:最少鏈接數 NoLoadBalancer:不適用負載均衡 |
LoadBalancerOptions |
負載均衡器配置 |
QoSOptions |
熔斷配置,在請求向下遊服務時使用斷路 |
AuthenticationOptions |
權限配置 |
啓動結果
啓動web
項目,web頁面報錯,但無妨,使用PostMan請求網關接口訪問api1/TestOnes成功。
聚合API文檔(SwaggerUI)
前面配置了網關接口上游,可是頁面Swagger沒有顯示,這節主要是整合SwaggerUI。
首先須要配置ApiGateway項目的Swagger,在配置文件配置上面兩個接口的SwaggerNames,代碼中遍歷添加到網關項目的SwaggerUI中,代碼以下
ConfigureServices
services.AddSwaggerGen(options => { options.SwaggerDoc(Configuration["Swagger:Name"], new Info { Title = Configuration["Swagger:Title"], Version = Configuration["Swagger:Version"] }); });
Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { var apis = Configuration["Apis:SwaggerNames"].Split(";").ToList(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc() .UseSwagger() .UseSwaggerUI(options => { apis.ToList().ForEach(key => { options.SwaggerEndpoint($"/{key}/swagger.json", key); }); options.DocumentTitle = "網關"; }); app.UseOcelot().Wait(); }
appsettings.json
"Swagger": { "Name": "ApiGateway", "Title": "網關服務", "Version": "v1" }, "Apis": { "SwaggerNames": "SwaggerAPI1;SwaggerAPI2" }
PS:SwaggerAPI1、SwaggerAPI2是前面兩個接口的SwaggerName,這裏須要對應上。
配置Swagger的上游請求對象(ocelot.json)
//swagger API1配置 { "DownstreamPathTemplate": "/SwaggerAPI1/swagger.json", "DownstreamScheme": "http", "UpstreamPathTemplate": "/SwaggerAPI1/swagger.json", "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ], "DownstreamHostAndPorts": [ { "Host": "119.29.50.115", "Port": 80 }, { "Host": "localhost", "Port": 13001 } ] }, //swagger API2配置 { "DownstreamPathTemplate": "/SwaggerAPI2/swagger.json", "DownstreamScheme": "http", "UpstreamPathTemplate": "/SwaggerAPI2/swagger.json", "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ], "DownstreamHostAndPorts": [ { "Host": "111.230.160.62", "Port": 80 }, { "Host": "localhost", "Port": 13002 } ] }
啓動結果
使用SwaggerUI整合了API1和API2的接口文檔。
IdentityServer 集成
官網文檔地址:http://docs.identityserver.io/en/latest/index.html
IdentityServer4是一個基於OpenID Connect和 OAuth 2.0的針對 ASP .NET Core 2.0的框架。
IdentityServer是將規範兼容的OpenID Connect和OAuth 2.0終結點添加到任意ASP .NET
Core應用程序的中間件。你構建包含登陸和註銷頁面的應用程序,IdentityServer中間件會向其添加必要的協議頭,以便客戶端應用程序可使用這些標準協議與其對話。
添加受權服務項目
新建 .Net core 2.2 web項目,添加如下Nuget包:
配置appsetting.json
配置測試環境下的客服端信息和Identity API
資源配置,具體配置須要按照本身的邏輯定義,這裏只是爲告終合我下面的IdentityServerConfig文件所定義,代碼以下,
爲告終合我下面的IdentityServerConfig文件所定義,代碼以下,
"IdentityServer": { "ApiName": "default-api", "ApiSecret": "secret", "Clients": [ { "ClientId": "client", "AllowedGrantTypes": [ "password" ], "ClientSecrets": [ { "Value": "def2edf7-5d42-4edc-a84a-30136c340e13" } ], "AllowedScopes": [ "default-api" ] }, { "ClientId": "demo", "ClientName": "MVC Client Demo", "AllowedGrantTypes": [ "hybrid", "client_credentials" ], "RequireConsent": "true", "ClientSecrets": [ { "Value": "def2edf7-5d42-4edc-a84a-30136c340e13" } ], "RedirectUris": [ "http://openidclientdemo.com:8001/signin-oidc" ], "PostLogoutRedirectUris": [ "http://openidclientdemo.com:8001/signout-callback-oidc" ], "AllowedScopes": [ "openid", "profile", "default-api" ], "AllowOfflineAccess": "true" } ] }
添加IdentityServerConfig類
IdentityServerConfig 類分爲三個方法:
定義API資源:
public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("default-api", "Default (all) API") { Description = "AllFunctionalityYouHaveInTheApplication", ApiSecrets= {new Secret("secret") } } }; }
定義身份資源:
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResources.Email(), new IdentityResources.Phone(), new IdentityResources.Address() }; } public static IEnumerable<IdentityResource> GetIdentityResources() { var customProfile = new IdentityResource( name: "custom.profile", displayName: "Custom profile", claimTypes: new[] { "name", "email", "status" }); return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), customProfile }; }
IdentityResource 具體屬性
Enabled
指示此資源是否已啓用且能夠請求。默認爲true。
Name
標識資源的惟一名稱。這是客戶端將用於受權請求中的scope參數的值。
DisplayName
顯示名稱。
Description
描述。
Required
默認爲false。(暫未深究理解)
Emphasize
默認爲false。(暫未深究理解)
ShowInDiscoveryDocument
指定此範圍是否顯示在發現文檔中。默認爲true。
UserClaims
應包含在身份令牌中的關聯用戶聲明類型的列表。
定義測試客服端
此處則是經過appsetting.json 文件獲取配置
public static IEnumerable<Client> GetClients(IConfiguration configuration) { var clients = new List<Client>(); foreach (var child in configuration.GetSection("IdentityServer:Clients").GetChildren()) { clients.Add(new Client { ClientId = child["ClientId"], ClientName = child["ClientName"], AllowedGrantTypes = child.GetSection("AllowedGrantTypes").GetChildren().Select(c => c.Value).ToArray(), RequireConsent = bool.Parse(child["RequireConsent"] ?? "false"), AllowOfflineAccess = bool.Parse(child["AllowOfflineAccess"] ?? "false"), ClientSecrets = child.GetSection("ClientSecrets").GetChildren().Select(secret => new Secret(secret["Value"].Sha256())).ToArray(), AllowedScopes = child.GetSection("AllowedScopes").GetChildren().Select(c => c.Value).ToArray(), RedirectUris = child.GetSection("RedirectUris").GetChildren().Select(c => c.Value).ToArray(), PostLogoutRedirectUris = child.GetSection("PostLogoutRedirectUris").GetChildren().Select(c => c.Value).ToArray(), }); } return clients; }
配置Startup
ConfigureServices
這裏只是用做測試,因此沒有在數據庫中讀取配置,而是在內存中獲取。相應的數據庫讀取方法也有說明。
public void ConfigureServices(IServiceCollection services) { //var connectionString = Configuration.GetConnectionString("Default"); //services.AddDbContext<MagicodesAdminContext>(options => options.UseSqlServer(connectionString)); //services.AddIdentity<AbpUsers, AbpRoles>() // .AddEntityFrameworkStores<MagicodesAdminContext>() // .AddDefaultTokenProviders(); services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryPersistedGrants() .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()) .AddInMemoryApiResources(IdentityServerConfig.GetApiResources()) .AddInMemoryClients(IdentityServerConfig.GetClients(Configuration)) //.AddAspNetIdentity<AbpUsers>() //從數據庫讀取配置等內容(clients, resources) //.AddConfigurationStore(options => //{ // options.ConfigureDbContext = b => // b.UseSqlServer(connectionString); //}) // this adds the operational data from DB (codes, tokens, consents) //.AddOperationalStore(options => //{ // options.ConfigureDbContext = b => // b.UseSqlServer(connectionString); // options.PersistedGrants.Name = "AbpPersistedGrants"; // //options.DeviceFlowCodes.Name = // // this enables automatic token cleanup. this is optional. // options.EnableTokenCleanup = true; //}); //.AddAspNetIdentity() //.AddAbpPersistedGrants<AdminDbContext>() //.AddAbpIdentityServer<User>(); ; }
Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); }
啓動結果
就這樣能夠啓動服務了,瀏覽器啓動會顯示以下頁面,由於沒有任何頁面啓動,所爲顯示爲404。
但無妨,咱們可使用PostMan 訪問:
http://localhost:13004/.well-known/openid-configuration
你會看到官方所謂的發現文檔。客戶端和API將使用它來下載必要的配置數據。到此爲止IdentityServer服務已經搭建成功!
首次啓動時,IdentityServer將爲您建立一個開發人員簽名密鑰,它是一個名爲的文件。您沒必要將該文件檢入源代碼管理中,若是該文件不存在,將從新建立該文件。tempkey.rsa
配置ApiGateway網關項目
在前面Ocelot章節中,配置了ocelot.json,這裏繼續修改ocelot.json文件,啓用權限認證
{ "UpstreamPathTemplate": "/gateway/1/{url}", "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put" ], "DownstreamPathTemplate": "/api1/{url}", "DownstreamScheme": "http", "ServiceName": "API1", "UseServiceDiscovery": true, "LoadBalancer": "RoundRobin", "DownstreamHostAndPorts": [ { "Host": "119.29.50.115", "Port": 80 }, { "Host": "localhost", "Port": 13001 } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 } //啓用權限認證 "AuthenticationOptions": { "AuthenticationProviderKey": "IdentityBearer", "AllowedScopes": [ ] } }
而後還須要在ApiGateway項目中修改appsetting.json文件,添加IdentityService服務配置。
"IdentityService": { "Uri": "http://localhost:13004",//認證服務IP "DefaultScheme": "IdentityBearer", "UseHttps": false, "ApiName": "default-api", "ApiSecret": "def2edf7-5d42-4edc-a84a-30136c340e13" }
接下來就是配置 ApiGateway項目 Startup文件了。
須要引入Nuget包:IdentityServer4.AccessTokenValidation
public void ConfigureServices(IServiceCollection services) { //Identity Server Bearer Tokens Action<IdentityServerAuthenticationOptions> isaOpt = option => { option.Authority = Configuration["IdentityService:Uri"]; option.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityService:UseHttps"]); option.ApiName = Configuration["IdentityService:ApiName"]; option.ApiSecret = Configuration["IdentityService:ApiSecret"]; option.SupportedTokens = SupportedTokens.Both; }; services.AddAuthentication().AddIdentityServerAuthentication(Configuration["IdentityService:DefaultScheme"], isaOpt); services .AddOcelot(Configuration) //啓用緩存 .AddCacheManager(x => { x.WithDictionaryHandle(); }) .AddPolly() services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); ; services.AddSwaggerGen(options => { options.SwaggerDoc(Configuration["Swagger:Name"], new Info { Title = Configuration["Swagger:Title"], Version = Configuration["Swagger:Version"] }); }); }
配置完成後啓用Service.Test一、Service.Test二、ApiGateway、IdentityService項目。使用SwaggerUI請求會提示401
Unauthorized,這個時候IdentityService就起到做用了。
使用PostMan去請求IdentityService獲取token
使用token訪問接口,數據返回正常
調用Ocelot管理API
經過IdentityServer 身份驗證來調用Ocelot 管理接口。
首先須要作的是引入相關的NuGet包:Install-Package Ocelot.Administration
修改 ApiGateway項目 Startup文件
添加代碼.AddAdministration(「/administration」, isaOpt);路徑名稱可自取。
public void ConfigureServices(IServiceCollection services) { //Identity Server Bearer Tokens Action<IdentityServerAuthenticationOptions> isaOpt = option => { option.Authority = Configuration["IdentityService:Uri"]; option.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityService:UseHttps"]); option.ApiName = Configuration["IdentityService:ApiName"]; option.ApiSecret = Configuration["IdentityService:ApiSecret"]; option.SupportedTokens = SupportedTokens.Both; }; services.AddAuthentication().AddIdentityServerAuthentication(Configuration["IdentityService:DefaultScheme"], isaOpt); services .AddOcelot(Configuration) //啓用緩存 .AddCacheManager(x => { x.WithDictionaryHandle(); }) .AddPolly() .AddAdministration("/administration", isaOpt); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); ; services.AddSwaggerGen(options => { options.SwaggerDoc(Configuration["Swagger:Name"], new Info { Title = Configuration["Swagger:Title"], Version = Configuration["Swagger:Version"] }); }); }
API方法
POST {adminPath} / connect / token
獲取token
請求的主體是表單數據,以下所示
client_id 設爲管理員
client_secret 設置爲設置管理服務時使用的任何內容。
scope 設爲管理員
grant_type 設置爲client_credentials
獲取{adminPath} /configuration
得到當前的Ocelot配置。
POST {adminPath} / configuration
這會覆蓋現有配置。
請求的主體是JSON,它與咱們用於在文件系統上設置Ocelot.json格式相同。
若是要使用此API,則運行Ocelot的進程必須具備寫入ocelot.json或ocelot.{environment}
.json所在磁盤的權限。這是由於Ocelot會在保存時覆蓋它們。
刪除{adminPath} / outputcache / {region}
清除全部緩存區域
Consul(服務發現)
Consul包含多個組件,可是做爲一個總體,提供服務發現和服務配置的工具。
主要特性:
這裏框架主要介紹服務發現和健康檢查。
本地部署
下載相應版本consul
軟件包,下載地址:https://www.consul.io/downloads.html,如下內容爲windows講解。承接上面的網關項目,整合Consul。
安裝
解壓完成,只有一個consul.exe,別慌,確實就只有一個文件。
管理員運行CMD ,CD 到consul 文件夾,直接運行 consul
命令,出現以下頁面,則配置成功
添加服務配置
添加服務註冊配置文件,在consul.exe同級目錄下添加config
(名字可自取)文件夾,在config
文件夾中建立service.json(名字可自取)文件,用來註冊服務和服務檢查配置。如圖所示:
配置service.json,代碼以下:
{ "services": [ { "id": "API1",//惟一標識 "name": "API1",//服務名稱 "tags": [ "API1" ],//服務標籤 "address": "172.0.0.1",//我隨便配的IP,注意配置服務的真實IP和port "port": 80 }, { "id": "API2", "name": "API2", "tags": [ "API2" ], "address": "172.0.0.1",//我隨便配的IP,注意配置服務的真實IP和port "port": 81 } ] }
這樣服務註冊配置就OK了,接下來使用配置啓動Consul,下面是幾種形式啓動consul,詳細的命令參數能夠移步到官方文檔查看。
開發模式啓動以下,在輸出窗口中能夠看到consul ui HTTP 啓動路徑爲
127.0.0.1:8500 ,註冊了API 和 API2 兩個服務。
瀏覽器訪問 127.0.0.1:8500 ,能夠看到Consul UI頁面
添加檢查配置
須要查看服務的運行狀態是否健康,就須要配置檢查。具體檢查配置移步官方文檔。
檢查定義有一下幾種:
腳本檢查:
{ "check": { "id": "mem-util", "name": "Memory utilization", "args": ["/usr/local/bin/check_mem.py", "-limit", "256MB"], "interval": "10s", "timeout": "1s" } }
HTTP檢查:
{ "check": { "id": "api", "name": "HTTP API on port 5000", "http": "https://localhost:5000/health", "tls_skip_verify": false, "method": "POST", "header": {"x-foo":["bar", "baz"]}, "interval": "10s", "timeout": "1s" } }
TCP檢查:
{ "check": { "id": "ssh", "name": "SSH TCP on port 22", "tcp": "localhost:22", "interval": "10s", "timeout": "1s" } }
TTL檢查:
{ "check": { "id": "web-app", "name": "Web App Status", "notes": "Web app does a curl internally every 10 seconds", "ttl": "30s" } }
Docker檢查:
{ "check": { "id": "mem-util", "name": "Memory utilization", "docker_container_id": "f972c95ebf0e", "shell": "/bin/bash", "args": ["/usr/local/bin/check_mem.py"], "interval": "10s" } }
gRPC檢查:
{ "check": { "id": "mem-util", "name": "Service health status", "grpc": "127.0.0.1:12345", "grpc_use_tls": true, "interval": "10s" } }
本地服務的別名檢查:
{ "check": { "id": "web-alias", "alias_service": "web" } }
我這邊簡單使用了TCP檢查, 繼續修改service.json文件,檢測 tcp爲
「172.0.0.1:80」的服務,修改成以下代碼:
{ "services": [ { "id": "API1",//惟一標識 "name": "API1",//服務名稱 "tags": [ "API1" ],//服務標籤 "address": "172.0.0.1",//我隨便配的IP,注意配置服務的真實IP和port "port": 80 }, { "id": "API2", "name": "API2", "tags": [ "API2" ], "address": "172.0.0.1",//我隨便配的IP,注意配置服務的真實IP和port "port": 81 } ], "check": [ { "id": "APICheck", "name": "APICheck", "tcp": "119.29.50.115:80", "interval": "10s", "timeout": "1s" } ] }
check
定義爲service同級節點則是爲全部服務使用同一個檢查規則,定義在services節點內則是具體爲某一個服務定義檢查規則
啓動以下圖,很明顯多了一個名叫APICheck 的代理。
啓動頁面也有不一樣,checks 爲2了,說明check
配置成功了。點擊某個服務進去能夠查看詳細信息
docker部署(騰訊雲)
前面說的是本地部署,如今說一下基於騰訊雲docker
部署。首先拉去docker鏡像建立服務。
Docker Hub(鏡像文件庫) 裏包含Consul
的鏡像文件,只須要在Docker建立服務使用鏡像就能夠了。
設置容器端口爲8500,服務端口爲80,經過Ingress進行路由轉發。
訪問服務外網,結果以下,配置成功
配置Ocelot 網關
首先修改前面的網關項目ApiGateway Startup.cs 文件裏的 ConfigureServices方法,添加
.AddConsul()方法代碼以下:
public void ConfigureServices(IServiceCollection services) { //Identity Server Bearer Tokens Action<IdentityServerAuthenticationOptions> isaOpt = option => { option.Authority = Configuration["IdentityService:Uri"]; option.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityService:UseHttps"]); option.ApiName = Configuration["IdentityService:ApiName"]; option.ApiSecret = Configuration["IdentityService:ApiSecret"]; option.SupportedTokens = SupportedTokens.Both; }; services.AddAuthentication().AddIdentityServerAuthentication(Configuration["IdentityService:DefaultScheme"], isaOpt); services .AddOcelot(Configuration) .AddConsul() //啓用緩存 .AddCacheManager(x => { x.WithDictionaryHandle(); }) .AddPolly() .AddAdministration("/administration", isaOpt); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); ; services.AddSwaggerGen(options => { options.SwaggerDoc(Configuration["Swagger:Name"], new Info { Title = Configuration["Swagger:Title"], Version = Configuration["Swagger:Version"] }); }); }
接下來配置ocelot.json 文件,在GlobalConfiguration
節點下添加服務發現提供程序配置
//服務發現提供程序 "ServiceDiscoveryProvider": { "Host": "111.230.118.59", "Port": 80, "Type": "PollConsul", "PollingInterval": 1000 }
項目上游配置添加ServiceName 和 UseServiceDiscovery屬性,代碼以下:
{ "UpstreamPathTemplate": "/gateway/2/{url}", "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put" ], "DownstreamPathTemplate": "/api2/{url}", "DownstreamScheme": "http", "ServiceName": "API2", "UseServiceDiscovery": true, "LoadBalancer": "RoundRobin", "DownstreamHostAndPorts": [ { "Host": "111.230.160.62", "Port": 80 }, { "Host": "localhost", "Port": 13002 } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 } "AuthenticationOptions": { "AuthenticationProviderKey": "IdentityBearer", "AllowedScopes": [ ] } }
啓動OcelotGateway,API001,API002項目,經過http://localhost:13000/gateway/1/values,和http://localhost:13000/gateway/2/values訪問;由於Ocelot配置了Consul的服務治理,因此能夠經過配置的服務名稱和GlobalConfiguratin的Consul
http
api接口查找到對應服務的地址,進行訪問,這些都是Ocelot幫咱們作,這點很容易證實,能夠修改Consul配置文件中服務的address爲錯誤IP,就會發現經過13000端口訪問不成功。
集成消息隊列——CAP
簡介
CAP 是一個基於 .NET Standard 的 C#
庫,它是一種處理分佈式事務的解決方案,一樣具備 EventBus
的功能,它具備輕量級、易使用、高性能等特色。
微服務系統的過程當中,一般須要使用事件來對各個服務進行集成,在這過程當中簡單的使用消息隊列並不能保證數據的最終一致性,
CAP
採用的是和當前數據庫集成的本地消息表的方案來解決在分佈式系統互相調用的各個環節可能出現的異常,它可以保證任何狀況下事件消息都是不會丟失的。
Github 地址:https://github.com/dotnetcore/CAP
支持消息隊列:
數據庫存儲:
環境準備
咱們以RabbitMQ 與Sql Server來說解。
首先咱們須要安裝RabbitMQ 服務,很簡單,官方下載最新的安裝包。
可是在安裝RabbitMQ
時會提示安裝Erlang,Erlang是一種通用的面向併發的編程語言,Erlang來編寫分佈式應用要簡單的多。RabbitMQ是用Erlang實現的一個高併發高可靠AMQP消息隊列服務器。
官方下載對應的Erlang 安裝程序,建議RabbitMQ和Erlang都安裝最新版本
安裝完成以後,會多瞭如下幾個程序,安裝包幫我生成了start、remove、stop等命令程序。咱們拿來直接用就能夠了,固然你也能夠配置環境變量,使用命令啓動。先運行start
程序運行起來。
.Net Core 集成 CAP
Nuget 包下載:
繼續修改測試項目Service.Test1項目,使用CodeFirst生成數據庫:
新建測試類Test:
public class Test { public int Id { get; set; } public string Name { get; set; } public string Title { get; set; } }
添加AppDbContext 數據庫上下文 文件,代碼以下:
public class AppDbContext:DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) base(options) { } public virtual DbSet<Test> Tests { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } }
配置數據庫鏈接字符串:
"ConnectionStrings": { "Default": "Server=(localdb)\\MSSQLLocalDB; Database=Service_test1; Trusted_Connection=True;" }
Program.cs 文件配置讀取appsettings.json文件。
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(); }) .UseStartup<Startup>();
Startup.cs 文件ConfigureServices添加數據訪問配置
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default"))); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
到這裏正常的CodeFirst
項目配置已經完成了,直接運行數據遷移命令就能夠建立數據庫了。
可是我這裏須要集成CAP,確定這樣是不行的。須要進行CAP的配置,繼續在ConfigureServices
添加以下代碼:
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default"))); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); Action<CapOptions> capOptions = option => { option.UseEntityFramework<AppDbContext>(); option.UseSqlServer(Configuration.GetConnectionString("Default")); option.UseRabbitMQ("localhost");//UseRabbitMQ 服務器地址配置,支持配置IP地址和密碼 option.UseDashboard();//CAP2.X版本之後官方提供了Dashboard頁面訪問。 if (Convert.ToBoolean(Configuration["Cap:UseConsul"])) { option.UseDiscovery(discovery => { discovery.DiscoveryServerHostName = Configuration["Cap:DiscoveryServerHostName"]; discovery.DiscoveryServerPort = Convert.ToInt32(Configuration["Cap:DiscoveryServerPort"]); discovery.CurrentNodeHostName = Configuration["Cap:CurrentNodeHostName"]; discovery.CurrentNodePort = Convert.ToInt32(Configuration["Cap:CurrentNodePort"]); discovery.NodeId = Convert.ToInt32(Configuration["Cap:NodeId"]); discovery.NodeName = Configuration["Cap:NodeName"]; discovery.MatchPath = Configuration["Cap:MatchPath"]; }); } }; services.AddCap(capOptions);
RabbitMQ 也是支持配置options
option.UseRabbitMQ(cfg => { cfg.HostName = Configuration["MQ:Host"]; cfg.VirtualHost = Configuration["MQ:VirtualHost"]; cfg.Port = Convert.ToInt32(Configuration["MQ:Port"]); cfg.UserName = Configuration["MQ:UserName"]; cfg.Password = Configuration["MQ:Password"]; });
CAP 內置集成了Consul
服務註冊,註冊的同時默認攜帶了簡況檢查,可是隻支持HTTP檢查,因此咱們須要在接口中定義health
路徑提供給檢查訪問。
在appsetting.json 文件中添加相應的配置節點:
"Cap": { "UseConsul": true,//是否開啓 "CurrentNodeHostName": "localhost",//當前節點IP "CurrentNodePort": 13001,//當前節點Port "DiscoveryServerHostName": "127.0.0.1",//發現服務主機IP "DiscoveryServerPort": 8500,//發現服務主機Port "NodeId": 1,//節點標識 "NodeName": "CAP_API1",//節點名稱 "MatchPath": "/api1/TestOnes"//健康檢查根路勁 最終的路徑爲api1/TestOnes/health }
進行數據遷移建立數據庫,表結構以下:
Cap 發佈
接下來就是去使用Cap 發佈了,修改Controller代碼
public class TestOnesController : ControllerBase { private readonly ICapPublisher _capBus; public TestOnesController(ICapPublisher capPublisher) { _capBus = capPublisher; } [HttpGet] public ActionResult<IEnumerable<string>> Get() { _capBus.Publish("services.test1.show.time", DateTime.Now); return new string[] { "TestOnes_value1", "TestOnes_value2" }; } //定義路由爲health提供給服務檢查使用 [HttpGet] [Route("health")] public ActionResult<string> Health() { return "Health!!!!!"; } }
由於啓用的Consul ,因此要按照前面說過的consul 教程來啓動consul
訪問http://127.0.0.1:8500,頁面以下
接下來啓動項目,仍是老樣子直接看到以下頁面。
可是咱們集成了CAP,因此能夠訪問呢http://localhost:13001/cap 訪問cap
Dashboard頁面查看詳細
這裏通常啓動的話發出的時不存在,也是由於前面有測試過,數據庫裏存在了。咱們調用api1/TestOnes方法
發出消息。
請求成功,在來看看數據庫。數據庫多了兩張表,以張是接收數據表,一張是發佈數據表。
再來看看裏面的數據,也是就是發佈的消息,由於以前請求過四次,我這邊就多了四條數據。
cap Dashboard也能看到一些統計和數據列表
再來看看consul 頁面,一個CAP_API1 的服務已經被註冊進來了
若是前面 MatchPath
路徑沒有配置對的話,就會出現下面的狀況,致使沒法經過健康檢查。
Cap 訂閱(接收)
使用API訂閱消息,爲了方便,使用同一個項目的另外一個接口實現訂閱
[Route("api1/[controller]")] [ApiController] public class ValuesController : ControllerBase { [HttpGet("Received")] [CapSubscribe("services.test1.show.time")]//配置發佈時填寫的Name public ActionResult<string> GetReceivedMessage(DateTime datetime) { Console.WriteLine("訂閱:"+datetime); return "訂閱:" + datetime; } }
這樣就OK了,可是若是你時在不一樣的項目,仍是須要像前面同樣配置CAP。
啓動項目請求一次CAP發佈接口,查看http://localhost:13001/cap
能夠看到接收的裏面有1條數據
訂閱列表中也有了一條數據
在來看數據庫也添加一條數據
最後——附上整體代碼
整個實踐代碼已託管到Github,具體以下所示:https://github.com/magicodes/Magicodes.Simple.Services