asp.net core microservices 架構之eureka服務發現


 

一 簡介                                                     

  微服務將需多的功能拆分為許多的輕量級的子應用,這些子應用相互調度。好處就是輕量級,完全符合了敏捷開發的精神。我們知道ut(單元測試),不僅僅提高我們的程序的健壯性,而且可以強制將類和方法的設計盡量的單一化。那么微服務也是這樣,敏捷對於軟件工程的意義就是快速開發,驗證市場需求,然后快速改進,從而適應市場節奏。什么東西要快速,就必須輕量級。大家知道一個應用的復雜程度,完全是和這個項目的功能和代碼數量掛鈎的,這是軟件自誕生就存在的問題,一個設計不好的軟件,最后會讓這個軟件更新和改進變的非常復雜,直至沒有人敢去碰這個應用,我一直認為這個代碼量和復雜程度掛鈎,困難程度是以指數的量增加的,而不僅僅是線性增加。這就需要一個好的設計去解決問題,一個微服務盡量的單一,與其他子應用幾乎沒有代碼上的關聯,所以可以快速出原型,快速開發,驗證市場,也可以快速砍掉一個項目,而不影響其他的應用。

  當然,因為微服務有他的局限性,所以也有它的壞處,比如一致性會打折扣,對於一致性的問題,我前幾篇文章已經做過探討。還有就是雖然軟件開發部署工作解脫了,但是您想,原來一個應用,現在三四個應用進行協調和通訊,對於這個分布式架構的要求就會高,如何把他們打散又要弱關聯在一起,那么eureka誕生了。asp.net core支持一個底層的庫,Microsoft.Extensions.Http.Polly,這個庫是表達策略,例如以流暢且線程安全的方式處理重試、斷路器、超時、Bulkhead 隔離和回退,就是防止雪崩,熔斷降級等特性。感興趣的可以看asp.net core 官方文檔: https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2#use-polly-based-handlers 。多說一句,asp.net core也有后台服務job的底層api,有興趣的可以試着做出一套asp.net core的job框架。甚至服務發現,配合zookeeper,也可以開發出一套屬於asp.net core的框架。

  言歸正傳,這里還需要注意的是一點是,一個微服務是可以和以前的比如面向傳統服務類型的應用共存的,如果公司內部可以提出一個應用作為微服務來設計,那么我恭喜你,從泥潭中跨出了一步,如果你們公司全部用微服務設計了,那么真的恭喜,完全跳出了泥潭。當然一些重量級的應用,因為必須使用分布式事務等等的特殊要求,還是可以存在,微服務還是需要根據實際情況來實施的。

二 eureka集成                                                           

  說到eureka前,我們需要明白一個術語:backing service。我把他翻譯為協助服務,或者幫助服務。主要意思就是對資源具有管理,丟失處理,連接和配置等功能的一個程序。那微服務資源自然就是我們各種各樣的服務,如圖所示:

當然除過eureka,還有etcd,Consul,Marathon,ZooKeeper可以供大家挑選。

使用docker運行eureka服務器端:

docker run -p 8081:8080 -d --name eureka \
-d netflixoss/eureka:1.3.1

運行成功后,如下圖所示:

讓我們用我們的Walt.Framework.OrderService項目定義一個api接口和將配置eureka客戶端,然后至今運行,就會直接將實例添加進eureka服務端:

設計api,我們直接看生成的api描述,使用swagger生成的api描述:

再安裝eureka客戶端,首先查找都有那些包:

nuget.exe list eureka

顯示結果,紅框中的包就是文檔中要求針對netcore的包:

 

客戶端需要引入包:

dotnet add Walt.TestMcroServices.Webapi.csproj package Steeltoe.Discovery.ClientCore

然后再注冊服務,這個大家都應該熟悉的不能再熟悉了,因為我們開發自定義的服務已經有三個了:

public class Startup {
    ...
    public IConfiguration Configuration { get; private set; }
    public Startup(...)
    {
      ...
    }
    public void ConfigureServices(IServiceCollection services)
    {
        // Add Steeltoe Discovery Client service
        services.AddDiscoveryClient(Configuration); //添加服務 // Add framework services.
        services.AddMvc();
        ...
    }
    public void Configure(IApplicationBuilder app, ...)
    {
        ...
        app.UseStaticFiles();
        app.UseMvc();

        // Use the Steeltoe Discovery Client service
        app.UseDiscoveryClient();
    //這里是將服務應用到http通道,我們知道這里處理的都是asp.net core 相關請求的http通道的一些事情,類似與asp.net的那幾個application事件。
}

這里多說一句關於owin的事情,上面代碼的中間件技術和OWIN完全不同,OWIN:開放 Web 接口,它定義了在管道中使用中間件來處理請求和相關響應的標准方法。我們看看asp.net core 的管道都干些什么?如圖所示:

 

官方說辭:

所以無論各個環節都是處理http上下文中的內容,web驅動監聽到http請求和發送請求的內容,就是request,和response,在這兩個之間就是我們的通道,不管是處理生成http內容時,還是身份驗證,session處理,都是這個通道之間的某個環節。

那owin作用是什么尼?就是定義這些中間環節的一些標准,所以任何web框架,只要實現了owin接口標准,兩個不同web框架,就都可以相互兼容。

言歸正准,繼續eureka的客戶配置, 配置文件:

{
  "Logging": {
    "LogLevel": {
      "Default": "trace",
      "System": "trace",
      "Microsoft": "trace",
      "Steeltoe": "Debug"
    },
    "KafkaLog":{
      "Prix":"這是我的自定義日志提供程序",
      "LogStoreTopic":"mylog-orderservice"
    }
  },
  "KafkaService":{
    "Properties":{
      "bootstrap.servers":"192.168.249.106:9092",
      "group.id":"group1"
    }
  },
  "zookeeperService":{
    "Connectstring":"192.168.249.106:2181",
    "SessionTimeout":12000000
  },
  "spring": {
    "application": {
      "name": "orderservice"
    }
  },
  "eureka": {
    "client": {
      "serviceUrl": "http://192.168.249.105:8080/eureka/v2/",
      "shouldFetchRegistry": false
    },
    "instance": {
      "ipAddress":"192.168.249.102",
      "preferIpAddress":true,
      "port": 802,
      "instanceId":"orderserviceinstance" } }
}

 

紅顏色的就是eureka需要使用的配直節。

所以在startup中的配置就需要把整個configuration傳進去,讓他自己查找,而不用精確找到configuration配直節后,再傳入服務構建,這個非常簡單,總共就兩行代碼。

  

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Steeltoe.Discovery.Client;
using Swashbuckle.AspNetCore.Swagger;


namespace Walt.TestMicroservices.OrderService
{
    public class Startup
    {
        public Startup(IConfiguration configuration
            ,IHostingEnvironment hostingEn
            , ILoggerFactory loggerFac )
        {
            Configuration = configuration;
            HostingEn = hostingEn;
            LoggerFac = loggerFac; 
        }
 
        public IConfiguration Configuration { get; }

        public IHostingEnvironment HostingEn { get; }

        public ILoggerFactory LoggerFac { get; set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(mvcOptions=>{
                
            }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        //     services.AddAuthorization();
        //     services.AddAuthentication("Bearer")
        //   .AddIdentityServerAuthentication(options =>
        //   {
        //       options.Authority = "http://localhost:64433";
        //       options.RequireHttpsMetadata = false;
        //       options.ApiName = "api1";
        //   });
            // Add Steeltoe Discovery Client service
            services.AddDiscoveryClient(Configuration);
            // Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
            });
            var log = LoggerFac.CreateLogger<Startup>();
            log.LogDebug("服務配置完成");
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {

            var log = LoggerFac.CreateLogger<Startup>();
            log.LogInformation("infomation");
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
            app.UseDiscoveryClient();
            app.UseSwagger();
            app.UseSwaggerUI(c =>
           {
               c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
               c.RoutePrefix = string.Empty;
           });
            app.UseMvc(routes => {
                        routes.MapRoute(
                            name: "default",
                            template: "api/{controller=Home}/{action=Index}/{id?}");
                    });


            app.UseAuthentication();
            log.LogDebug("通道配置完畢");
        }
    }
}

 

 

 

 運行:

 

 

我們看到注冊成功了,那看調用方:Walt.TestMicroServices.Webapi

startup類中注冊:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Walt.Framework.Service;
using Walt.Framework.Configuration;
using Walt.Framework.Service.Kafka;
using Steeltoe.Discovery.Client;
using System;
using Steeltoe.Common.Http.Discovery;

namespace Walt.TestMicroServoces.Webapi
{
    public class Startup
    {
        public Startup(IConfiguration configuration
            ,IHostingEnvironment hostingEn
            , ILoggerFactory loggerFac )
        {
            Configuration = configuration;
            HostingEn = hostingEn;
            LoggerFac = loggerFac; 
        }
 
        public IConfiguration Configuration { get; }

        public IHostingEnvironment HostingEn { get; }

        public ILoggerFactory LoggerFac { get; set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IKafkaService, KafkaService>();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddAuthorization();
            services.AddAuthentication("Bearer")
          .AddIdentityServerAuthentication(options =>
          {
              options.Authority = "http://localhost:64433";
              options.RequireHttpsMetadata = false;

              options.ApiName = "api1";
          });

            services.AddKafka(KafkaBuilder =>
            {
                var kafkaConfig = Configuration.GetSection("KafkaService");
                KafkaBuilder.AddConfiguration(kafkaConfig);
            });

            //services.AddSingleton<IOrderService, OrderService>();
            // Add Steeltoe Discovery Client service
            services.AddDiscoveryClient(Configuration);

            // Add Steeltoe handler to container
            services.AddTransient<DiscoveryHttpMessageHandler>();

            // Configure a HttpClient
            services.AddHttpClient<OrderService>(c =>
            {
                c.BaseAddress = new Uri("http://orderservice");
            })
             .AddHttpMessageHandler<DiscoveryHttpMessageHandler>() //這個就是攔截http請求,然后讓發現服務處理這個http請求,而不適用默認的http請求處理程序
             .AddTypedClient<IOrderService, OrderService>(); //將上面DiscoveryHttpMessageHandler這個處理程序生成httpclient然后注入給這個服務類,然后再將這個服務類加入DI
var log = LoggerFac.CreateLogger<Startup>(); log.LogDebug("服務配置完成"); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { var log= LoggerFac.CreateLogger<Startup>(); log.LogInformation("infomation"); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseMvc(); app.UseDiscoveryClient(); app.UseAuthentication(); log.LogDebug("通道配置完畢"); } } }

 

 

 

這里看配置文件:

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Debug",
      "Microsoft": "Debug"
    },
    "KafkaLog":{
      "Prix":"這是我的自定義日志提供程序",
      "LogStoreTopic":"mylog-webapi"
    }
  },
  "KafkaService":{
    "Properties":{
      "bootstrap.servers":"192.168.249.106:9092",
      "group.id":"group1"
    }
  },
  "zookeeperService":{
    "Connectstring":"192.168.249.106:2181",
    "SessionTimeout":12000000
  },
  "spring": {
    "application": {
      "name": "webapi"
    }
  },
  "eureka": {
    "client": {
      "serviceUrl": "http://192.168.249.105:8080/eureka/v2/",
      "shouldRegisterWithEureka": true
    },
    "instance": {
      "ipAddress":"192.168.249.102",
      "preferIpAddress":true,
      "port": 801,
      "instanceId":"webapiinstance"
    }
  }
}

 

看調用類,很簡單,因為asp.net core和eueka的集成已經在上一步驟中的startup中處理,這里直接使用:

using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Steeltoe.Common.Discovery;

namespace Walt.TestMicroServoces.Webapi
{
    public class OrderService:IOrderService
    {
        private IConfiguration _config;

        private HttpClient _httpClient;

        public OrderService(IConfiguration config
        ,HttpClient httpClient)
        {
            _config=config;
            _httpClient = httpClient;
        }  
        public Task<string> GetOrder()
        {
            var task=_httpClient.GetStringAsync("Order");
            return task;
        }
    }
}

調用:

首先看日志:

 

只要獲取到這個實例,就沒什么問題了。

頁面結果:

 注意:我在用nuget包的時候,總是獲取不到實例,不知道什么,后來沒辦法就用源碼:

直接就可以了,就算不可以,調式源碼,很快也就解決了,看來坑都怕源碼,尤其是這種還沒大規模使用和討論的小項目,所以我這次的代碼就把這個引用保留,大家下載后需要自己下載 Descover源碼,然后引用這個工程就ok了。

Discovery github地址:https://github.com/SteeltoeOSS/Discovery

 三 集成polly                                               

  polly 提供了微服務之間調用的容錯功能,無論是慢超市,還是失敗,對於微服務之間提供了很多策略,如下的策略:

Policy Premise Aka How does the policy mitigate?
Retry 
(policy family)
(quickstart ; deep)

Many faults are transient and may self-correct after a short delay.

重試

"Maybe it's just a blip" Allows configuring automatic retries.
Circuit-breaker
(policy family)
(quickstart ; deep)

When a system is seriously struggling, failing fast is better than making users/callers wait. 

失敗而不是調用等待

Protecting a faulting system from overload can help it recover.

"Stop doing it if it hurts" 

"Give that system a break"
Breaks the circuit (blocks executions) for a period, when faults exceed some pre-configured threshold.
Timeout
(quickstart ; deep)

Beyond a certain wait, a success result is unlikely.

超時

"Don't wait forever" Guarantees the caller won't have to wait beyond the timeout.
Bulkhead Isolation
(quickstart ; deep)

When a process faults, multiple failing calls backing up can easily swamp resource (eg threads/CPU) in a host.

A faulting downstream system can also cause 'backed-up' failing calls upstream.

Both risk a faulting process bringing down a wider system.

一個進程失敗或多個失敗的調用可以很容易的淹沒主機的資源。

所以需要隔離牆。

"One fault shouldn't sink the whole ship" Constrains the governed actions to a fixed-size resource pool, isolating their potential to affect others.
Cache
(quickstart ; deep)
Some proportion of requests may be similar. "You've asked that one before" Provides a response from cache if known. 

Stores responses automatically in cache, when first retrieved.
Fallback
(quickstart ; deep)

Things will still fail - plan what you will do when that happens.

某些將仍然失敗,在它發生的時候,你要有計划准備做一些事。

"Degrade gracefully" Defines an alternative value to be returned (or action to be executed) on failure.
PolicyWrap
(quickstart ; deep)

Different faults require different strategies; resilience means using a combination.

不同的失敗請求有不同的情況,所以可以對上面的策略有不同的結合處理。

"Defence in depth" Allows any of the above policies to be combined flexibly.

  每個策略大家有興趣試着做例子。

  代碼中開發非常簡單,提供了三種配置策略的方法,下面是其中之一:

 

var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
    .AddPolicyHandler(request => 
        request.Method == HttpMethod.Get ? timeout : longTimeout);

 

這個在官方文檔:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2#use-polly-based-handlers

講解的非常詳細。

還有就是polly的github地址:https://github.com/App-vNext/Polly

都有非常詳細的介紹。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM