微服务配置文件可视化集中管理 - 第二章


目录

  • 微服务配置文件可视化集中管理 - 第二章
  • 前言
  • 一、JsonConfigurationFileParser源码更改.
  • 二、应用站点热更新实现步骤.
  • 1.应用站点创建api接口用于接收配置中心配置更改的通知.
  • 2.调用ConfigurationRoot里的Reload()函数.
  • 3.测试
  • 3.1.我们先启动,配置中心站点:http://localhost:41004
  • 3.2.在启动应用站点:http://localhost:41000 首次启动,应用站点从配置中心拉取配置信息. 拉取的配置如下:
  • 3.3.配置中心,配置更改,通知应用站点.
  • 总结



前言

上期回顾:在微服务配置文件可视化集中管理 - 第一章中我们已经实现了在.net core项目中启动的时候,从远程配置中心拉取配置信息,并且自定义实现了IConfigurationProvider提供器,达到了完美兼容.net core应用使用IConfiguration来取配置信息的目的.

本期目标:

  1. 若配置中心有信息更改,如何通知到所有的应用站点,进行热更新.
  2. 若配置中心挂了,怎么进行容错,不能因为配置中心宕机,而造成所有的应用站点不能正常工作.

一、JsonConfigurationFileParser源码更改.

第一章中重写Load()函数时,方法里用到了这样一句代码,JsonConfigurationFileParser.Parse(response);这个函数的作用是让我们将从配置中心拉取回来的Json字符串装换为,key:value的格式(并且支持层级格式)
即如Json:

{
		"database": "Server=192.168.10.146;Database=DAS;User ID=xxx;Password=xxx;Pooling=true;Max Pool Size=500;Connection Lifetime=20;",
		"baseApi": {
			"dk": "193.168.10.146"
		}
}

我们要通过无缝适配获取方式:
var baseApi = _configuration[“baseApi:dk”];
就需要JsonConfigurationFileParser.Parse(response);这句代码,
它的作用,是将Json串,转化为ConfigurationProvider里的Data,它的类型时一个字典IDictionary<string, string>.这样我们在用的时候,就可以方便的取到值了.

JsonConfigurationFileParser更改后的源码如下:(直接复制到你的项目,使用就行):

public class JsonConfigurationFileParser
    {
        private readonly IDictionary<string, string> _data = (IDictionary<string, string>) new SortedDictionary<string, string>((IComparer<string>) StringComparer.OrdinalIgnoreCase);
        private readonly Stack<string> _context = new Stack<string>();
        private string _currentPath;
        private string jsonText;

        private JsonConfigurationFileParser(string jsonText)
        {
            this.jsonText = jsonText;
        }

        public static IDictionary<string, string> Parse(string jsonText) => new JsonConfigurationFileParser(jsonText).ParseStream();

        private IDictionary<string, string> ParseStream()
        {

          this._data.Clear();
          this.VisitJObject(JObject.Parse(jsonText));
          return this._data;
        }

        private void VisitJObject(JObject jObject)
        {
          foreach (JProperty property in jObject.Properties())
          {
            this.EnterContext(property.Name);
            this.VisitProperty(property);
            this.ExitContext();
          }
        }

        private void VisitProperty(JProperty property) => this.VisitToken(property.Value);

        private void VisitToken(JToken token)
        {
          switch (token.Type)
          {
            case JTokenType.Object:
              this.VisitJObject(token.Value<JObject>());
              break;
            case JTokenType.Array:
              this.VisitArray(token.Value<JArray>());
              break;
            case JTokenType.Integer:
            case JTokenType.Float:
            case JTokenType.String:
            case JTokenType.Boolean:
            case JTokenType.Null:
            case JTokenType.Raw:
            case JTokenType.Bytes:
              this.VisitPrimitive(token.Value<JValue>());
              break;
            default:
              throw new FormatException("JToken is error");
          }
    }

    private void VisitArray(JArray array)
    {
      for (int index = 0; index < array.Count; ++index)
      {
        this.EnterContext(index.ToString());
        this.VisitToken(array[index]);
        this.ExitContext();
      }
    }

    private void VisitPrimitive(JValue data)
    {
      string currentPath = this._currentPath;
      if (this._data.ContainsKey(currentPath))
        throw new FormatException("JValue is Error");
      this._data[currentPath] = data.ToString((IFormatProvider) CultureInfo.InvariantCulture);
    }

    private void EnterContext(string context)
    {
      this._context.Push(context);
      this._currentPath = ConfigurationPath.Combine(this._context.Reverse<string>());
    }

    private void ExitContext()
    {
      this._context.Pop();
      this._currentPath = ConfigurationPath.Combine(this._context.Reverse<string>());
    }
  }

上面的代码,更改处其实是如下的地方:

//更改源码前
 private IDictionary<string, string> ParseStream(Stream input)
    {
      this._data.Clear();
      this._reader = new JsonTextReader((TextReader) new StreamReader(input));
      this._reader.DateParseHandling = DateParseHandling.None;
      this.VisitJObject(JObject.Load((JsonReader) this._reader));
      return this._data;
    }

 //更改源码后
 private IDictionary<string, string> ParseStream()
        {

          this._data.Clear();
          this.VisitJObject(JObject.Parse(jsonText));
          return this._data;
        }

这就是第一章中说的,需要将远程获取的jsonText,通过这个类的转化,就可以写入字典Data里了(稍微更改一下源码),以此实现无缝衔接原来的IConfiguration获取配置的方式.

二、应用站点热更新实现步骤.

注意:

配置中心站点:http://localhost:41004
拉取最新配置接口地址:http://localhost:41004/api/Configs
请求参数:
key:配置标识(通过这个标识,能拉取到各自应用的配置)
dev:环境标签(通过这个标识,拉取指定的环境配置,当然也可以不传,这样取什么环境的配置,完成交给配置中心来管理,也是可以的)
请求方式:GET

应用站点:http://localhost:41000
应用站点用于接收通知的接口地址:http://localhost:41000/api/Test/Notify
请求参数:无
请求方式:GET

1.应用站点创建api接口用于接收配置中心配置更改的通知.

应用站点http://localhost:41000接收配置更改通知的代码如下(示例):

/// <summary>
    /// 测试接收配置文件更改通知
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : Controller
    {
        /// <summary>
        /// 构造函数注入
        /// </summary>
        IConfiguration _configuration;
        public TestController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        [HttpGet]
        public bool Notify()
        {
            var configurationRoot = (ConfigurationRoot) _configuration;
            configurationRoot.Reload();
            return true;
        }
    }

这里接收配置中心,通知的接口路由为:/api/Test/Notify

2.调用ConfigurationRoot里的Reload()函数.

可以看到,这句代码configurationRoot.Reload():

var configurationRoot = (ConfigurationRoot) _configuration;
            configurationRoot.Reload();

我们调用了Reload()函数,就能达到热更新.非常简单.官方已经给我们提供了这个接口IConfigurationRoot,它里面就有个Reload()函数,专门为我们做热更新的,它是继承了IConfiguration,具体继承关系,如下图所示:

微服务之间文件上传_微服务之间文件上传

3.测试

3.1.我们先启动,配置中心站点:http://localhost:41004

3.2.在启动应用站点:http://localhost:41000 首次启动,应用站点从配置中心拉取配置信息. 拉取的配置如下:

{
		"database": "Server=192.168.10.146;Database=DAS;User ID=xxx;Password=xxx;Pooling=true;Max Pool Size=500;Connection Lifetime=20;",
		"baseApi": {
			"dk": "193.168.10.146"
		}
}

我们请求应用站点,用于测试的action,看首次获取的配置.

/// <summary>
    /// 测试配置文件获取
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : Controller
    {
        /// <summary>
        /// 构造函数注入
        /// </summary>
        IConfiguration _configuration;
        public TestController(IConfiguration configuration)
        {
            _configuration = configuration;

        }
        
        [HttpGet]
        public List<KeyValuePair<string,string>> Get()
        {
            var list = new List<KeyValuePair<string, string>>();
            var database = _configuration["database"];
            var baseApi = _configuration["baseApi:dk"];
            list.Add(new KeyValuePair<string, string>("database",database));
            list.Add(new KeyValuePair<string, string>("baseApi",baseApi));
            return list;
        }
    }

请求:http://localhost:41000/api/test/get

微服务之间文件上传_后端_02


这时候,我们成功获取到了配置文件.

3.3.配置中心,配置更改,通知应用站点.

配置中心,更改后的配置为如下所示:

{
		"database": "Server=.",
		"baseApi": {
			"dk": "我更改了"
		}
}

调用应用站点通知接口:
http://localhost:41000/api/test/Notify

/// <summary>
    ///  
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : Controller
    {
        /// <summary>
        /// 构造函数注入
        /// </summary>
        IConfiguration _configuration;
        public TestController(IConfiguration configuration)
        {
            _configuration = configuration;

        }

        [HttpGet]
        public bool Notify()
        {
            var configurationRoot = (ConfigurationRoot) _configuration;
            configurationRoot.Reload();
            return true;
        }
    }

通知以后,在请求http://localhost:41000/api/test/get,看配置信息是否已经热更新?

打断点后,结果如下截图所示:

微服务之间文件上传_asp.net_03


好了,已经完美的实现了热更新.


总结

  • 当前解决了热更新的问题.但是配置中心挂了,怎么进行容错,不能因为配置中心宕机,而造成所有的应用站点不能正常工作.**微服务配置文件可视化集中管理 - 第三章(完结篇)**中详细解答.