.NET Core用数据库做配置中心加载Configuration

本文介绍了一个在.NET中用数据库做配置中心服务器的方式,介绍了读取配置的开源自定义ConfigurationProvider,并且讲解了主要实现原理。

1、 为什么用数据库做配置中心

在开发youzack.com这个学英语网站的时候,需要保存第三方接口AppKey、JWT等配置信息。youzack是一个由登录注册、听力精听、背单词、背单词第二版等4个子网站组成,为了保证网站的可用性,网站采用集群式部署,同一个子网站部署2台Web服务器实例,因此整个系统部署了2*4=8个Web服务实例。配置信息如果都保存到本地配置文件的话,管理特别麻烦,比如,如果一个配置项要修改的话,就要修改8个地方,因此需要保存到一个配置中心服务器上,各个应用都从配置中心服务器读取配置。

目前,有Apollo、Nacos、Spring Cloud Config等开源的配置中心可供使用,功能非常强大,不过需要单独部署维护配置中心服务器。我这个网站并不复杂,为了避免运维的麻烦,我要尽量减少网站中使用的服务的数量。

youzack所在的阿里云也有对应的配置中心服务可以用,不用自己去部署维护,但是我不想让网站依赖于特定云服务商,而且那样的话在本地开发环境也要特殊处理。

因为这些子网站都要连接数据库,因此把配置信息存到数据库里,用数据库来做配置中心服务器,最符合我的要求。

2、 项目优点

由于网站采用.NET 5开发,为了方便各个项目读取配置,我开发了一个自定义的ConfigurationProvider,名字叫做Zack.AnyDBConfigProvider。

这个Zack.AnyDBConfigProvider的优点如下:

  1. 配置保存到数据库表中,管理简单;

  2. 支持几乎所有关系数据库,只要.NET能连上的数据库都支持;

  3. 支持配置的版本化管理;

  4. 支持符合.NET配置命名规则的多级配置的覆盖;

  5. 配置项的值类型支持丰富,既支持简单的字符串、数字等类型,也支持json等格式;

  6. 采用.Net Standard2开发,因此可以支持.NET Framework、.NET Core等。

 

项目GitHub地址:

https://github.com/yangzhongke/Zack.AnyDBConfigProvider

3、  Zack.AnyDBConfigProvider用法

第一步:

在数据库中建一张表,默认名字是T_Configs,这个表名允许自定义为其他名字,具体见后续步骤。表必须有Id、Name、Value三个列,Id定义为整数、自动增长列,Name和Value都定义为字符串类型列,列的最大长度根据系统配置数据的长度来自行确定,Name列为配置项的名字,Value列为配置项的值。

允许具有相同Name的多行数据,其中Id值最大的一条的值生效,这样就实现了简单的配置版本管理。因此,如果不确认一个新的配置项一定成功的话,可以先新增一条同名的配置,如果出现问题,只要把这条数据删除就可以回滚到旧的配置项。

Name列的值遵循.NET中配置的“多层级数据的扁平化”(详见微软文档https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0),如下都是合法的Name列的值:

Api:Jwt:Audience

Age

Api:Names:0

Api:Names:1

 

Value列的值用来保存Name类对应的配置的值。Value的值可以是普通的值,也可以使用json数组,也可以是json对象。比如下面都是合法的Value值:

["a","d"]

{"Secret": "afd3","Issuer":"youzack","Ids":[3,5,8]}

ffff

3

下面这个数据就是后续演示使用的数据:

第二步:

创建一个ASP.NET 项目,演示案例是使用VisualStudio 2019创建.NET Core 3.1的ASP.NETCore MVC项目,但是Zack.AnyDBConfigProvider的应用范围并不局限于这个版本。

通过NuGet安装开发包:

Install-Package Zack.AnyDBConfigProvider

 

第三步:配置数据库的连接字符串

虽然说项目中其他配置都可以放到数据库中了,但是数据库本身的连接字符串仍然需要单独配置。它既可以配置到本地配置文件中,也可以通过环境变量等方式配置,下面用配置到本地json文件来举例。

打开项目的appsettings.json,增加如下节点:

  "ConnectionStrings": {

    "conn1":"Server=127.0.0.1;database=youzack;uid=root;pwd=123456"

 },

接下来在Program.cs里的CreateHostBuilder方法的webBuilder.UseStartup<Startup>();之前增加如下代码:

webBuilder.ConfigureAppConfiguration((hostCtx, configBuilder)=>{

       var configRoot =configBuilder.Build();

       string connStr = configRoot.GetConnectionString("conn1");

       configBuilder.AddDbConfiguration(()=> newMySqlConnection(connStr),reloadOnChange:true,reloadInterval:TimeSpan.FromSeconds(2));

});

       上面代码的第3行用来从本地配置中读取到数据库的连接字符串,然后第4行代码使用AddDbConfiguration来添加Zack.AnyDBConfigProvider的支持。我这里是使用MySql数据库,所以使用new MySqlConnection(connStr)创建到MySQL数据库的连接,你可以换任何你想使用的其他数据库管理系统。reloadOnChange参数表示是否在数据库中的配置修改后自动加载,默认值是false。如果把reloadOnChange设置为true,则每隔reloadInterval这个指定的时间段,程序就会扫描一遍数据库中配置表的数据,如果数据库中的配置数据有变化,就会重新加载配置数据。AddDbConfiguration方法还支持一个tableName参数,用来自定义配置表的名字,默认名称为T_Configs。

       不同版本的开发工具生成的项目模板不一样,所以初始代码也不一样,所以上面的代码也许并不能原封不动的放到你的项目中,请根据自己项目的情况来定制化配置的代码。

 

第四步:

剩下的就是标准的.NET 中读取配置的方法了,比如我们要读取上面例子中的数据,那么就如下配置。

首先创建Ftp类(有IP、UserName、Password三个属性)、Cors类(有string[]类型的Origins、Headers两个属性)。

然后在Startup.cs的ConfigureServices方法中增加如下代码:

services.Configure<Ftp>(Configuration.GetSection("Ftp"));

services.Configure<Cors>(Configuration.GetSection("Cors"));

然后在Controller中读取配置:

public class HomeController : Controller

{

       private readonlyILogger<HomeController> _logger;

       private readonlyIConfiguration config;

       private readonlyIOptionsSnapshot<Ftp> ftpOpt;

       private readonlyIOptionsSnapshot<Cors> corsOpt;

 

       publicHomeController(ILogger<HomeController> logger, IConfiguration config,IOptionsSnapshot<Ftp> ftpOpt, IOptionsSnapshot<Cors> corsOpt)

       {

              _logger = logger;

              this.config =config;

              this.ftpOpt =ftpOpt;

              this.corsOpt =corsOpt;

       }

 

       public IActionResultIndex()

       {

              string redisCS = config.GetSection("RedisConnStr").Get<string>();

              ViewBag.s =redisCS;

              ViewBag.ftp =ftpOpt.Value;

              ViewBag.cors =corsOpt.Value;

              return View();

       }

}

关于把读取出来的配置如何使用就不再介绍了。我这里只是把配置显示到界面上。你可以把配置修改后,再刷新界面,就可以看到修改后的配置。

 

4、 源码原理讲解

项目github地址:

https://github.com/yangzhongke/Zack.AnyDBConfigProvider,最核心的类是DBConfigurationProvider。

.NET中自定义配置提供者都要实现IConfigurationProvider接口,一般都直接继承自ConfigurationProvider这个抽象类。ConfigurationProvider中最重要的方法就是Load(),自定义配置提供者都要实现Load方法来加载数据,加载的数据按照键值对的形式保存到Data属性中。Data属性是IDictionary<string,string>类型,Key为配置的名字,遵循.NET的“多层级数据的扁平化”规范。如果配置项发生了改变则调用OnReload()方法来通知监听配置改变的代码。

上面介绍了ConfigurationProvider类的基本工作机制,我们下面再分析一下Zack.AnyDBConfigProvider中的DBConfigurationProvider类的主要代码的原理。

首先是DBConfigurationProvider类的构造函数:

ThreadPool.QueueUserWorkItem(obj => {

       while (!isDisposed)

       {

              Load();

              Thread.Sleep(interval);

       }

});

       可以看到,如果启用了ReloadOnChange,那么每隔指定的时间,就会调用Load重新加载数据。

       下面是Load方法的主要代码:

public override void Load()

{

       base.Load();

       var clonedData =Data.Clone();

       string tableName =options.TableName;

       try

       {

              lockObj.EnterWriteLock();

              Data.Clear();               

              using (var conn =options.CreateDbConnection())

              {

                     conn.Open();

                     DoLoad(tableName,conn);

              }

       }

       catch(DbException)

       {

              //if DbExceptionis thrown, restore to the original data.

              this.Data =clonedData;

              throw;

       }

       finally

       {

              lockObj.ExitWriteLock();

       }

       //OnReload cannot bebetween EnterWriteLock and ExitWriteLock, or "A read lock may not beacquired with the write lock held in this mode" will be thrown.

       if(Helper.IsChanged(clonedData, Data))

       {

              OnReload();

       }

}

       Load方法的主要思路就是:首先创建Data属性的一个拷贝clonedData,用于稍后比较“数据是否修改了”。因为如果启用了ReloadOnChange,那么Load是在一个线程中被定期调用的,而读取配置的代码最终会调用TryGet方法来读取配置,为了避免TryGet读到Load加载一半的数据造成数据混乱,因此需要使用锁来控制读写的同步。因为通常读的频率高于写的频率,为了避免用普通的锁造成的性能问题,这里使用ReaderWriterLockSlim类来实现“只允许一个线程写入,但是允许多个线程读”。把加载配置写入Data属性的代码放到EnterWriteLock()、ExitWriteLock()之间,而把读取配置的代码(见TryGet方法),用EnterReadLock()和ExitReadLock()包裹起来即可。

       需要注意,在Load方法中,一定要注意把OnReload()放到ExitWriteLock()之后,否则会导致运行时报“A read lock maynot be acquired with the write lock held in this mode”异常。因为OnReload方法会导致程序调用TryGet读取数据,而TryGet中用了“读锁”,这样就造成了“写锁”中嵌套“读锁”这个默认不允许的行为。

       在DoLoad方法中,会从数据库中读取数据加载到Data中。在Load方法的最后,就会把之前保存的Data属性的拷贝值clonedData和加载之后的新的Data属性值比较一下,如果发现数据有变化,就调用OnReload()通知“数据变化了,来加载新数据吧”。

       DoLoad方法中就是加载配置的值到Data属性了,虽然代码比较多,但是逻辑并不复杂,主要就是根据“多层级数据的扁平化”规范来解析和加载数据。因为我之前对于这个规范没有吃透,导致走了一些弯路。这块也是我的这个开源项目的一个亮点,因为如果只是按照“多层级数据的扁平化”规范来保存配置的话,数据库中的name就必须“Ftp:IP”、“Ftp:UserName”、“Cors:Origins:0”、“Cors:Origins:1”、“Cors:Origins:2”这样的方式写,但是经过我的处理,配置的值就可以用可读性非常强的json格式了(当然仍然兼容严格的“多层级数据的扁平化”规范)。

 

5、 结论

Zack.AnyDBConfigProvider是一个可以用数据库做配置中心服务器的开源库,让你可以在不增加额外的配置中心服务器的情况下,让项目具备简单的版本管理的配置中心,而且以一种可读性很强的格式来进行配置。希望这个开源项目能够帮助大家,欢迎使用过程中反馈问题,如果感觉好用,欢迎推荐给其他朋友。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Abp Vnext Pro 的 Vue 实现版本

Abp Vnext Pro 的 Vue 实现版本开箱即用的中后台前端/设计解决方案知识点.Net Core5.0Abp Vnext 4.x ,Ant Design, Vue2.xMysql,Redis,Hangfire,ES(日志可选),Nocas(可选,未集成,计划中),RabbitMq(未集成,计划中)微服务架构设计, DDD 实践容器化 CI CD系统功能用户管理角色管理…

2018年,该转行AI工程师吗?

如此火爆的AI&#xff0c;会不会像Android和iOS一样&#xff0c;五年后归于平淡&#xff1f;转型AI真的有必要吗&#xff1f;2017年&#xff0c;AI就像一个点石成金的神器&#xff0c;所有的行业&#xff0c;任何的创业&#xff0c;抑或是职位背景&#xff0c;只要沾着这个词&a…

使用 Tye 辅助开发 dotnet 应用程序

newbe.pro 已经给我们写了系列文章介绍Tye 辅助开发k8s 应用&#xff1a;使用 Tye 辅助开发 k8s 应用竟如此简单&#xff08;一&#xff09;使用 Tye 辅助开发 k8s 应用竟如此简单&#xff08;二&#xff09;使用 Tye 辅助开发 k8s 应用竟如此简单&#xff08;三&#xff09;使…

你感兴趣的大学专业真相 | 16万人参与调查,看完80%都哭了

俗话说&#xff0c;隔行如隔山学科专业也是如此你以为我的专业十分高大上事实却是我也不知道我在学啥下面就由各个专业的同学为你揭秘他们专业的真相~人文社会类法学▼泛泛SaMa&#xff1a;在我们法学界&#xff0c;唯有秃头这件事&#xff0c;不分男女不墮紅塵&#xff1a;第一…

通过 .NET NativeAOT 实现用户体验升级

前言TypedocConverter 是我先前因帮助维护 monaco-editor-uwp 但苦于 monaco editor 的 API 实在太多&#xff0c;手写 C# 的类型绑定十分不划算而发起的一个项目。这个工具可以将 typedoc 根据 TypeScript 生成的 JSON 文件直接生成对应的 C# 类型绑定代码&#xff0c;并提供完…

序列每天从0开始_序列比对(十一)——计算符号序列的全概率

前文介绍了在知道符号序列后用viterbi算法求解最可能路径。本文介绍了如何使用前向算法和后向算法计算符号序列的全概率。如果一个符号序列中每个符号所对应的状态是已知的&#xff0c;那么这个符号序列出现的概率是容易计算的&#xff1a;但是&#xff0c;如果一个符号序列中每…

SQL 2005 使用row_number来分页

今天研究了一下row_number,用它来返回特定行的记录感觉是非常方便的&#xff0c;所以就做了个分页的存储过程&#xff0c;但不知道性能较之top和游标之类的那个好 代码 createprocedure[dbo].[proc_TestPage]--表名 tablenamenvarchar(255), --排序字段 sortcolumnnvarchar(255…

债务大爆发,中国30%家庭不堪一击!

债务大爆发&#xff0c;30%中国家庭“不堪一击”&#xff01;从2007年到2016年&#xff0c;中国家庭的债务率翻了一倍多。已经有超过1/3的家庭属于高负债家庭。前不久&#xff0c;深圳中兴网信科技有限公司的一研发组主管欧某&#xff0c;以最决绝的方式&#xff0c;从中兴通迅…

腾讯35k招.NET Core开发,深扒这些技术要求 真的很难吗?

3月草长莺飞&#xff0c;3月招聘满天飞&#xff0c;各种高薪招聘更是心里种草&#xff0c;前几天分享了腾讯牛年35k的.NET Core招聘需求&#xff0c;分享了一波资料深受好评&#xff0c;本着再接再厉的精神&#xff0c;本文继续为大家上干货&#xff0c;补齐腾讯的各种要求。新…

【直观理解】为什么梯度的负方向是局部下降最快的方向?

推荐阅读时间&#xff1a;8min~15min主要内容&#xff1a;为什么梯度的负方向是局部下降最快的方向&#xff1f;刚接触梯度下降这个概念的时候&#xff0c;是在学习机器学习算法的时候&#xff0c;很多训练算法用的就是梯度下降&#xff0c;然后资料和老师们也说朝着梯度的反方…

紫光物联linux登录账号,紫光展锐打造操作系统生态,赋能万物互联智能时代

本周&#xff0c;以“象由芯生科技服务人民”为主题的2020紫光展锐市场峰会重磅开启&#xff0c;广大生态合作伙伴共聚一堂&#xff0c;共话数字世界新未来。在今天举办的“操作系统OS研讨会”上&#xff0c;来自紫光展锐工程一线的架构师带来了一场整个操作系统领域的饕餮盛宴…

一行命令搭建内部的管道

在上一篇《边缘计算k8s集群之SuperEdge》文章中&#xff0c;笔者基于ECK搭建了边缘集群并添加了节点。通过边缘集群&#xff0c;我们可以很方便的管理各个地域的节点&#xff0c;本地、各云厂商的机房、客户所在地、海外的都可以。在本篇内容&#xff0c;我们将讲述如何使用ips…

ArchiMate - 发布【企业架构语言ArchiMate v0.5.pdf】

在《年度总结和计划&#xff1a;去年4个1&#xff0c;今年5个1》中说过今年我准备在项目组引入1个架构语言&#xff08;ArchiMate&#xff09;&#xff0c;为了便于大家学习&#xff0c;我把一些内容集成一本电子书&#xff0c;目前发布0.5版本&#xff0c;后续还会不断更新&am…

那些有趣/用的 Python 库

图片处理pip install pillowfrom PIL import Imageimport numpy as npa np.array(Image.open(test.jpg))b [255,255,255] - aim Image.fromarray(b.astype(uint8))im.save(new.jpg)youtube-dl下载国外视频pip install youtube-dl #直接安装youtube-dlpip install -U youtube…

linux系统刷分辨率,Linux下设置其分辨率及刷新率

行频&#xff1a;行频又称为“水平扫描频率”&#xff0c;指电子枪每秒在荧光屏上扫过的水平线的数量&#xff0c;其值等于“场频 垂直分辨率1.04”&#xff0c;单位为KHz(千赫兹)。行频是一个综合分辨率和场频的参数&#xff0c;该值越大&#xff0c;显示器可以提供的分辨率越…

.NET 5 部署在docker上运行

1、创建站点创建一个ASP.NET Core Web应用程序&#xff0c;选中启用Docker支持。自动帮我们创建一个Dockerfile文件。2、编写Dockerfile文件dockerfile是一个文件格式的配置文件&#xff0c;用户可以使用dockerfile来快速构建自定义的镜像。由一行行命令语句组成&#xff0c;并…

Nexus:一站式私有仓库管理(NuGet、Maven、npm、Docker)

我们在日常开发中经常需要使用到私有仓库&#xff0c;比如 dotNET 中的 NuGet、Java 中的 Maven、前端的 npm&#xff0c;还有 Docker 镜像&#xff0c;每一个私有仓库各自管理&#xff0c;维护起来比较麻烦&#xff0c;而 Nexus 可以将其统一起来。本文将介绍 Nexus 的安装以及…

众里寻 Bug 千百度,蓦然回首,它却在隔壁老张处……

程序员与 Bug 是一对矛盾的存在&#xff0c;程序员既要在解决 Bug 中获得成就感&#xff0c;同时也讨厌 Bug 本身的存在。“程序不息&#xff0c;Bug 不止”&#xff0c;程序员在与 Bug 的斗争中&#xff0c;也有很多有趣的事情发生&#xff0c;我们整理了一些程序员在调试 Bug…

Blazor WASM 实现人民币大写转换器

点击上方蓝字关注“汪宇杰博客”导语.NET 5 正式发布已经有一段时间了&#xff0c;其中 Blazor 技术是该版本的亮点之一。作为微软技术的被坑者&#xff0c;年少的我曾经以为 SilverLight 能血虐 Flash&#xff0c;Zune 能团灭 iPod&#xff0c;WP 能吊打 iPhone&#xff0c;UW…

金山安全实验室公布中国互联网六大类钓鱼网站

金山安全实验室公布中国互联网六大类钓鱼网站金山安全实验室研究人员对中国大陆钓鱼网站的普遍特征进行分析&#xff0c;发现以下六个领域最容易被钓鱼网站***&#xff1a;1.QQ十年庆典、QQ抽奖、腾讯活动&#xff1b;2.证券、股票分析、黑庄、理财专家等财经领域&#xff1b;3…