.net core的配置介绍(二):自定义配置(Zookeeper,数据库,Redis)
上一篇介绍了.net core的配置原理已经系统提供的一些常用的配置,但有时我们的配置是存放在Zookeeper,DB,Redis中的,这就需要我们自己去实现集成了。
这里再介绍几个我们用的多的配置集成方案,可以加强我们对.net core配置机制的理解。
Zookeeper
.net core集成使用Zookeeper的做法在之前的博客中已经介绍过了,祥看:Zookeeper基础教程(六):.net core使用Zookeeper
通用配置
对于配置,数量一般不会很多,放在内存中一般时候是可以接受的,这样一方面方便使用,另一方面避免了频繁访问数据库或者Redis等第三方设置带来的性能消耗。
其实,对于数据在数据库或者redis等其他存储设备上的集成,官方给我们提供了一个InMemory解决方案,也就是上一篇说到的从内存集合中获取配置的方案。在使用时,我们只需要在程序启动时从数据库或者Redis等位置将数据读取出来,然后已InMemory的方式集成进去就行了。但是这样做有个不足,就是当配置被修改时,我们需要重新启动应用服务才能生效,这样就是放弃了.net core配置重新加载机制,因此我们需要自己去实现这个集成。
通过上述,我们可以认为数据库和Redis是同一类东西,但是又不能使用InMemory解决方案,因此,为满足以后的其它第三方配置需求,我们希望有一种通用的配置提供者,它需要满足两点:
1、提供配置所需的数据
2、能触发配置的重新加载更新
为满足这两点,我们可以提供一个接口,如:
public interface IDataProvider
{
/// <summary>
/// 获取配置数据
/// </summary>
/// <returns></returns>
IDictionary<string, string> Process();
/// <summary>
/// 开启监听,决定什么时候触发重新加载配置
/// </summary>
/// <param name="trigger"></param>
void Watch(Action trigger);
}
在上一篇我们已经讲到,集成配置需要我们自己实现两个接口:IConfigurationSource 和 IConfigurationProvider 。另外还提到,如果我们的配置来自其它文件,推荐分别继承 FileConfigurationSource 和 FileConfigurationProvider 两个抽象类,否则推荐自己实现 IConfigurationSource 接口,但是 IConfigurationProvider 接口的实现类继承 ConfigurationProvider 抽象类,显然数据库和Redis之类的都不是文件,于是我们可以有这样两个实现类:
public class CommonConfigurationSource : IConfigurationSource
{
public Type DataProviderType { get; set; }
/// <summary>
/// 是否监控源数据变化
/// </summary>
public bool ReloadOnChange { get; set; } = true;
/// <summary>
/// 加载延迟
/// </summary>
public int ReloadDelay { get; set; } = 250; public IConfigurationProvider Build(IConfigurationBuilder builder)
{
if (!typeof(IDataProvider).IsAssignableFrom(DataProviderType))
{
throw new ArgumentException("Data Provider Type must implement IDataProvider");
} return new CommonConfigurationProvider(this);
}
}
CommonConfigurationSource
public class CommonConfigurationProvider : ConfigurationProvider, IDisposable
{
ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
CommonConfigurationSource commonConfigurationSource;
IDisposable _changeTokenRegistration;
IDataProvider dataProvider; public CommonConfigurationProvider(CommonConfigurationSource commonConfigurationSource)
{
this.commonConfigurationSource = commonConfigurationSource;
this.dataProvider = Activator.CreateInstance(commonConfigurationSource.DataProviderType) as IDataProvider; if (commonConfigurationSource.ReloadOnChange)
{
dataProvider.Watch(() =>
{
OnReload();
});
_changeTokenRegistration = ChangeToken.OnChange(
() => GetReloadToken(),
() =>
{
Thread.Sleep(commonConfigurationSource.ReloadDelay);
Load();
});
}
} /// <summary>
/// 加载配置
/// </summary>
public override void Load()
{
Data = dataProvider.Process();
}
/// <summary>
/// 释放
/// </summary>
public void Dispose()
{
_changeTokenRegistration?.Dispose();
}
}
CommonConfigurationProvider
还没有完,同上一篇介绍的.net core自带的配置一样,我们还需要提供拓展方法去集成:
public static class CommonConfigurationExtensions
{
/// <summary>
/// 集成通用配置
/// </summary>
/// <param name="builder"></param>
/// <param name="dataProviderType"></param>
/// <param name="reloadOnChange"></param>
/// <param name="reloadDelay"></param>
/// <returns></returns>
public static IConfigurationBuilder AddCommonConfiguration(this IConfigurationBuilder builder, Type dataProviderType, bool reloadOnChange = false, int reloadDelay = 250)
{
return builder.Add<CommonConfigurationSource>(source =>
{
source.DataProviderType = dataProviderType;
source.ReloadDelay = reloadDelay;
source.ReloadOnChange = reloadOnChange;
});
}
/// <summary>
/// 集成通用配置
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="builder"></param>
/// <param name="reloadOnChange"></param>
/// <param name="reloadDelay"></param>
/// <returns></returns>
public static IConfigurationBuilder AddCommonConfiguration<T>(this IConfigurationBuilder builder, bool reloadOnChange = false, int reloadDelay = 250) where T : IDataProvider, new()
{
return builder.AddCommonConfiguration(typeof(T), reloadOnChange, reloadDelay);
}
}
CommonConfigurationExtensions
接下来我们看看怎么使用。
数据库(MySql)
数据库我们以mysql为例,接下来我们只需要实现IDataProvider接口,用来实现配置数据的提供和配置的重新加载,比如我的数据中有数据:
而接下来我们的IDataProvider接口实现类是:
/// <summary>
/// 要求存在空构造函数
/// </summary>
public class MysqlDataProvider : IDataProvider
{
private IDbConnection GetDbConnection()
{
string connectionString = @"Server=192.168.209.128;Port=3306;Database=test;Uid=root;Pwd=123456"; return new MySqlConnector.MySqlConnection(connectionString);
} /// <summary>
/// 获取配置数据
/// </summary>
/// <returns></returns>
public IDictionary<string, string> Process()
{
using (var con = GetDbConnection())
{
if (con.State != ConnectionState.Open)
{
con.Open();
} using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT `Key`,`Value` FROM Configurations";
var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
IDictionary<string, string> dict = new Dictionary<string, string>();
while (reader.Read())
{
var key = reader.GetString(0);
var value = reader.GetString(1);
dict[key] = value;
}
return dict;
}
}
}
/// <summary>
/// 开启监听,决定什么时候触发重新加载配置
/// </summary>
/// <param name="trigger"></param>
public void Watch(Action trigger)
{
//表示什么时候出发一次配置重新加载
//比如定时加载
var timer = new System.Timers.Timer();
timer.Interval = 1000 * 60;//一分钟加载一次
timer.Elapsed += new System.Timers.ElapsedEventHandler((sender, e) =>
{
timer.Enabled = false;
trigger.Invoke();
timer.Enabled = true;
});
timer.Start();
}
}
其中,在实现Watch方法时,我们采用了一个定时间,定时从数据库获取配置,当然,最佳做法是使用一条消息总线来实现,比如采用消息队列等,这里只是简单的说明介绍一下。
接下来看看怎么使用:
static void Main(string[] args)
{
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddCommonConfiguration<MysqlDataProvider>(true);
var configuration = builder.Build();
do
{
Console.Write("请输入指令:");
var line = Console.ReadLine();
if (line == "reload" || line == "r")
{
configuration.Reload();
}
else if (line == "print" || line == "p")
{
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}
}
while (true);
}
如果是.net core webapi或者mvc,只需要:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
} public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>(); webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddCommonConfiguration<MysqlDataProvider>(true); });
}); }
Program
运行后输入p:
接下来可以修改数据库中的配置:
接下来只需要等1分钟(MysqlDataProvider 中Watch方法中的定时器1分钟刷新),或者直接输入r,使用IConfigurationRoot的Reload方法强制刷新。
刷新之后,再输入p,查看到配置已自动更新了。
可以看到,数据库配置以被重新加载。
Redis
接下来说说Redis,假如我们的Redis有如下结构的配置,其中Config表示的是前缀,表明这个前缀下的RedisKey都是配置:
在集成之前,同样的,我们需要先实现IDataProvider接口:
public class RedisDataProvider : IDataProvider
{
StackExchange.Redis.ConnectionMultiplexer connectionMultiplexer;
public StackExchange.Redis.IConnectionMultiplexer GetConnectionMultiplexer()
{
if (connectionMultiplexer == null)
{
var configurationOptions = new StackExchange.Redis.ConfigurationOptions();
configurationOptions.DefaultDatabase = 0;
configurationOptions.EndPoints.Add("192.168.209.128:6379");
connectionMultiplexer = StackExchange.Redis.ConnectionMultiplexer.Connect(configurationOptions);
}
return connectionMultiplexer;
} public IDictionary<string, string> Process()
{
var connectionMultiplexer = GetConnectionMultiplexer();
int database = 0;
var server = connectionMultiplexer.GetServer(connectionMultiplexer.GetEndPoints().First());
var db = connectionMultiplexer.GetDatabase(database);
IDictionary<string, string> dict = new Dictionary<string, string>();
int pageSize = 10;
int pageOffset = 0;
string prefix = "Config";//加载配置节点的前缀
while (true)
{
var keys = server.Keys(
database: database,
pattern: new StackExchange.Redis.RedisValue(prefix + "*"),
pageSize: pageSize,
pageOffset: pageOffset
);
if (keys.Count() == 0) break; foreach (var key in keys)
{
try
{
var value = db.StringGet(key);
dict[key.ToString().Substring(prefix.Length).TrimStart(':')] = value.ToString();
}
catch
{
continue;
}
} pageOffset += pageSize;
}
return dict;
} public void Watch(Action trigger)
{
//采用Redis的发布订阅模式进行监听
var connectionMultiplexer = GetConnectionMultiplexer();
var subscriber = connectionMultiplexer.GetSubscriber();
subscriber.Subscribe(new StackExchange.Redis.RedisChannel("Watch_RedisChannel", StackExchange.Redis.RedisChannel.PatternMode.Auto), (channel, message) =>
{
trigger?.Invoke();
});
}
}
在介绍数据库的使用时,提到在Watch中推荐使用消息总线来实现重新加载,于是乎,我们利用Redis的订阅与发布来实现这个功能,真是一举两得
另外,在开发过程中,我们应该讲Redis中配置与缓存等数据分开,比如使用不同的database来存放,这样允许我们在读取配置的时候避免读取到大量的缓存数据而影响到性能。
接下来看看使用:
static void Main(string[] args)
{
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddCommonConfiguration<RedisDataProvider>(true);
var configuration = builder.Build();
do
{
Console.Write("请输入指令:");
var line = Console.ReadLine();
if (line == "reload" || line == "r")
{
configuration.Reload();
}
else if (line == "publish")
{
var configurationOptions = new StackExchange.Redis.ConfigurationOptions();
configurationOptions.DefaultDatabase = 0;
configurationOptions.EndPoints.Add("192.168.209.128:6379");
var connectionMultiplexer = new RedisDataProvider().GetConnectionMultiplexer();
connectionMultiplexer.GetSubscriber().Publish("Watch_RedisChannel", "Reload");
}
else if (line == "print" || line == "p")
{
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}
}
while (true);
}
如果是.net core webapi或者mvc,只需要:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
} public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>(); webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddCommonConfiguration<RedisDataProvider>(true);
});
}); }
Program
运行后输入p:
接下来,我们修改Redis中的配置,比如使用RedisManager工具修改,比如修改一个配置:
保存后,在控制台输入publish发布重新加载的订阅消息,或者输入r,使用IConfigurationRoot的Reload方法强制刷新。
刷新之后,再输入p,查看到配置已自动更新了。
可见集成完成!
总结
通过这里的例子,应该能对.net core提供的配合有个更完整的了解。这里虽然给出了一种通用集成配置的方案,比如数据库和Redis的集成,但是还需要读者提供一个IDataProvider接口的实现类,不过这也算是一种练习吧。
.net core的配置介绍(二):自定义配置(Zookeeper,数据库,Redis)的更多相关文章
- AgileEAS.NET SOA 中间件平台5.2版本下载、配置学习(二):配置WinClient分布式运行环境
一.前言 AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台.用于帮助中小型软件企业建立一条适合市 ...
- VS2012 常用web.config配置解析之自定义配置节点
在web.config文件中拥有一个用户自定义配置节点configSections,这个节点可以方便用户在web.config中随意的添加配置节点,让程序更加灵活(主要用于第三方插件的配置使用) 自定 ...
- Ubuntu配置OpenStack 二:配置时间同步NTP和安装数据库Maridb以及问题总结
继上一节Ubuntu配置OpenStack 一:配置主机环境,下面继续为安装时间同步,以及配置openstack的安装包源和安装数据库Maridb.(全文截图都是由自己徒手搭建完成并且截图) 一.安装 ...
- nginx介绍(二) - 默认配置
前言 前面, 在浏览器中, 输入linux 的ip, 出现了以下页面: 那这个页面在哪里呢? 一. 工具 notepad++ 在进入主题之前, 先来介绍下, 一会使用到的工具. 在notepad++里 ...
- flume安装及配置介绍(二)
注: 环境: skylin-linux Flume的下载方式: wget http://www.apache.org/dyn/closer.lua/flume/1.6.0/apache-flume-1 ...
- ODI中通过配置表和自定义逆向工程获取数据库信息
自定义逆向工程RKM 从配置表meta_db, meta_table, meta_column, meta_key中获取生产库的元数据信息.
- MongoDB副本集配置系列二:配置MongoDB副本集
接上一篇博客: http://www.cnblogs.com/xiaoit/p/4479066.html 1:首先创建3台虚拟机作为配置环境 IP1:192.168.91.128 IP2:192.16 ...
- 接口--全局异常配置--异常处理handle自定义配置
在重写了异常处理的handle类之后需要配置配置文件中handle的路径:
- [ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]
提到"配置"二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...
- [ASP.NET Core 3框架揭秘] 配置[4]:将配置绑定为对象
虽然应用程序可以直接利用通过IConfigurationBuilder对象创建的IConfiguration对象来提取配置数据,但是我们更倾向于将其转换成一个POCO对象,以面向对象的方式来使用配置, ...
随机推荐
- Java 8实现BASE64编解码
Java一直缺少BASE64编码 API,以至于通常在项目开发中会选用第三方的API实现.但是,Java 8实现了BASE64编解码API,它包含到java.util包.下面我会对Java 8的BAS ...
- docker之镜像制作
#:下载镜像并初始化系统 root@ubuntu:~# docker pull centos #:创建目录 root@ubuntu:/opt# mkdir dockerfile/{web/{nginx ...
- 实现new Date(), 获取当前时间戳
JS 获取时间戳: 我相信大家找了很久了吧! 希望我写的这个对您有些帮助哦~ 大家是不是以为时间戳是关于时间的,都去 new Date() 里面找方法了啊,我来告诉你们正确的吧 其实大家用 JS 里的 ...
- promise ,async 小记
Promise Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及该异步操作的结果值. 摇色子游戏,随机1-6的一个整数,并且将其返回. function fn() { retur ...
- python实现skywalking邮件告警webhook接口
1.介绍 Skywalking可以对链路追踪到数据进行告警规则配置,例如响应时间.响应百分比等.发送警告通过调用webhook接口完成.webhook接口用户可以自定义. 2.默认告警规则 告警配置文 ...
- 访问者模式(Visitor Pattern)——操作复杂对象结构
模式概述 在软件开发中,可能会遇到操作复杂对象结构的场景,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式,还有可能增加新的处理 ...
- yum的epel源
目录 一.centos7的源 一.centos7的源 以下一个不行,可以试试另一个 rpm -ivh https://mirrors.aliyun.com/epel/epel-release-late ...
- STL 较详尽总结
STL就是Standard Template Library,标准模板库.这可能是一个历史上最令人兴奋的工具的最无聊的术语.从根本上说,STL是一些"容器"的集合,这些" ...
- HGAME pwn ROP_LEVEL2
花了好多天,终于把这个题彻底弄懂了...自己太菜了 下载文件,首先checksec检查一下保护. 只开启了堆栈不可执行,接下来拖到IDA看一下C的伪代码. 大致先让你输入,然后再次让你输入. 第 ...
- 摘要任务工期计算(Project)
<Project2016 企业项目管理实践>张会斌 董方好 编著 先说一个好消息:摘要工期是可以自动计算的. 比如A1.A2.A3.A4四个任务,工期如下图安排: 而他们的摘要任务,就不再 ...