.net core集成使用consul


  快速啟動一個consul集群可以參考:使用docker快速部署一個consul集群

  .net core集成使用consul是通過consul提供出來api接口來實現的,可以分成兩個部分來說明:配置集成、服務注冊。

  代碼比較多,已上傳到gitee上了,地址見:https://gitee.com/shanfeng1000/dotnetcore-demo/tree/master/Consul

  這是一個Demo項目,介紹.net core集成使用rabbitmq消息隊列,使用的.net core 3.1,這里簡單介紹:

 

  集成使用Consul的kv store(配置服務)

  .net core從consul的kv stroe中獲取配置很容易,但是實現kv store的熱加載有三種方式:

  方式一:阻塞式查詢(長輪詢)(推薦)

  有關阻塞式查詢的介紹,可以參考官網:https://www.consul.io/api-docs/features/blocking

  不過這里可以簡單的將阻塞式查詢理解為,consul為請求資源設置了一個index(可以理解為版本號),當資源更新時,consul會將版本號增加,而當使用api請求時可以攜帶一個index參數(起始版本)和wait參數(等待時間),當在wait時間內,存在index大於請求的index參數時(如果本就存在,那就不用等待了),會返回一個響應,響應會攜帶一個X-Consul-Index的Header,表示新的index,否則會在wait時間后超時返回響應,表示沒有新的index。

  在上面gitee的Demo中的AspNetCore.WebApi.Client中的Program中集成使用:  

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();

                ...
}).UseConsul(options
=> { options.Address = "http://192.168.209.128:18401"; options.Datacenter = "dc1"; options.Token = "245d0a09"; options.Prefix = "Root/Consul"; //使用阻塞式查詢實現熱更新 options.Mode = WatchMode.Poll; options.Interval = TimeSpan.FromMinutes(3); });

  現在consul中的kv store中Root/Consul節點下的所有kv都將被集成到.net core中的IConfiguration中去了,修改此節點下的任意kv都會自動更新IConfiguration,這是最簡單的一種熱更新方式。

  方式二:consul watch

  有關consul watch的介紹,可以參考官網:https://www.consul.io/docs/dynamic-app-config/watches

  consul watch是一種監聽機制,可以監聽kv、service、node等信息,當它們發生改變時,觸發某些handle,而這些handle包括執行shell腳本,發送http請求等。

  事實上,consul watch是基於阻塞式查詢的一種實現,但是遺憾的是,目前consul watch並沒有提供出來api接口出來注冊handle,所以在集成時,我們的項目需要提供出接口來公consul watch通知調用。

  在上面gitee的Demo中的AspNetCore.WebApi.Server中的Program中集成使用:  

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                ...

                webBuilder.ConfigureAppConfiguration(builder =>
                {
                    builder.AddConsul(options =>
                    {
                        options.Address = "http://192.168.209.128:18401";
                        options.Datacenter = "dc1";
                        options.Token = "245d0a09";
                        options.Prefix = "Root/Consul";

                        //使用consul-template或者watch來實現熱更新,需要在Configure中使用UseConsulWatch攔截更新配置請求
                        options.Mode = WatchMode.Watch;
                        options.ReloadName = "demo";
                    });
                });
            });

  這樣,項目啟動后,可以從consul的kv store中讀取配置,我們還需要提供一個回調的接口,可以在Startup的Configure方法時使用中間件UseConsulWatch來攔截:  

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseConsulWatch();
        
        ...
    }

  默認情況下,這個接口是POST請求,地址是http://host:port/consul?name=reloadName,這里的reloadName就是在Program中配置的那個ReloadName,表示收到更新后,只會重新加載指定name名稱的這個IConfigurationProvider,多個name之間使用逗號分隔。reloadName為空則表示重新加載所有的IConfigurationProvider,有了接口,接下來我們需要執行consul watch來啟動監聽:  

    # 執行consul watch
    consul watch -http-addr=192.168.209.128:18402 -datacenter=dc1 -prefix=Root/Consul -type=keyprefix "curl -X POST http://192.168.28.212:16001/consul -d ''"
    
    說明:
    -http-addr:表示連接的consul地址,默認是127.0.0.1:8500
    -datacenter:數據中心
    -type:表示監聽的類型,可選的有key, keyprefix, services, nodes, service, checks, event,這里是配置,所以選擇keyprefix,表示具有這個前綴的所有kv(key表示單個的kv)
    -prefix:kv的前綴
    curl -X POST http://192.168.28.212:16001/consul -d '':這是kv更新后需要執行的命令,可以是shell腳本,也可以是普通的命令,這里是調用項目接口來更新項目配置實現熱更新

  現在,更新kv store,會發現程序已經實現了熱加載

  方式三:consul-template

  有關consul-template相關的介紹可以參考:https://github.com/hashicorp/consul-template

  consul-template的下載地址:https://releases.hashicorp.com/consul-template/

  consul-template是基於consul watch的一套模板工具,可以這么理解consul-template,首先你需要提供一個模板,一般是一個ctmpl文件(語法類似於go template,具體可以參考這里)。接着,需要指定consul地址,及認證等信息,這樣,consul-template連接到consul,然后會根據模板中需要的參數對consul進行監聽,當對應的參數更新后會重新渲染模板。模板渲染更新后,我們可以使用這個新模板,一般我們先要將模板輸出成文件才能使用(一般是配置文件),所以我們還需要指定一個輸出文件路徑。輸出文件之后,我們還能需要執行一些腳本,命令等等。一般為了方便管理,這些配置都是寫在一個config.hcl文件中。

  .net core使用與上面consul watch一樣,只是監聽不在是使用consul watch,所以上面監聽部分換成consul-template可以寫成:

  首先創建一個config.hcl文件:  

  consul {
	address = "192.168.209.128:18402",
	token = "245d0a09"
  }
	
  template {
	contents = "{{ tree \"Root/Consul\" | explode | toJSONPretty }}",
	command = "curl -X POST http://192.168.28.212:16001/consul -d ''"
  }

  上面的contents及模板內容,這里的意思是以json格式輸出Root/Consul下的kv,當Root/Consul下的kv更新時,會重新渲染這個模板,當然,contents的內容也可以寫在一個ctmpl文件中,然后將contents換成source用於指定這個ctmpl文件的位置。command表示在模板渲染完成之后需要執行的命令,這里是使用curl發出一個http請求。更多配置說明參考:https://github.com/hashicorp/consul-template/blob/master/docs/configuration.md#configuration-file

  現在可以使用consul-template來啟動監控了:  

    # 啟動
    consul-template -config config.hcl -dry

    說明:
    -config:指定配置文件
    -dry:表示渲染后的模板輸出到標准輸出中,而不是輸出到一個文件中,如果沒有這個參數,則需要在上面config.hcl中的temlate節點中添加一個destination節點,用於指明模板渲染后的輸出文件路徑,這里因為沒使用到渲染后的文件,不需要使用文件,所以使用-dry輸出即可

  現在,更新kv store,會發現程序也已經實現了熱加載。

  注:

  consul-template是一個很靈活的模板工具,我們可以將consul、consul-template、nginx一起使用,組合成一套服務自動發現功能:nginx使用consul-template生成conf文件,而consul-template監控consul中已經注冊的服務,當有新實例加入或者已有實例退出時,consul-template會重新渲染生成新的conf文件,渲染完成后執行命令或者腳本讓nginx重新加載即可。這種方式常常用在分布式系統的場景,因為如果我們的項目采用了分布式部署,難道要為分布式中的每個節點都手動的用consul watch?特別是容器化的應用,consul watch就不太現實了,這個時候可以使用consul-template來實現:

  首先我們先創建一個ctmpl文件(如:demo.ctmpl):  

    #!/bin/bash
    
    #這行代碼用於添加kv的監控
    #{{ tree "Root/Consul" }}
    
    #輸出server服務的所有實例,並請求重新加載配置
    {{range service "server@dc1"}}    
    curl -X POST http://{{.Address}}:{{.Port}}/consul -d ''
    {{end}}

  接着創建一個demo.hcl文件:  

  consul {
    address = "192.168.209.128:18402",
    token = "245d0a09"
  }

  template {
    source = "./demo.ctmpl",
    destination = "demo.sh",
    command = "/bin/bash demo.sh"
  }

  然后啟動:  

    # 啟動,因為有輸出文件,不能使用-dry
    consul-template -config demo.hcl

  這是,當名稱為server的服務有多個實例時,kv 的更新會通知到所有的實例。(當然,這個例子中,配置加載采用這種方式也有缺點,因為新實例加入或者已有實例退出也會觸發熱加載)

  

  使用Consul作為服務注冊中心

  consul是一款很優秀的服務治理工具,在這個demo項目中,我簡單的做了一個封裝,方便集成使用,如AspNetCore.WebApi.Client的Startup中的服務注冊:

    public void ConfigureServices(IServiceCollection services)
    {
        ...

        //consul服務注冊
        services.AddConsulClient("consul", options =>
        {
            options.Address = "http://192.168.209.128:18401";
            options.Datacenter = "dc1";
            //options.Token = "token";//如果有token
        }).AddService(options =>
        {
            options.Host = ip;
            options.Port = port;
            options.Id = $"client_{ip}_{port}";
            options.Name = "client";
            options.Tags = new[] { "client" };
            options.HealthCheckPath = "Health";
        });

        ...
    }

  通過AddConsulClient方法創建一個Consul的客戶端,通過AddService使用這個客戶端添加服務,其中服務的健康檢查支持簡單的http和grpc兩種方式,如果采用grpc方式,那么需要提供一個標准的grpc服務,官方給出的health.proto如下:  

    syntax = "proto3";
    package grpc.health.v1;
    
    message HealthCheckRequest {
      string service = 1;
    }
    
    message HealthCheckResponse {
      enum ServingStatus {
        UNKNOWN = 0;
        SERVING = 1;
        NOT_SERVING = 2;
        SERVICE_UNKNOWN = 3; 
      }
      ServingStatus status = 1;
    }
    
    service Health {
      rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
    
      rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
    }

  注:此文件是官方標准,不要做任何改動,否則可能導致健康檢查失敗

  Grpc方式的使用如AspNetCore.WebApi.Server的Startup中的服務注冊:  

    public void ConfigureServices(IServiceCollection services)
    {
        ...

        //consul服務注冊
        services.AddConsulClient(options =>
        {
            options.Address = "http://192.168.209.128:18405";
            options.Datacenter = "dc1";
            //options.Token = "token";//如果有token
        }).AddService(options =>
        {
            //http服務
            options.Host = ip;
            options.Port = port;
            options.Id = $"server_{ip}_{port}";
            options.Name = "server";
            options.Tags = new[] { "server" };
            options.HealthCheckPath = "Health";
        }).AddService(options =>
        {
            //grpc服務
            options.Host = ip;
            options.Port = grpcPort;
            options.Id = $"server_grpc_{ip}_{grpcPort}";
            options.Name = "server_grpc";
            options.Tags = new[] { "server" };
            //options.HealthCheckUrl = $"http://{ip}:{port}/Health";//使用http的健康檢測

            //使用grpc做健康檢查
            options.HealthCheckUseGrpc = true;
            options.HealthCheckUrl = $"{ip}:{grpcPort}";//依賴health.proto
        });

        ...
    }

  AspNetCore.WebApi.Server的Startup中注冊了兩個服務,一個用於提供http服務,一個用於提供grpc服務,添加的服務,最終使用IHostedService來完成注冊。

  服務之間的通信通常采用http方式和grpc方式,如AspNetCore.WebApi.Client使用http和grpc兩種方式調用AspNetCore.WebApi.Server服務,在AspNetCore.WebApi.Client的Startup中添加HttpClient和GrpcClient的客戶端:  

    public void ConfigureServices(IServiceCollection services)
    {
        ...

        //http client
        services.AddHttpClient("http", client =>
        {
            client.BaseAddress = new Uri("http://server");//server是Server的http服務注冊進Consul的服務名
        }).AddServiceDiscovery("consul", LoadBalancerMode.RoundRobin);//添加服務發現機制

        //grpc client
        services.AddGrpcClient<WebApiServer.WebApiServerClient>("grpc", options =>
        {
            options.Address = new Uri("http://server_grpc");//server_grpc是Server的grpc服務注冊進Consul的服務名
        })
        .AddServiceDiscoveryPolling(options =>//添加服務發現機制
        {
            options.Address = "http://192.168.209.128:18406";
            options.Datacenter = "dc1";
            //options.Token = "token";//如果有token
        });
        
        ...
    }

  AddHttpClient和AddGrpcClient就是你熟悉的那種調用方式,只是還添加了一個尾巴:AddServiceDiscovery和AddServiceDiscoveryPolling

  AddServiceDiscovery:添加服務發現機制,服務名即請求地址中的host部分(不能攜帶端口),通知指定使用的consul客戶端(如果是名稱,則是使用AddConsulClient添加的客戶端,也可以指定具體的consul信息),LoadBalancerMode是均衡模式,也就是說,當發起一個請求時,會將host部分作為服務名,使用指定的這個consul客戶端去獲取所有這個服務的實例,然后采用LoadBalancerMode指定的模式從這些實例中獲得一個可用的實例,然后轉而請求這個實例的資源。

  AddServiceDiscoveryPolling:作用同AddServiceDiscovery,只是不在是每次請求都是使用consul客戶端去獲取服務實例,而是從實例緩存去獲取可用的實例,這背后有一個定時器定時的去獲取服務實例,然后刷新實例緩存,這樣可以提高性能,但是不保證服務實例的可用性。

  調用方式只需像原來的HttpClient和GrpcClient的方式去調用就可以了,如AspNetCore.WebApi.Client的RemoteController中的使用:  

    /// <summary>
    /// 使用Http方式調用遠程接口
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    [HttpGet]
    public async Task<object> Http(string name)
    {
        var httpClientFactory = HttpContext.RequestServices.GetRequiredService<IHttpClientFactory>();
        var httpClient = httpClientFactory.CreateClient("http");//http是Startup中注冊的http client名稱
        var response = await httpClient.GetAsync($"/Remote?name={name}");
        return await response.Content.ReadAsStringAsync();
    }
    /// <summary>
    /// 使用Grpc方式調用遠程接口
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    [HttpGet]
    public async Task<object> Grpc(string name)
    {
        var grpcClientFactory = HttpContext.RequestServices.GetRequiredService<GrpcClientFactory>();
        var grpcClient = grpcClientFactory.CreateClient<WebApiServer.WebApiServerClient>("grpc");//grpc是Startup中注冊的grpc client名稱
        var data = await grpcClient.SayAsync(new DataRequest() { Name = name });
        return data.Message;
    }

 


免責聲明!

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



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