【NET CORE微服務一條龍應用】第二章 配置中心使用


背景

系列目錄:【NET CORE微服務一條龍應用】開始篇與目錄

在分布式或者微服務系統里,通過配置文件來管理配置內容,是一件比較令人痛苦的事情,再謹慎也有濕鞋的時候,這就是在項目架構發展的過程中,配置中心存在的意義。

其實配置中心的組件已經有非常出名的案例,比如攜程的阿波羅配置中心(https://github.com/ctripcorp/apollo

為什么又造輪子,因為不想發布項目的時候到處切管理平台。

基本要求

作為一個通用的配置組件,需要支持如下功能:

1、客戶端定時刷新獲信最新配置信息並進行熱更新

2、配置有更新服務端主動推送重載或更新命令至客戶端進行配置獲取

所以涉及相對應組件如下:

1、支持廣播的消息通知組件,目前使用redis(StackExchange.Redis)、Zookeeper(Rabbit.Zookeeper)實現客戶端全局監聽服務,服務端可以推送不同組建不同的命令

2、支持定時獲取最新配置,目前使用HostedService實現全局統一啟動,客戶端實現全局啟動接口,接口使用Timer進行定時獲取配置

3、支持net core原生IConfiguration接口獲取配置中心數據

 服務端設計

 

管理服務端主要實現:

1、三表增刪改查

2、配置內容表,每次新增或者修改,當前配置信息版本號為,所以配置最大版本號然后加一

3、應用表列表增加主動通知功能

配置查詢服務端

主要提供配置信息的查詢接口

1、接口入參如下

public class QueryConfigInput
    {
        [NotEmpty("config_001","AppId不能為空")]
        public string AppId { set; get; }
        public long Version { set; get; }
        [NotEmpty("config_002", "簽名不能為空")]
        public string Sign { set; get; }
        [NotEmpty("config_005", "NamespaceName不能為空")]
        public string NamespaceName { set; get; }
        public string Env { set; get; }
    }

2、查詢邏輯

   2.1 入參基本驗證

   2.2 AppId 密鑰進行簽名驗證

   2.3 請求配置環境定位

   2.4 查詢當前請求應用和共有配置應用

   2.5 查詢大於當前查詢版本號的配置信息並返回

配置中心客戶端

客戶端主要實現原理和功能

1、配置信息請求,當前Http請求,需根據配置信息組合請求url,然后請求獲取配置,每次請求帶上當前配置最大版本號(在以后請求時只獲取有更新的配置)

2、配置信息本地存儲(容災),第一次獲取成功后,把配置信息進行版本文件存儲,以后的請求中當有配置更新時再進行文件存儲。

3、當配置請求失敗時進行本地文件配置信息的還原應用。

4、配置定時獲取

5、客戶端接收更新或者重載命令

6、原生IConfiguration配置查詢支持

部分功能介紹

客戶端參數

"ConfigServer": {
    "AppId": "PinzhiGO",
    "AppSercet": "xxxxxxxxxxxxx",
    "ServerUrl": "http://10.10.188.136:18081/", // 配置查詢服務端地址
    "NamespaceName": "Pinzhi.Identity.WebApi",
    "Env": "dev",
    "RefreshInteval": 300
  },

原生IConfiguration配置查詢

查看AddJsonFile源碼,可以發現實現自定義配置源,需要集成和實現ConfigurationProvider和IConfigurationSource兩個方法

代碼如下

public class BucketConfigurationProvider : ConfigurationProvider, IDataChangeListener, IConfigurationSource
    {
        private readonly ConfigurationHelper _configurationHelper;
        public BucketConfigurationProvider(BucketConfigOptions options)
        {
            _configurationHelper = new ConfigurationHelper(options);
            Data = new ConcurrentDictionary<string, string>();
        }

        public override void Load()
        {
            DataChangeListenerDictionary.Add(this);
            Data = _configurationHelper.Get().ConfigureAwait(false).GetAwaiter().GetResult();
        }

        private void SetData(ConcurrentDictionary<string, string> changeData)
        {
            foreach(var dic in changeData)
            {
                if (Data.ContainsKey(dic.Key))
                    Data[dic.Key] = dic.Value;
                else
                    Data.Add(dic);
            }
            // Data = new Dictionary<string, string>(_configRepository.Data, StringComparer.OrdinalIgnoreCase);
        }

        public void OnDataChange(ConcurrentDictionary<string, string> changeData)
        {
            SetData(changeData);
            OnReload();
        }

        public IConfigurationProvider Build(IConfigurationBuilder builder) => this;
    }

當有配置更新時,我們需要更新到ConfigurationProvider的Data中,所以我們需要實現自定義接口IDataChangeListener的OnDataChange方法,當客戶端請求發現有配置更新時,會調用接口的OnDataChange把最新的配置信息傳遞進來。

啟用原生IConfiguration方法如下:

 .ConfigureAppConfiguration((hostingContext, _config) =>
                   {
                       _config
                       .SetBasePath(Directory.GetCurrentDirectory())
                       .AddJsonFile("appsettings.json", true, true)
                       .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
                       .AddEnvironmentVariables(); // 添加環境變量
                       var option = new BucketConfigOptions();
                       _config.Build().GetSection("ConfigServer").Bind(option);
                       _config.AddBucketConfig(option);
                   })

定時配置獲取

常規做法是寫一個hostedservice的方法,然后寫一個timer去定時獲取,由於其他的組件可能都需要有定時的情況,我們統一處理了一下定時的任務,每個組件實現IExecutionService接口,然后組件會在啟動的時候循環調用IExecutionService的StartAsync的方法,組件包Bucket.Config.HostedService,原理比較簡單,使用代碼如下:

// 添加全局定時任務
            services.AddBucketHostedService(builder => {
                builder.AddAuthorize().AddConfig().AddErrorCode();
            });
public class AspNetCoreHostedService : IBucketAgentStartup
    {
        private readonly IEnumerable<IExecutionService> _services;

        public AspNetCoreHostedService(IEnumerable<IExecutionService> services)
        {
            _services = services;
        }

        public async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            foreach (var service in _services)
                await service.StartAsync(cancellationToken);
        }

        public async Task StopAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            foreach (var service in _services)
                await service.StopAsync(cancellationToken);
        }
    }

組件命令監聽

和上面原則一樣,也進行了統一的封裝,目前監聽主要實現了redis和zookeeper,下面舉例redis

組件監聽需實現接口

public interface IBucketListener
    {
        string ListenerName { get; }
        Task ExecuteAsync(string commandText);
    }

命令序列化實體

public class NetworkCommand
    {
        public string NotifyComponent { set; get; }
        public string CommandText { set; get; }
    }
    public enum NetworkCommandType
    {
        /// <summary>
        /// 更新
        /// </summary>
        Refresh,
        /// <summary>
        /// 重載
        /// </summary>
        Reload,
    }

在hostedservice啟動時實現

public Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            _subscriber = _redisClient.GetSubscriber(_redisListenerOptions.ConnectionString);
            return _subscriber.SubscribeAsync(RedisListenerKey, (channel, message) =>
            {
                var command = JsonConvert.DeserializeObject<Bucket.Values.NetworkCommand>(message);
                _extractCommand.ExtractCommandMessage(command);
            });
        }

在接口IExtractCommand里會根據各個監聽組件的ListenerName進行對應的調用

使用方法如下:

// 添加應用監聽
            services.AddListener(builder => {
                //builder.UseRedis();
                builder.UseZookeeper();
                builder.AddAuthorize().AddConfig().AddErrorCode();
            });

所以對應組件實現的命令監聽只要關心自身邏輯即可嗎,代碼如下

 public class BucketConfigListener : IBucketListener
    {
        public string ListenerName => "Bucket.Config";

        private readonly IDataRepository _dataRepository;

        public BucketConfigListener(IDataRepository dataRepository)
        {
            _dataRepository = dataRepository;
        }

        public async Task ExecuteAsync(string commandText)
        {
            if (!string.IsNullOrWhiteSpace(commandText) && commandText == NetworkCommandType.Refresh.ToString())
                await _dataRepository.Get();
            if (!string.IsNullOrWhiteSpace(commandText) && commandText == NetworkCommandType.Reload.ToString())
                await _dataRepository.Get(true);
        }
    }

配置中心使用配置如下

 .ConfigureAppConfiguration((hostingContext, _config) =>
                   {
                       _config
                       .SetBasePath(Directory.GetCurrentDirectory())
                       .AddJsonFile("appsettings.json", true, true)
                       .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
                       .AddEnvironmentVariables(); // 添加環境變量
                       var option = new BucketConfigOptions();
                       _config.Build().GetSection("ConfigServer").Bind(option);
                       _config.AddBucketConfig(option);
                   })

// ConfigureServices 
// 添加配置服務
            services.AddConfigServer(Configuration);
// 添加應用監聽
            services.AddListener(builder => {
                //builder.UseRedis();
                builder.UseZookeeper();
                builder.AddAuthorize().AddConfig().AddErrorCode();
            });
            // 添加全局定時任務
            services.AddBucketHostedService(builder => {
                builder.AddAuthorize().AddConfig().AddErrorCode();
            });

//使用
private readonly IConfiguration _configuration;
        private readonly IConfig _config;

        public AuthController(IConfiguration configuration, IConfig config)
        {
            _configuration= configuration;
            _config= config;
        }
// 獲取值
_configuration.GetValue<string>("qqqq");
_config.StringGet("qqqq");

Appsettings.json相關配置信息轉移至配置中心

由於配置中心客戶端實現了原生的IConfiguration,所以appsetting的相關配置我們完全可以移至配置中心中,由於appsetting使用的是json,所以在配置中心服務端配置信息的Key需要轉換,舉例:

"BucketListener": {
    "Redis": {
      "ConnectionString": "127.0.0.1:6379,allowadmin=true",
      "ListenerKey": "Bucket.Sample"
    },
    "Zookeeper": {
      "ConnectionString": "localhost:2181",
      "ListenerKey": "Bucket.Sample"
    }
  }

在配置中心key如下:

BucketListener:Redis:ConnectionString

BucketListener:Redis:ListenerKey

......

數組使用如下:

DbConfig:0:Name

DbConfig:0:DbType

DbConfig:1:Name

DbConfig:1:DbType

總結

個人寫作水平有限,涉及的東西也很多,篇幅有限所以只做了大體介紹,忘諒解

本章涉及源碼
https://github.com/q315523275/FamilyBucket/tree/master/src/Config 客戶端組件

https://github.com/q315523275/FamilyBucket/tree/master/%E5%9F%BA%E7%A1%80%E6%9C%8D%E5%8A%A1%E9%A1%B9%E7%9B%AE/Pinzhi.ConfigServer 配置查詢服務端

https://github.com/q315523275/FamilyBucket/tree/master/%E5%9F%BA%E7%A1%80%E6%9C%8D%E5%8A%A1%E9%A1%B9%E7%9B%AE/Pinzhi.Platform 綜合管理服務接口


免責聲明!

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



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