.Net Core使用Consul+Ocelot搭建簡易微服務項目

時代在變,技術也在更新迭代。從傳統的單體應用架構到現在的分佈式集羣架構,在技術的學習上真的是一點都不能鬆懈。

網上關於微服務與Consul的話題太多了,我在這裏不做過多描述。

其實就是在微服務中我們可以利用Consul可以實現服務的發現、治理、健康檢查等...

用它先下載它:

我此番在windows下操作,打開下載的Consul所在文件夾,輸入 consul.exe agent -dev

Consul的默認啓動端口爲8500,如果能正常顯示頁面則啓動成功。

 新建解決方案,建立一個.Net Core MVC的項目和一個.Net Core WebApi的項目。 安裝NuGet包Consul

首先Api端服務實例啓動時需到Consul中進行服務註冊,Web Client直接與Consul進行連接,從Consul中拿到服務實例並配置策略及發送http請求等。

如圖所示:

 Consul每隔一段時間就會調用一次註冊的服務實例進行健康檢查。

在Api項目中新建一個IConfiguration的擴展方法:

public static void ConsulExtend(this IConfiguration configuration)
{
    ConsulClient client = new ConsulClient(m =>
    {
        m.Address = new Uri("http://localhost:8500/");
        m.Datacenter = "dc1";
    });
    //啓動的時候在consul中註冊實例服務
    //在consul中註冊的ip,port
    string ip = configuration["ip"];
    int port = int.Parse(configuration["port"]);
    int weight = string.IsNullOrWhiteSpace(configuration["weight"]) ? 1 : int.Parse(configuration["weight"]);
    client.Agent.ServiceRegister(new AgentServiceRegistration()
    {
        ID = "service" + Guid.NewGuid(),//唯一的
        Name = "MicroserviceAttempt",//組(服務)名稱
        Address = ip,
        Port = port,//不同的端口=>不同的實例
        Tags = new string[] { weight.ToString() },//標籤
        Check = new AgentServiceCheck()//服務健康檢查
        {
            Interval = TimeSpan.FromSeconds(12),//間隔12s一次 檢查
            HTTP = $"http://{ip}:{port}/Api/Health/Index",
            Timeout = TimeSpan.FromSeconds(5),//檢測等待時間
            DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(20)//失敗後多久移除
        }
    });
    Console.WriteLine($"{ip}:{port}--weight:{weight}");
}

心跳檢查的接口:

[ApiController]
[Route("api/[controller]/[action]")]
public class HealthController : Controller
{
    readonly IConfiguration _configuration;
    public HealthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }
    [HttpGet]
    public IActionResult Index()
    {
        //心跳,consul會每隔幾秒調一次
        Console.WriteLine($"{ _configuration["port"]} Invoke");
        return Ok();
    }
}

在Startup類中的Configure方法加入:

//啓動時註冊,且註冊一次
this.Configuration.ConsulExtend();

將api項目啓動(三個端口)

dotnet ServicesInstances.dll --urls="http://*:5726" --ip="127.0.0.1" --port=5726
dotnet ServicesInstances.dll --urls="http://*:5727" --ip="127.0.0.1" --port=5727
dotnet ServicesInstances.dll --urls="http://*:5728" --ip="127.0.0.1" --port=5728

接着是web端,新建控制器:

public class UserController : Controller
{readonly HttpSender _httpSender;
    public UserController(HttpSender httpSender)
    {
        _httpSender = httpSender;
    }
    //暫不考慮線程安全
    private static int index = 0;
    public async Task<IActionResult> Index()
    {
        #region nginx版 只知道nginx地址就行了
        //var str = await _httpSender.InvokeApi("http://localhost:8088/api/User/GetCustomerUser");
        #endregion

        #region consul
        //new一個consul實例
        ConsulClient client = new ConsulClient(m =>
        {
            new Uri("http://localhost:8500/");
            m.Datacenter = "dc1";
        });
        //與consul進行通信(連接),得到consul中所有的服務實例
        var response = client.Agent.Services().Result.Response;
        string url = "http://MicroserviceAttempt/api/User/GetCustomerUser";
        Uri uri = new Uri(url);
        string groupName = uri.Host;
        AgentService agentService = null;//服務實例
        var serviceDictionary = response.Where(m => m.Value.Service.Equals(groupName, StringComparison.OrdinalIgnoreCase)).ToArray();//找到的全部服務實例
        //{
        //    agentService = serviceDictionary[0].Value;
        //}
        {
            //輪詢策略=>達到負載均衡的目的
            agentService = serviceDictionary[index++ % 3].Value;
        }
        {
            //平均策略(隨機獲取索引--相對平均)=>達到負載均衡的目的
            agentService = serviceDictionary[new Random(index++).Next(0, serviceDictionary.Length)].Value;
        }
        {
            //權重策略,給不同的實例分配不同的壓力,註冊時提供權重
            List<KeyValuePair<string, AgentService>> keyValuePairs = new List<KeyValuePair<string, AgentService>>();
            foreach (var item in keyValuePairs)
            {
                int count = int.Parse(item.Value.Tags?[0]);//在服務註冊的時候給定權重數量
                for (int i = 0; i < count; i++)
                {
                    keyValuePairs.Add(item);
                }
            }
            agentService = keyValuePairs.ToArray()[new Random(index++).Next(0, keyValuePairs.Count())].Value;
        }
        url = $"{uri.Scheme}://{agentService.Address}:{agentService.Port}{uri.PathAndQuery}";
        string content = await _httpSender.InvokeApi(url);
        #endregion
        return View(JsonConvert.DeserializeObject<CustomerUser>(content));
    }
}
public class HttpSender
{
    public async Task<string> InvokeApi(string url)
    {
        using (HttpClient client = new HttpClient())
        {
            HttpRequestMessage message = new HttpRequestMessage();
            message.Method = HttpMethod.Get;
            message.RequestUri = new Uri(url);
            var result = client.SendAsync(message).Result;
            string content = result.Content.ReadAsStringAsync().Result;
            return content;
        }
    }
}

啓動web項目,訪問User-Index這個視圖,會輪詢不同的服務實例。

但是這樣做不好,客戶端都需要和Consul進行連接,拿到所有的服務實例,直接和服務實例進行交互,服務實例就暴露了--所以需要網關。

網關將服務實例與客戶端進行隔離,是所有Api請求的入口。因此可以統一鑑權。當然微服務網關的作用有很多,大家可自行百度瞭解。

新建一個網關的項目,請求先到達網關,再由網關分發請求到不同的實例。如圖:

 引入NuGet包:Ocelot、Ocelot.Provider.Consul

修改Startup類:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOcelot().AddConsul();
        //services.AddControllers();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        //將默認的請求管道全部丟掉
        app.UseOcelot();
        //if (env.IsDevelopment())
        //{
        //    app.UseDeveloperExceptionPage();
        //}

        //app.UseHttpsRedirection();

        //app.UseRouting();

        //app.UseAuthorization();

        //app.UseEndpoints(endpoints =>
        //{
        //    endpoints.MapControllers();
        //});
    }
}

新建一個json文件,用於配置策略等...

{//*************************單地址多實例負載均衡+Consul*****************************
  "Routes": [
    {
      //GeteWay轉發=>Downstream
      "DownstreamPathTemplate": "/api/{url}", //服務地址--url變量
      "DownstreamScheme": "http",
      //http://localhost:6299/T5/User/GetCustomerUser
      "UpstreamPathTemplate": "/T5/{url}", //網關地址--url變量 衝突的還可以加權重Priority
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "UseServiceDiscovery": true, //使用服務發現
      "ServiceName": "MicroserviceAttempt", //Consul服務名稱
      "LoadBalancerOptions": {
        "Type": "RoundRobin" //輪詢  //"LeastConnection":最少連接數服務器   "NoloadBalance":不負載均衡     "CookieStickySession":會話粘滯
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://127.0.0.1:6299",
    "ServiceDiscoveryProvider": {
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"//由Consul提供服務發現,每次請求去Consul
    }
    //"ServiceDiscoveryProvider": {
    //  "Host": "localhost",
    //  "Port": 8500,
    //  "Type": "PollConsul", //由Consul提供服務發現,每次請求去Consul
    //  "PollingInterval": 1000//輪詢Consul,評率毫秒--down是不知道的
    //}
  }
  //*************************單地址多實例負載均衡+Consul*****************************
}

啓動網關(項目)服務:

dotnet GateWay-Ocelot.dll --urls="http://*:6299" --ip="127.0.0.1" --port=6299

調用服務接口:

每請求一次就輪詢不同的服務實例,達到負載均衡。

到此就結尾了。如有不當的地方,請諒解,希望能幫到大家。

代碼已上傳至我的github:

Smiling Face with Smiling Eyes on Microsoft Windows 10 May 2019 Update

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章