.NET Core 3.0之创建基于Consul的Configuration扩展组件
写在前面
经过前面三篇关于.net core configuration的文章之后,本篇文章主要讨论如何扩展一个configuration组件出来。如果前面三篇文章没有看到,可以点击如下地址访问
-
.net core 3.0之深入源码理解configuration(一)
-
.net core 3.0之深入源码理解configuration(二)
-
.net core 3.0之深入源码理解configuration(三)
了解了configuration的源码后,再去扩展一个组件就会比较简单,接下来我们将在.net core 3.0-preview5的基础上创建一个基于consul的配置组件。
相信大家对consul已经比较了解了,很多项目都会使用consul作为配置中心,此处也不做其他阐述了,主要是讲一下,创建consul配置扩展的一些思路。使用consul配置功能时,我们可以将信息转成json格式后再存储,那么我们在读取的时候,在体验上就像是从读取json文件中读取一样。
开发前的准备
初始化consul
假设你已经安装并启动了consul,我们打开key/value功能界面,创建两组配置选项出来,分别是commonservice和userservice,如下图所示
配置值采用json格式
实现思路
我们知道在configuration整个的设计框架里,比较重要的类configurationroot,内部又有一个iconfigurationprovider集合属性,也就是说我们追加iconfigurationprovider实例最终也会被放到到该集合中,如下图所示
该项目中,我使用到了一个已经封装好的consul(v0.7.2.6)类库,同时基于.net core关于configuration的设计风格,做如下的框架设计
考虑到我会在该组件内部创建consulclient实例,所以对consulclient构造函数的一部分参数做了抽象提取,并添加到了iconsulconfigurationsource中,以增强该组件的灵活性。
之前说过,consul中的配置信息是以json格式存储的,所以此处使用到了microsoft.extensions.configuration.json.jsonconfigurationfileparser,用以将json格式的信息转换为configuration的通用格式key/value。
核心代码
iconsulconfigurationsource
1: /// <summary>
2: /// consulconfigurationsource
3: /// </summary>
4: public interface iconsulconfigurationsource : iconfigurationsource
5: {
6: /// <summary>
7: /// cancellationtoken
8: /// </summary>
9: cancellationtoken cancellationtoken { get; }
10:
11: /// <summary>
12: /// consul构造函数实例,可自定义传入
13: /// </summary>
14: action<consulclientconfiguration> consulclientconfiguration { get; set; }
15:
16: /// <summary>
17: /// consul构造函数实例,可自定义传入
18: /// </summary>
19: action<httpclient> consulhttpclient { get; set; }
20:
21: /// <summary>
22: /// consul构造函数实例,可自定义传入
23: /// </summary>
24: action<httpclienthandler> consulhttpclienthandler { get; set; }
25:
26: /// <summary>
27: /// 服务名称
28: /// </summary>
29: string servicekey { get; }
30:
31: /// <summary>
32: /// 可选项
33: /// </summary>
34: bool optional { get; set; }
35:
36: /// <summary>
37: /// consul查询选项
38: /// </summary>
39: queryoptions queryoptions { get; set; }
40:
41: /// <summary>
42: /// 重新加载延迟时间,单位是毫秒
43: /// </summary>
44: int reloaddelay { get; set; }
45:
46: /// <summary>
47: /// 是否在配置改变的时候重新加载
48: /// </summary>
49: bool reloadonchange { get; set; }
50: }
consulconfigurationsource
该类提供了一个构造函数,用于接收servicekey和cancellationtoken实例
1: public consulconfigurationsource(string servicekey, cancellationtoken cancellationtoken)
2: {
3: if (string.isnullorwhitespace(servicekey))
4: {
5: throw new argumentnullexception(nameof(servicekey));
6: }
7:
8: this.servicekey = servicekey;
9: this.cancellationtoken = cancellationtoken;
10: }
其build()方法也比较简单,主要是初始化consulconfigurationparser实例
1: public iconfigurationprovider build(iconfigurationbuilder builder)
2: {
3: consulconfigurationparser consulparser = new consulconfigurationparser(this);
4:
5: return new consulconfigurationprovider(this, consulparser);
6: }
consulconfigurationparser
该类比较复杂,主要实现consul配置的获取、监控以及容错处理,公共方法源码如下
1: /// <summary>
2: /// 获取并转换consul配置信息
3: /// </summary>
4: /// <param name="reloading"></param>
5: /// <param name="source"></param>
6: /// <returns></returns>
7: public async task<idictionary<string, string>> getconfig(bool reloading, iconsulconfigurationsource source)
8: {
9: try
10: {
11: queryresult<kvpair> kvpair = await this.getkvpairs(source.servicekey, source.queryoptions, source.cancellationtoken).configureawait(false);
12: if ((kvpair?.response == null) && !source.optional)
13: {
14: if (!reloading)
15: {
16: throw new formatexception(resources.error_invalidservice(source.servicekey));
17: }
18:
19: return new dictionary<string, string>();
20: }
21:
22: if (kvpair?.response == null)
23: {
24: throw new formatexception(resources.error_valuenotexist(source.servicekey));
25: }
26:
27: this.updatelastindex(kvpair);
28:
29: return jsonconfigurationfileparser.parse(source.servicekey, new memorystream(kvpair.response.value));
30: }
31: catch (exception exception)
32: {
33: throw exception;
34: }
35: }
36:
37: /// <summary>
38: /// consul配置信息监控
39: /// </summary>
40: /// <param name="key"></param>
41: /// <param name="cancellationtoken"></param>
42: /// <returns></returns>
43: public ichangetoken watch(string key, cancellationtoken cancellationtoken)
44: {
45: task.run(() => this.refreshforchanges(key, cancellationtoken), cancellationtoken);
46:
47: return this.reloadtoken;
48: }
另外,关于consul的监控主要利用了queryresult.lastindex属性,该类缓存了该属性的值,并与实获取的值进行比较,以判断是否需要重新加载内存中的缓存配置
consulconfigurationprovider
该类除了实现load方法外,还会根据reloadonchange属性,在构造函数中注册onchange事件,用于重新加载配置信息,源码如下:
1: public sealed class consulconfigurationprovider : configurationprovider
2: {
3: private readonly consulconfigurationparser configurationparser;
4: private readonly iconsulconfigurationsource source;
5:
6: public consulconfigurationprovider(iconsulconfigurationsource source, consulconfigurationparser configurationparser)
7: {
8: this.configurationparser = configurationparser;
9: this.source = source;
10:
11: if (source.reloadonchange)
12: {
13: changetoken.onchange(
14: () => this.configurationparser.watch(this.source.servicekey, this.source.cancellationtoken),
15: async () =>
16: {
17: await this.configurationparser.getconfig(true, source).configureawait(false);
18:
19: thread.sleep(source.reloaddelay);
20:
21: this.onreload();
22: });
23: }
24: }
25:
26: public override void load()
27: {
28: try
29: {
30: this.data = this.configurationparser.getconfig(false, this.source).configureawait(false).getawaiter().getresult();
31: }
32: catch (aggregateexception aggregateexception)
33: {
34: throw aggregateexception.innerexception;
35: }
36: }
37: }
调用及运行结果
此处调用在program中实现
1: public class program
2: {
3: public static void main(string[] args)
4: {
5: cancellationtokensource cancellationtokensource = new cancellationtokensource();
6:
7: webhost.createdefaultbuilder(args).configureappconfiguration(
8: (hostingcontext, builder) =>
9: {
10: builder.addconsul("userservice", cancellationtokensource.token, source =>
11: {
12: source.consulclientconfiguration = cco => cco.address = new uri("http://localhost:8500");
13: source.optional = true;
14: source.reloadonchange = true;
15: source.reloaddelay = 300;
16: source.queryoptions = new queryoptions
17: {
18: waitindex = 0
19: };
20: });
21:
22: builder.addconsul("commonservice", cancellationtokensource.token, source =>
23: {
24: source.consulclientconfiguration = cco => cco.address = new uri("http://localhost:8500");
25: source.optional = true;
26: source.reloadonchange = true;
27: source.reloaddelay = 300;
28: source.queryoptions = new queryoptions
29: {
30: waitindex = 0
31: };
32: });
33: }).usestartup<startup>().build().run();
34: }
35: }
运行结果,如下图所示,我们已经加载到了两个consulprovider实例,这与我们在program中添加的两个consul配置一致,其中所加载到的值也和.net core configuration的key/value风格相一致,所加载到的值也会consul中所存储的相一致
总结
基于源码扩展一个配置组件出来,还是比较简单的,另外需要说明的是,该组件关于json的处理主要基于.net core原生组件,位于命名空间内的system.text.json中,所以该组件无法在.net core 3.0之前的版本中运行,需要引入额外的json组件辅助处理。
源码已经托管于github,地址:https://github.com/littlehorse8/navyblue.extensions.configuration.consul,记得点个小星星哦