1. 程式人生 > >winserver的consul部署實踐與.net core客戶端使用(附demo源碼)

winserver的consul部署實踐與.net core客戶端使用(附demo源碼)

指令 his on() client模式 mvc lan -s enc .com

前言

隨著微服務興起,服務的管理顯得極其重要。都知道微服務就是”拆“,把臃腫的單塊應用,拆分成多個輕量級的服務,每個服務可以在短周期內重構、叠代、交付。隨著微服務的數量增多,因量變引起了質量,帶來新的問題其中一個是服務的管理問題。隨著業務發展微服務增多,可能技術負責人也無法清楚記著服務的部署情況,服務的健康也不能時刻關註著,因此服務治理系統的作用必不可少。

本篇文章的源碼:

demo:https://github.com/SkyChenSky/Consul.Demo

封裝:https://github.com/SkyChenSky/Sikiro.Tookits.Consul

Consul

Consul是一款簡單、易用、可伸縮性強的服務治理系統。

主要核心功能有:

  1. 服務發現
  2. 健康檢查
  3. 鍵值存儲
  4. 多數據中心

consul代理會每個一段時間對註冊中心的服務節點進行訪問,如果響應碼為“20X"認為是健康。

鍵值存儲可以認為是一個簡易的k/v數據庫,因此可以用此來存放配置信息。

服務發現

服務發現分服務註冊和服務查找。

服務註冊

將服務節點信息(地址+端口)添加(刪除)到註冊表,註冊表會記錄著服務的節點信息和狀態

服務查找

由其他的服務或者系統通過註冊表查詢到指定可用服務的節點信息。

服務發現的方式

服務發現的方式又分自主式和代理式。

自主式

由各個服務主動的將自己節點信息添加(刪除)到註冊中心。實現是通過統一封裝或者程序庫,由服務各個節點承擔服務發現的功能,與代理式相比由各自節點分擔的訪問壓力。

技術分享圖片

代理式

由一個系統(負載均衡系統)或者服務(API網關)來完成服務發現。因為由一個系統或者服務完成,隨著註冊服務的增加會帶來性能瓶頸,因此需要對此做集群。

技術分享圖片

Consul模式

Consul有兩種模式,Client和Server,無論各種模式都有一個consul agent。

Client模式

Client模式是一個輕量級的consul agent,只擁有註冊服務、健康檢查、轉發查詢等功能。

Server模式

Server模式與Client模式相比,除了擁有Client模式的功能還多出了數據存儲,leader選舉等。

官方建議Server模式應保證3-5個,而且應該是奇數,為什麽呢,因為少於3個無法保證高可用,多於5個又會給數據庫同步的一致性帶來壓力,而Client數量控制則沒有講究。

技術分享圖片

集群部署

下載consul https://www.consul.io/downloads.html

在服務器A,打開cmd,

consul agent -server -bootstrap-expect=1 -bind=192.168.20.80 -client=192.168.20.80 -join=192.168.20.80 -datacenter=dc1 -data-dir=data -ui -node=consul-80

在服務器B,打開cmd,

consul agent -server -bind=192.168.20.81 -client=192.168.20.81 -join=192.168.20.80 -data-dir=data -node=consul-81 

打開瀏覽器輸入http://192.168.20.80:8500

技術分享圖片

指令簡析

  • -server
    • consul以server模式啟動,不填則默認以client模式
  • -bootstrap-expect=1
    • 集群節點數,當集群節點數達到聲明數量才會進行數據同步
  • -bind=192.168.20.80
    • 當前consul服務綁定地址
  • -client=192.168.20.80
    • http接口綁定地址,客戶端調用需要
  • -join=192.168.20.80
    • 啟動服務時加入目標集群
  • -node=consul-81
    • 服務節點名稱
  • -ui
    • 啟動web管理後臺

客戶端實踐

安裝Consul

技術分享圖片

封裝擴展

只貼部分核心代碼,具體可以查看demo源碼。

註入ConsulClient

public static IServiceCollection AddConsul(this IServiceCollection serviceCollection, Action<ConsulConfiguration> optionAction)
        {
            _consulConfiguration = new ConsulConfiguration();
            optionAction(_consulConfiguration);

            var consulClient = new ConsulClient(x =>
                x.Address = new Uri(_consulConfiguration.Host));

            serviceCollection.AddSingleton(consulClient);

            return serviceCollection;
        }

把當前服務註冊到Consul

private static ConsulConfiguration _consulConfiguration;

        public static IApplicationBuilder UseConsul(this IApplicationBuilder app, IApplicationLifetime lifetime, Action<ServerConfiguration> optionAction)
        {
            var consulClient = app.ApplicationServices.GetService<ConsulClient>();
            if (consulClient == null)
                throw new Exception("please AddConsul first");

            var serverConfiguration = new ServerConfiguration();
            optionAction(serverConfiguration);

            var serviceRegistration = GetServiceRegistration(serverConfiguration);

            //添加註冊
            consulClient.Agent.ServiceRegister(serviceRegistration).Wait();

            //取消註冊
            lifetime.ApplicationStopping.Register(() =>
            {
                consulClient.Agent.ServiceDeregister(serviceRegistration.ID).Wait();
            });
            return app;
        }
private static Uri GetSelfUri(string uristring)
        {
            return new Uri(uristring);
        }

        private static AgentServiceRegistration GetServiceRegistration(ServerConfiguration serverConfiguration)
        {
            var localIp = GetSelfUri(serverConfiguration.SelfHost);

            var serviceRegistration = new AgentServiceRegistration
            {
                Check = new AgentServiceCheck//健康檢查
                {
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(60),
                    Interval = TimeSpan.FromSeconds(30),
                    HTTP = $"http://{localIp.Host}:{localIp.Port}/api/health",
                    Timeout = TimeSpan.FromSeconds(3)
                },
                ID = Guid.NewGuid().ToString("N"),
                Name = serverConfiguration.ServerName,
                Address = localIp.Host,
                Port = localIp.Port,
                Tags =
                    new[]
                    {
                        serverConfiguration.ServerName
                    }
            };

            return serviceRegistration;
        }

添加健康檢查接口

與上述封裝可以在同一個庫,避免每個web服務都要寫一個

[Route("api/[Controller]")]
    public class HealthController : Controller
    {
        [HttpGet]
        public OkResult Get()
        {
            return Ok();
        }
    }

在Startup.cs對Consul封裝進行調用

ConfigureServices

 public void ConfigureServices(IServiceCollection services)
 {
      services.AddOptions().AddConsul(option =>
      {
          option.WithHost(Configuration["ConsulConfiguration:Host"]);
      }).AddMvc();
 }

Configure

app.UseConsul(lifetime, option =>
{
    option.WithSelfHost(Configuration["SelfHost"]);
    option.WithServerName(Configuration["ConsulConfiguration:ServerName"]);
});

K/V擴展

只實現了put、get、delete,剩下可以自行按需添加

public static class ConsulKyExtensions
    {
        public static async Task<bool> KvPutAsync(this ConsulClient consulClient, string key, string value)
        {
            var kvPair = new KVPair(key)
            {
                Value = Encoding.UTF8.GetBytes(value)
            };
            var result = await consulClient.KV.Put(kvPair);

            if (result.StatusCode == HttpStatusCode.OK)
                return result.Response;

            return false;
        }

        public static bool KvPut(this ConsulClient consulClient, string key, string value)
        {
            var kvPair = new KVPair(key)
            {
                Value = Encoding.UTF8.GetBytes(value)
            };
            var result = consulClient.KV.Put(kvPair).ConfigureAwait(false).GetAwaiter().GetResult();

            if (result.StatusCode == HttpStatusCode.OK)
                return result.Response;

            return false;
        }

        public static async Task<string> KvGetAsync(this ConsulClient consulClient, string key)
        {
            var result = await consulClient.KV.Get(key);

            return Encoding.UTF8.GetString(result.Response.Value);
        }

        public static string KvGet(this ConsulClient consulClient, string key)
        {
            var result = consulClient.KV.Get(key).ConfigureAwait(false).GetAwaiter().GetResult();

            return Encoding.UTF8.GetString(result.Response.Value);
        }

        public static async Task<bool> KvDeleteAsync(this ConsulClient consulClient, string key)
        {
            var result = await consulClient.KV.Delete(key);

            if (result.StatusCode == HttpStatusCode.OK)
                return result.Response;

            return false;
        }

        public static bool KvDelete(this ConsulClient consulClient, string key)
        {
            var result = consulClient.KV.Delete(key).ConfigureAwait(false).GetAwaiter().GetResult();

            if (result.StatusCode == HttpStatusCode.OK)
                return result.Response;

            return false;
        }
    }

部署啟動

修改appsettings.json,填寫目標consul地址和本服務地址

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "ConsulConfiguration": {
    "Host": "http://192.168.20.80:8500",
    "ServerName": "ConsulWebDemo",
    "Id": "20E2CFBB-95C0-496A-B70F-11111111"
  },
  "SelfHost": "http://localhost:1495/"
}

啟動後,如果服務正常則可以顯示下圖效果。

技術分享圖片

winserver的consul部署實踐與.net core客戶端使用(附demo源碼)