.NETCore微服務探尋(一) - 閘道器

有什麼不能一笑而過呢發表於2020-06-15

前言

一直以來對於.NETCore微服務相關的技術棧都處於一個淺嘗輒止的瞭解階段,在現實工作中也對於微服務也一直沒有使用的業務環境,所以一直也沒有整合過一個完整的基於.NETCore技術棧的微服務專案。正好由於最近剛好辭職,有了時間可以寫寫自己感興趣的東西,所以在此想把自己瞭解的微服務相關的概念和技術框架使用實現記錄在一個完整的工程中,由於本人技術有限,所以錯誤的地方希望大家指出。\

專案地址:https://github.com/yingpanwang/fordotnet/tree/dev

什麼是Api閘道器

  由於微服務把具體的業務分割成單獨的服務,所以如果直接將每個服務都與呼叫者直接,那麼維護起來將相當麻煩與頭疼,Api閘道器擔任的角色就是整合請求並按照路由規則轉發至服務的例項,並且由於所有所有請求都經過閘道器,那麼閘道器還可以承擔一系列巨集觀的攔截功能,例如安全認證,日誌,熔斷

為什麼需要Api閘道器

 因為Api閘道器可以提供安全認證,日誌,熔斷相關的巨集觀攔截的功能,也可以遮蔽多個下游服務的內部細節

有哪些有名的Api閘道器專案

  • Zuul Spring Cloud 整合
  • Kong 一款lua輕量級閘道器專案
  • Ocelot .NETCore閘道器專案

Ocelot使用

1.通過Nuget安裝Ocelot

2.準備並編輯Ocelot配置資訊

Ocelot.json

{
  "ReRoutes": [
    // Auth
    {
      "UpstreamPathTemplate": "/auth/{action}", // 上游請求路徑模板
      "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ], // 上游請求方法
      "ServiceName": "Auth", // 服務名稱
      "UseServiceDiscovery": true, // 是否使用服務發現
      "DownstreamPathTemplate": "/connect/{action}", // 下游匹配路徑模板
      "DownstreamScheme": "http", // 下游請求
      "LoadBalancerOptions": { // 負載均衡配置
        "Type": "RoundRobin"
      }
      //,
      // 如果不採用服務發現需要指定下游host
      //"DownstreamHostAndPorts": [
      //  {
      //    "Host": "10.0.1.10",
      //    "Port": 5000
      //  },
      //  {
      //    "Host": "10.0.1.11",
      //    "Port": 5000
      //  }
      //]
    }
  ],
  "GlobalConfiguration": { // 全域性配置資訊
    "BaseUrl": "http://localhost:5000", // 請求 baseurl 
    "ServiceDiscoveryProvider": { //服務發現提供者
      "Host": "106.53.199.185",
      "Port": 8500,
      "Type": "Consul" // 使用Consul
    }
  }
}

3.新增Ocelot json檔案到專案中

將Config目錄下的ocelot.json新增到專案中

4.在閘道器專案中 StartUp ConfigService中新增Ocelot的服務,在Configure中新增Ocelot的中介軟體(由於我這裡使用了Consul作為服務發現,所以需要新增Consul的依賴的服務AddConsul,如果不需要服務發現的話可以不用新增)

5.將需要發現的服務通過程式碼在啟動時註冊到Consul中

我這裡自己封裝了一個註冊服務的擴充套件(寫的比較隨意沒有在意細節)

appsettings.json 中新增註冊服務配置資訊

"ServiceOptions": {
    "ServiceIP": "localhost",
    "ServiceName": "Auth",
    "Port": 5800,
    "HealthCheckUrl": "/api/health",
    "ConsulOptions": {
      "Scheme": "http",
      "ConsulIP": "localhost",
      "Port": 8500
    }
  }

擴充套件程式碼 ConsulExtensions(注意:3.1中 IApplicationLifetime已廢棄 所以使用的是IHostApplicationLifetime 作為程式生命週期注入的方式)


using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;

namespace ForDotNet.Common.Consul.Extensions
{
    /// <summary>
    /// 服務配置資訊
    /// </summary>
    public class ServiceOptions
    {
        /// <summary>
        /// 服務ip
        /// </summary>
        public string ServiceIP { get; set; }

        /// <summary>
        /// 服務名稱
        /// </summary>
        public string ServiceName { get; set; }

        /// <summary>
        /// 協議型別http or https
        /// </summary>
        public string Scheme { get; set; } = "http";

        /// <summary>
        /// 埠
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// 健康檢查介面
        /// </summary>
        public string HealthCheckUrl { get; set; } = "/api/values";

        /// <summary>
        /// 健康檢查間隔時間
        /// </summary>
        public int HealthCheckIntervalSecond { get; set; } = 10;

        /// <summary>
        /// consul配置資訊
        /// </summary>
        public ConsulOptions ConsulOptions { get; set; }
    }

    /// <summary>
    /// consul配置資訊
    /// </summary>
    public class ConsulOptions
    {
        /// <summary>
        /// consul ip
        /// </summary>
        public string ConsulIP { get; set; }

        /// <summary>
        /// consul 埠
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// 協議型別http or https
        /// </summary>
        public string Scheme { get; set; } = "http";
    }

    /// <summary>
    /// consul註冊客戶端資訊
    /// </summary>
    public class ConsulClientInfo
    {
        /// <summary>
        /// 註冊資訊
        /// </summary>
        public AgentServiceRegistration RegisterInfo { get; set; }

        /// <summary>
        /// consul客戶端
        /// </summary>
        public ConsulClient Client { get; set; }
    }

    /// <summary>
    /// consul擴充套件(通過配置檔案配置)
    /// </summary>
    public static class ConsulExtensions
    {
        private static readonly ServiceOptions serviceOptions = new ServiceOptions();

        /// <summary>
        /// 新增consul
        /// </summary>
        public static void AddConsulServiceDiscovery(this IServiceCollection services)
        {
            var config = services.BuildServiceProvider().GetService<IConfiguration>();
            config.GetSection("ServiceOptions").Bind(serviceOptions);
            //config.Bind(serviceOptions);

            if (serviceOptions == null)
            {
                throw new Exception("獲取服務註冊資訊失敗!請檢查配置資訊是否正確!");
            }
            Register(services);
        }

        /// <summary>
        /// 新增consul(通過配置opt物件配置)
        /// </summary>
        /// <param name="app"></param>
        /// <param name="life">引用生命週期</param>
        /// <param name="options">配置引數</param>
        public static void AddConsulServiceDiscovery(this IServiceCollection services, Action<ServiceOptions> options)
        {
            options.Invoke(serviceOptions);
            Register(services);
        }

        /// <summary>
        /// 註冊consul服務發現
        /// </summary>
        /// <param name="app"></param>
        /// <param name="life"></param>
        public static void UseConsulServiceDiscovery(this IApplicationBuilder app, IHostApplicationLifetime life)
        {
            var consulClientInfo = app.ApplicationServices.GetRequiredService<ConsulClientInfo>();
            if (consulClientInfo != null)
            {
                life.ApplicationStarted.Register( () =>
                {
                     consulClientInfo.Client.Agent.ServiceRegister(consulClientInfo.RegisterInfo).Wait();
                });

                life.ApplicationStopping.Register( () =>
                {
                     consulClientInfo.Client.Agent.ServiceDeregister(consulClientInfo.RegisterInfo.ID).Wait();
                });
            }
            else
            {
                throw new NullReferenceException("未找到相關consul客戶端資訊!");
            }
        }

        private static void Register(this IServiceCollection services)
        {
            if (serviceOptions == null)
            {
                throw new Exception("獲取服務註冊資訊失敗!請檢查配置資訊是否正確!");
            }
            if (serviceOptions.ConsulOptions == null)
            {
                throw new ArgumentNullException("請檢查是否配置Consul資訊!");
            }

            string consulAddress = $"{serviceOptions.ConsulOptions.Scheme}://{serviceOptions.ConsulOptions.ConsulIP}:{serviceOptions.ConsulOptions.Port}";

            var consulClient = new ConsulClient(opt =>
            {
                opt.Address = new Uri(consulAddress);
            });

            var httpCheck = new AgentServiceCheck()
            {
                DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10), // 服務啟動多久後註冊
                Interval = TimeSpan.FromSeconds(serviceOptions.HealthCheckIntervalSecond), // 間隔
                HTTP = $"{serviceOptions.Scheme}://{serviceOptions.ServiceIP}:{serviceOptions.Port}{serviceOptions.HealthCheckUrl}",
                Timeout = TimeSpan.FromSeconds(10)
            };

            var registration = new AgentServiceRegistration()
            {
                Checks = new[] { httpCheck },
                ID = Guid.NewGuid().ToString(),
                Name = serviceOptions.ServiceName,
                Address = serviceOptions.ServiceIP,
                Port = serviceOptions.Port,
            };

            services.AddSingleton(new ConsulClientInfo()
            {
                Client = consulClient,
                RegisterInfo = registration
            });
        }
    }
}

6.啟動執行

  • 啟動consul
  • 啟動 Auth,Gateway專案
  • 通過閘道器專案訪問Auth

啟動Consul

為了方便演示這裡是以開發者啟動的consul
在consul.exe的目錄下執行
consul agent -dev -ui // 開發者模式執行帶ui

啟動 Auth,Gateway專案

啟動專案和可以發現我的們Auth服務已經註冊進來了

通過閘道器訪問Auth

我們這裡訪問 http://localhost:5000/auth/token 獲取token

我們可以看到閘道器專案接收到了請求並在控制檯中列印出以下資訊

然後在Auth專案中的控制檯中可以看到已經成功接收到了請求並響應

相關文章