ASP.NET Core 配置源:实时生效

在之前的文章 ASP.NET Core 自定义配置源 和 ASP.NET Core etcd 配置源 中主要是介绍如何实现自定义的配置源,但不论内置的和自定义的配置源,都会面临如何使配置修改后实时生效的问题(修改配置后在不重启服务的情况下能马上生效)。在 ASP.NET Core etcd 配置源 的最后部分其实有用到 IOptionsSnapshot Options 快照的方式获取到最新配置,但其实这里依然不是实时数据,所以本文将继续深入介绍配置使用方式及内部处理机制。(以下测试代码基于 ASP.NET Core 3.1)。

Configuration 模式

在 ASP.NET Core Web API 应用程序中 IConfiguration 服务默认已以单例模式注入到 services 中,含以下 ConfigurationProvider 中的配置信息,同时对 appsettings.jsonappsettings.Development.json 已设置 reloadOnChange 为 true,即文件内容有变化将自动更新到 IConfiguration 的对象中:

代码如下:

public class TestController : ControllerBase
{private readonly IConfiguration _configuration;public TestController(IConfiguration configuration){_configuration = configuration;}[HttpGet]public string Get(){return _configuration["Name"];}
}

在不重启服务的情况下修改 Name 的值,重新调用接口将会马上获取到最新值,所以通过从 IConfiguration 中获取配置可以做到实时生效,这主要是因为在 JsonConfigurationProvider 的实现中当设置 reloadOnChange 为 true,则会监控文件的变化,一旦有变则重新加载当前 Provider 的配置信息,整个原理和 ASP.NET Core etcd 配置源 的实现类似,JsonConfigurationProvider 源码[1]

Options 模式

Options 模式 也是比较常用的一种配置使用方式,通过将某个 Section 的配置信息以对象的方式注入到服务中,在程序中使用将更加方便与形象,在 Options 模式 主要有 3 种使用方式,分别是:IOptionsIOptionsSnapshotIOptionsMonitor

使用方式及表现

首先在 appsettings.json 中添加如下配置:

{..."UserOption": {"Name": "beck"}
}

然后在 startup.csConfigureServices 注册配置服务:

public void ConfigureServices(IServiceCollection services)
{services.Configure<UserOption>(Configuration.GetSection("UserOption"));services.AddControllers();
}

使用方式如下:

public class TestController : ControllerBase
{private readonly IOptions<UserOption> _options;public TestController(IOptions<UserOption> options){_options = options;}[HttpGet]public void Get(){Console.WriteLine(_options.Value.Name);Thread.Sleep(5000); // 等待过程中,手动修改配置文件Console.WriteLine(_options.Value.Name);}
}

分布使用 IOptionsIOptionsSnapshotIOptionsMonitor(需改为 _options.CurrentValue) 进行测试,每种测试对配置文件中 Name 的值进行调整,具体表现结果如下:

  • IOptions:本次请求内修改不会生效,重新请求也不会生效,需重启服务;

  • IOptionsSnapshot:本次请求内修改不会生效,重新请求生效;

  • IOptionsMonitor:实时生效;

处理机制分析

基于以上表现结果,我们接下来通过 Options 源码[2] 来分析其本质原因。

首先在服务启动阶段 HostBuilder 的 Build 方法中调用 services.AddOptions() 进行 Options 相关服务的注册,代码如下:

public static IServiceCollection AddOptions(this IServiceCollection services)
{if (services == null){throw new ArgumentNullException(nameof(services));}services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));return services;
}

从代码中可以明显的看出 IOptionsIOptionsSnapshot 对应的实现都是OptionsManagerIOptions 的生命周期是 Singleton 模式,IOptionsSnapshot 的生命周期是 Scoped 模式,所以这个就比较好解释为什么 IOptions 方式下配置调整后需要重启才能生效,而 IOptionsSnapshot 是每次请求内不变,重新请求会变化的原因了。

另外 IOptionsMonitor 的具体实现是 OptionsMonitor,生命周期是 Singleton 模式。同时还包含了 IOptionsFactoryIOptionsMonitorCache 两个服务的注册,它们也是 Options 模式 下核心部分。

这几个服务之间的关系如下:

IOptionsFactory 主要负责创建 TOptions 类型的具体对象,核心代码如下:

public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{public TOptions Create(string name){var options = new TOptions();foreach (var setup in _setups){if (setup is IConfigureNamedOptions<TOptions> namedSetup){namedSetup.Configure(name, options);}else if (name == Options.DefaultName){setup.Configure(options);}}foreach (var post in _postConfigures){post.PostConfigure(name, options);}if (_validations != null){var failures = new List<string>();foreach (var validate in _validations){var result = validate.Validate(name, options);if (result.Failed){failures.Add(result.FailureMessage);}}if (failures.Count > 0){throw new OptionsValidationException(name, typeof(TOptions), failures);}}return options;}
}

其中 _setups 的来源即最开始 services.Configure 注册的配置服务,_postConfigures 的来源是以 services.PostConfigure 方式注册的配置服务(本例中未使用到),它们的区别是 PostConfigure 注册的服务将在 Configure 之后执行。_validations 是参数合法性验证集合,如果有需要,可以在服务注册时指定。

OptionsManager 同时是 IOptionsIOptionsSnapshot 的实现,内部通过 OptionsCache 缓存 IOptionsFactory 创建的具体 TOptions 对象,区别在于创建出的具体对象生命周期不一样,核心代码如下:

public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
{public TOptions Value{get{return Get(Options.DefaultName);}}public virtual TOptions Get(string name){name = name ?? Options.DefaultName;return _cache.GetOrAdd(name, () => _factory.Create(name));}
}

OptionsMonitorIOptionsMonitor 的实现,内部通过 IOptionsMonitorCache 缓存 IOptionsFactory 创建的具体 TOptions 对象,同时对于采用 IConfiguration 作为数据源类型的,通过 ChangeToken.OnChange 监听变化并实时更新配置。

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions> where TOptions : class, new()
{public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache){foreach (var source in _sources){ChangeToken.OnChange<string>(() => source.GetChangeToken(),(name) => InvokeChanged(name),source.Name);}}private void InvokeChanged(string name){name = name ?? Options.DefaultName;_cache.TryRemove(name);var options = Get(name);if (_onChange != null){_onChange.Invoke(options, name);}}public TOptions CurrentValue{get => Get(Options.DefaultName);}public virtual TOptions Get(string name){name = name ?? Options.DefaultName;return _cache.GetOrAdd(name, () => _factory.Create(name));}
}

自实现 OptionsMonitor

Configuration 对象默认提供了 GetReloadToken 方法,所以我们也可以通过监听 Token 的变化自己实现类似 OptionsMonitor 的效果,毕竟有时候并不会选择 Configuration 模式Options 模式,以下是在控制台程序中的使用,假设使用 Autofac 作为 DI 容器,SetUserOption 内将可重新注册 UserOption 对象。

private static UserOption userOption;static void Main(string[] args)
{var configurationRoot = GetRoot();ChangeToken.OnChange(() => configurationRoot.GetReloadToken(), () =>{SetUserOption(configurationRoot);Console.WriteLine(userOption.Name);});Console.WriteLine("started");Console.ReadKey();
}private static IConfigurationRoot GetRoot()
{var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", true, true);return builder.Build();
}private static void SetUserOption(IConfigurationRoot configuration)
{userOption = configuration.GetSection("UserOption").Get<UserOption>();
}

参考资料

[1]

JsonConfigurationProvider 源码: https://github.com/aspnet/Configuration/blob/master/src/Config.Json/JsonConfigurationProvider.cs

[2]

Options 源码: https://github.com/aspnet/Options/tree/master/src/Microsoft.Extensions.Options

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

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

相关文章

分布式事务理论模型

分布式事务 事务的概念&#xff0c;我们第一想到的应该是数据库的事务。所谓数据库事务就是只作为单个逻辑工作单元执行多个数据库操作的时候&#xff0c;数据库需要保证要么都成功&#xff0c;要么都失败&#xff0c;它必须满足ACID特性&#xff0c;即&#xff1a; 原子性&…

[MySQL基础]数据库的相关概念

DB: 数据库(database):存储数据的“仓库”&#xff0c;它保存了一系列有组织的数据。 DBMS: 数据库管理系统(Database Management System):数据库是通过DBMS创建和操作的容器。 SQL: 结构化查询语言(Structure Query Language):专门用来与数据库通信的语言。 SQL的优点: 1.几…

Linq下有一个非常实用的SelectMany方法,很多人却不会用

在平时开发中经常会看到有些朋友或者同事在写代码时会充斥着各种for&#xff0c;foreach&#xff0c;这种程式代码太多的话阅读性特别差&#xff0c;而且还显得特别累赘&#xff0c;其实在FCL中有很多帮助我们提高阅读感的方法&#xff0c;而现实中很多人不会用或者说不知道&am…

.NET Core前后端分离快速开发框架(Core.3.1+AntdVue)

引言时间真快&#xff0c;转眼今年又要过去了。回想今年&#xff0c;依次开源发布了Colder.Fx.Net.AdminLTE(254Star)、Colder.Fx.Core.AdminLTE(335Star)、DotNettySocket(82Star)、IdHelper(47Star)&#xff0c;这些框架及组件都是本着以实际出发&#xff0c;实事求是的态度&…

数据结构与算法--查找与排序另类用法-旋转数组中的最小数字

查找与排序 查找 查找与排序都在程序设计中常被用到的算法。查找相对而言简单&#xff0c;一般都是顺序查找&#xff0c;二分查找&#xff0c;哈希表查找&#xff0c;和二叉排序树查找。其中二分查找是我必须熟悉的一种。哈希表和二叉排序树主要点在于他的数据结构而不是算法…

[MySQL基础]MySQL常见命令介绍

show databases; use 库名; show tables; show tables from 库名 select database(); create table 名字( id int, name varchar(20)); desc 表名; select * from 表名; insert into 表名 (a,b,…,f) values(1,2,3,…,7); update 库名 set name‘lilei’ where id1; delete f…

如何选择好公司

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份前几天写了一篇文章&#xff1a;怎么判断自己在不在一家好公司。附带了一个投票调查&#xff0c;结果如下图&#xff1a;调研结果有点点扎心&#xff0c;有点点出乎我的意料。61%的小伙伴&#xff0c;都认为自…

数据结构与算法--再谈递归与循环(斐波那契数列)

再谈递归与循环 在某些算法中&#xff0c;可能需要重复计算相同的问题&#xff0c;通常我们可以选择用递归或者循环两种方法。递归是一个函数内部的调用这个函数自身。循环则是通过设置计算的初始值以及终止条件&#xff0c;在一个范围内重复运算。比如&#xff0c;我们求累加…

同步异步多线程这三者关系,你能给面试官一个满意的回答吗?

前几天一位朋友去面试&#xff0c;面试官问了他同步&#xff0c;异步&#xff0c;多线程之间是什么关系&#xff0c;异步比同步高效在哪&#xff1f;多线程比单线程高效在哪&#xff1f;由于回答的不好&#xff0c;让我帮他捋一下&#xff0c;其实回答这个问题不难&#xff0c;…

分布式事务框架seata

seata 前两篇文中总结了一下分布式事务已经现阶段常用的解决方案&#xff0c;现在来讨论一下现有的分布式事务框架seata&#xff0c;点击此处是seata的官网seata致力于微服务框架下提供高性能和简单易用的分布式事务服务。它提供了AT&#xff0c;TCC&#xff0c;Saga &#xf…

[一起读源码]走进C#并发队列ConcurrentQueue的内部世界 — .NET Core篇

在上一篇《走进C#并发队列ConcurrentQueue的内部世界》中解析了Framework下的ConcurrentQueue实现原理&#xff0c;经过抛砖引玉&#xff0c;得到了一众大佬的指点&#xff0c;找到了.NET Core版本下的ConcurrentQueue源码&#xff0c;位于以下地址&#xff1a;https://github.…

Java语法基础50题训练(上)

题目1: 有两只老虎&#xff0c;一只体重为180kg&#xff0c;一只体重为200kg&#xff0c;请用程序实现判断两只老虎的体重是否相同。 代码如下: public class OperatorTest {public static void main (String[] args) {int w1 180;int w2 200;boolean ans w1 w2?true:f…

EFCore.Sharding(EFCore开源分表框架)

简介本框架旨在为EF Core提供Sharding(即读写分离分库分表)支持,不仅提供了一套强大的普通数据操作接口,并且降低了分表难度,支持按时间自动分表扩容,提供的操作接口简洁统一.源码地址:EFCore.SHarding引言读写分离分库分表一直是数据库领域中的重难点,当数据规模达到单库极限的…

分布式事务 -- seata框架AT模式实现原理

Seata AT 模式 上一节中我们提到AT模式是基于XA事务模型演变过来的&#xff0c;所以他的整体机制也是一个改进版本的两阶段提交协议。 第一阶段&#xff1a;业务数据和回滚日志记录在同一个本地事务中提交&#xff0c;释放本地锁和链接资源第二阶段&#xff1a;提交异步化&…

[Java基础]数据输入

Scanner使用的基本步骤: 1.导包: import java.util.Scanner;2.创建对象: Scanner sc new Scanner(System.in);3.接收数据: int i sc.nextInt();代码如下: import java.util.Scanner;public class OperatorTest {public static void main (String[] args) {//创建对象Scan…

k8s中流量分离以及资源隔离实战

源宝导读&#xff1a;明源云客的终端用户越来越多&#xff0c;也涌现出线上流量活动的场景&#xff0c;大量的访问和接口请求导致服务器出现较高负载。本文将介绍云客团队为了缓解服务器压力&#xff0c;通过K8S进行分流与资源隔离的实践过程。一、背景PaaS和B2C的主要客户云客…

怎样实现WPF Prism Module的国际化和本地化?

English | 简体中文上一篇有简单介绍主工程的国际化&#xff0c;使用的资源字典(XAML)实现的。这几天我添加了几个Prism模块(Module)&#xff0c;发现子模块使用资源字典的方式实现国际化和本地化不好做&#xff0c;没有找到比较好的参考文章&#xff0c;所以换了一种方式&…

dotNET Core 3.X 使用 Jwt 实现接口认证

在前后端分离的架构中&#xff0c;前端需要通过 API 接口的方式获取数据&#xff0c;但 API 是无状态的&#xff0c;没有办法知道每次请求的身份&#xff0c;也就没有办法做权限的控制。如果不做控制&#xff0c;API 就对任何人敞开了大门&#xff0c;只要拿到了接口地址就可以…

数据结构与算法--代码鲁棒性案例分析

代码鲁棒性 鲁棒是robust的音译&#xff0c;就是健壮性。指程序能够判断输入是否符合规范&#xff0c;对不合要求的输入能够给出合理的结果。容错性是鲁棒的一个重要体现。不鲁棒的代码发生异常的时候&#xff0c;会出现不可预测的异常&#xff0c;或者程序奔溃。由于鲁棒性非…