.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,一经查实,立即删除!

相关文章

abaqus质量缩放系数取值_ABAQUS/Explicit质量缩放(MASS SCALING)使用心得  [转simwe]...

概要准静态分析或某些动态分析中&#xff0c;少数尺寸较小的单元控制稳态时间增量&#xff0c;为提高计算效率&#xff0c;ABAQUS/Explicit常采用质量缩放的方法。质量缩放可用于&#xff1a;1、缩放整个模型&#xff0c;单个单元或单元组的质量2、多步分析中&#xff0c;缩放每…

linux 修改默认脚本,linux环境初始脚本

#//把下面内容&#xff0c;做成init.sh文件&#xff0c;放到liunx任意目录下&#xff0c;用bash ./init.sh#//执行&#xff0c;或用chmod -R 777 init.sh#//赋权后&#xff0c;再用./init.sh#//执行#!/bin/bash## 初始centOS系统环境# 1. 在线安装一些编译环境# 2. 关闭防火墙#…

python lock_python lock一步步教你理解Python装饰器

请仔细看我们的decorator实例。我们定义了一个接受单个参数some_func的名为outer的函数。在outer内部我们定义了一个名为inner的嵌套函数。inner函数打印一个字符串然后调用some_func&#xff0c;在#1处缓存它的返回值。some_func的值可能在每次outer被调用时不同&#xff0c;但…

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…

Exchange 2010 DAG转载

http://bisheng.blog.51cto.com/409831/270739转载于:https://blog.51cto.com/delkar/273586

linux tomcat8 启动慢,Linux系统下Tomcat8启动速度很慢的解决方法

前言最近在工作中遇到一个问题&#xff0c;在Linux下Tomcat 8启动很慢&#xff0c;且日志上无任何错误&#xff0c;在日志中查看到如下信息&#xff1a;Log4j:[2017-08-2715:47:11] INFO ReadProperty:172 - Loading properties file from class path resource [resources/jdbc…

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

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

postgresql点云las_PostgreSQL 调研

摘要本文介绍了Postgresql的功能和集群构建方案&#xff0c;及集群读写分离、负载均衡和分库功能的实现方法。1. 简介PostgreSQL是一个功能强大的、可靠性高、能保证数据完整性和一致性对象的开源关系数据库系统。它可以运行在所有主流的操作系统上&#xff0c;包括Linux、UNIX…

如何正确执行碎片整理或在群集共享卷(CSV)上使用CHKDSK命令

在使用CHKDSK和碎片整理操作期间&#xff0c;需要将CSV设置为维护模式&#xff0c;此时只有协调器访问磁盘。微软提供了cmdlet的Repair-ClusterSharedVolume选项&#xff0c;该选项可以自动将某个磁盘设置为维护模式&#xff0c;并执行chkdsk /磁盘整理操作&#xff0c;然后再关…

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

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

linux多内核调度,linux-kernel – 如何在Linux内核(Samsung Exynos5422)中实现异构多处理(HMP)调度?...

码&#xff1a;检查#ifdef CONFIG_SCHED_HMP下的源代码主要在kernel/sched/core.c之内A(不是那样)简要概述&#xff1a;big.LITTLE cpu可以配置为2种操作模式&#xff1a;> IKS – 内核切换器(也称为cpu迁移)> GTS – 全局任务调度(也称为big.LITTLE MP)GTS是异构的操作形…

通过 .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;补齐腾讯的各种要求。新…

handler原子锁_Linux的原子操作与同步机制

Linux的原子操作与同步机制并发问题现代操作系统支持多任务的并发&#xff0c;并发在提高计算资源利用率的同时也带来了资源竞争的问题。例如C语言语句“count;”在未经编译器优化时生成的汇编代码为。当操作系统内存在多个进程同时执行这段代码时&#xff0c;就可能带来并发问…

ddr2和ddr3的区别

DDR3与DDR2的不同之处 1、逻辑Bank数量 DDR2 SDRAM中有4Bank和8Bank的设计&#xff0c;目的就是为了应对未来大容量芯片的需求。而DDR3很可能将从2Gb容量起步&#xff0c;因此起始的逻辑Bank就是8个&#xff0c;另外还为未来的16个逻辑Bank做好了准备。 2、封装&#xff08;Pac…

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

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