構建自己的簡單微服務架構(開源)

雪雁發表於2019-05-22

前言               

本篇僅作引導,內容較多,如果閱讀不方便,可以使用電腦開啟我們的文件官網進行閱讀。如下圖所示:

文件官網地址:https://docs.xin-lai.com/


 

目錄

總體介紹

  • 微服務架構的好處
  • 微服務架構的不足(這個時候就需要用到服務發現)
  • 傳統模式 
  • Ocelot(閘道器)模式 
  • 整合IdentityService(認證)
  • 整合consul(服務發現)

基於Ocelot搭建一個簡單的微服務架構 

  • Ocelot 
  1. 基本整合
  2. 新增Ocelot 
  3. 新增測試API專案
  4. 配置專案的上游請求物件(ocelot.json) 
  5. 啟動結果 
  • 聚合API文件(SwaggerUI
  1. ConfigureServices 
  2. Configure
  3. appsettings.json 
  4. 配置Swagger的上游請求物件(ocelot.json) 
  5. 啟動結果 
  • IdentityServer 整合 
  1. 新增授權服務專案 
  2. 配置appsetting.json 
  3. 新增IdentityServerConfig類 

定義API資源 

定義身份資源

定義測試客服端 

  • 配置Startup 
  1. ConfigureServices 
  2. Configure 
  3. 啟動結果 
  4. 配置ApiGateway閘道器專案 
  5. 呼叫Ocelot管理API 
  6. API方法 
  • Consul(服務發現)
  1. 本地部署 
  2. 安裝 
  3. 新增服務配置 
  4. 新增檢查配置 
  5. docker部署(騰訊雲) 
  6. 配置Ocelot 閘道器 
  • 整合訊息佇列——CAP 
  1. 簡介 
  2. 環境準備 
  3. .Net Core 整合 CAP 

Cap 釋出 

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
  • Ocelot.Administration Ocelot支援在執行時通過經過身份驗證的HTTP
    API更改配置。這可以通過兩種方式進行身份驗證:使用Ocelot的內部IdentityServer(僅用於驗證對管理API的請求)或將管理API身份驗證掛鉤到您自己的IdentityServer中。
  • Ocelot.Cache.CacheManager CacheManager.Net擴充套件包
  • Ocelot.Provider.Polly Polly.NET擴充套件包

在專案根目錄新增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包:

  • IdentityServer4.AspNetIdentity
  • IdentityServer4.EntityFramework 使用資料儲存機制

 

 

配置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.Test1、Service.Test2、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(服務發現)

官網地址:https://www.consul.io/

Consul包含多個元件,但是作為一個整體,提供服務發現和服務配置的工具。

主要特性:

  • 服務發現
    元件記錄了分散式系統中所有服務的資訊,其它服務可以據此找到這些服務。
  • 健康檢查 Consul 客戶端可用提供任意數量的健康檢查。
  • Key/Value儲存 應用程式可用根據自己的需要使用 Consul 的層級的 Key/Value
    儲存。
  • 多資料中心
    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 agent -dev -config-dir=./config
  • 以服務方式啟動 consul agent -server -bootstrap-expect 2 -data-dir
    ./tmp/consul -node=n1 -bind=192.168.109.241 -ui-dir ./dist -dc=dc1
  • 以客戶端方式啟動 consul agent -data-dir ./tmp/consul -ui-dir ./dist
    -bind=192.168.109.204 -dc=dc1

開發模式啟動如下,在輸出視窗中可以看到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

支援訊息佇列:

  • Kafka
  • RabbitMQ
  • AzureServiceBus

資料庫儲存:

  • Sql Server
  • MySql
  • PostgreSQL
  • MongoDB

環境準備

我們以RabbitMQ 與Sql Server來講解。

首先我們需要安裝RabbitMQ 服務,很簡單,官方下載最新的安裝包。

 

 

但是在安裝RabbitMQ
時會提示安裝Erlang,Erlang是一種通用的面向併發的程式語言,Erlang來編寫分散式應用要簡單的多。RabbitMQ是用Erlang實現的一個高併發高可靠AMQP訊息佇列伺服器。

官方下載對應的Erlang 安裝程式,建議RabbitMQ和Erlang都安裝最新版本

 

安裝完成之後,會多了以下幾個程式,安裝包幫我生成了start、remove、stop等命令程式。我們拿來直接用就可以了,當然你也可以配置環境變數,使用命令啟動。先執行start
程式執行起來。

 

 

 

.Net Core 整合 CAP

Nuget 包下載:

  • DotNetCore.CAP 核心包
  • DotNetCore.CAP.RabbitMQ CAP RabbitMQ
  • DotNetCore.CAP.SqlServer CAP Sql Server 擴充套件包

繼續修改測試專案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



相關文章