上一篇介绍了.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)的更多相关文章

  1. AgileEAS.NET SOA 中间件平台5.2版本下载、配置学习(二):配置WinClient分布式运行环境

    一.前言 AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台.用于帮助中小型软件企业建立一条适合市 ...

  2. VS2012 常用web.config配置解析之自定义配置节点

    在web.config文件中拥有一个用户自定义配置节点configSections,这个节点可以方便用户在web.config中随意的添加配置节点,让程序更加灵活(主要用于第三方插件的配置使用) 自定 ...

  3. Ubuntu配置OpenStack 二:配置时间同步NTP和安装数据库Maridb以及问题总结

    继上一节Ubuntu配置OpenStack 一:配置主机环境,下面继续为安装时间同步,以及配置openstack的安装包源和安装数据库Maridb.(全文截图都是由自己徒手搭建完成并且截图) 一.安装 ...

  4. nginx介绍(二) - 默认配置

    前言 前面, 在浏览器中, 输入linux 的ip, 出现了以下页面: 那这个页面在哪里呢? 一. 工具 notepad++ 在进入主题之前, 先来介绍下, 一会使用到的工具. 在notepad++里 ...

  5. flume安装及配置介绍(二)

    注: 环境: skylin-linux Flume的下载方式: wget http://www.apache.org/dyn/closer.lua/flume/1.6.0/apache-flume-1 ...

  6. ODI中通过配置表和自定义逆向工程获取数据库信息

    自定义逆向工程RKM 从配置表meta_db, meta_table, meta_column, meta_key中获取生产库的元数据信息.

  7. MongoDB副本集配置系列二:配置MongoDB副本集

    接上一篇博客: http://www.cnblogs.com/xiaoit/p/4479066.html 1:首先创建3台虚拟机作为配置环境 IP1:192.168.91.128 IP2:192.16 ...

  8. 接口--全局异常配置--异常处理handle自定义配置

    在重写了异常处理的handle类之后需要配置配置文件中handle的路径:

  9. [ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]

    提到"配置"二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...

  10. [ASP.NET Core 3框架揭秘] 配置[4]:将配置绑定为对象

    虽然应用程序可以直接利用通过IConfigurationBuilder对象创建的IConfiguration对象来提取配置数据,但是我们更倾向于将其转换成一个POCO对象,以面向对象的方式来使用配置, ...

随机推荐

  1. python做一个http接口测试框架

    目录结构 project case#测试用例 suite#测试目录 logs#测试日志 papi#测试类 result#测试结果 setting.py#配置文件 1.日志类,用于测试时日志记录 pya ...

  2. 增大Oracle Virtualbox的磁盘空间

    https://blog.csdn.net/hiyachen/article/details/102131823 背景 在virtualbox中装好Linux以及Application之后,发现硬盘空 ...

  3. Spring Boot中使用Servlet与Filter

    在Spring Boot中使用Servlet,根据Servlet注册方式的不同,有两种使用方式.若使用的是Servlet3.0+版本,则两种方式均可使用:若使用的是Servlet2.5版本,则只能使用 ...

  4. 图书管理系统总结——数据库操纵(二):DML语句

    这里以最基本的DML语句作为例子,其他各种复杂的DML语句可以根据这些基本JDBC语句得到. 一.查询表格 这里以两张表关联查询为例,采用动态方式,根据输入的条件在WHERE语句后面接上相应的各种条件 ...

  5. java 9+版本中,接口的内容总结

    java 9+版本中,接口的内容可以有: 1.成员变量其实是常量,格式: [public]  [static]  [final] 数据类型 常量名称=数据值: 注意: 常量必须进行赋值,而且一旦赋值不 ...

  6. VSCode上发布第一篇博客

    在VSCode上发布到博客园的第一篇博客 前段时间在VSCode安装好插件WriteCnblog,多次检查writeCnblog configuration配置信息也是完全正确的,但是一直没能在VSC ...

  7. react功能实现-组件创建

    这里主要从两个角度来分析创建一个组件需要怎么做,一个是元素,一个是数据.整理向,大量借鉴,非原创. 1.渲染组件. 我们先明确一点,所有的元素都必须通过render方法来输出渲染.所有,每个组件类最终 ...

  8. 2019"深思杯"山东省大学生网络安全技能大赛部分wp

    签到 载入OD查看字符串 上下左右 这道题出来的时候真的是一点思路都没有,一直以为是什么编码来着,看了大佬们的 wp 原来是画图 拿大佬的脚本: from PIL import Image im = ...

  9. PowerDotNet平台化软件架构设计与实现系列(08):缓存平台

    几乎所有后端应用都会或多或少用到缓存,尤其是分布式缓存服务,以及和本地缓存构造的二级缓存.根据我们一贯的节约代码的风格,为了复用的目标,抽象出缓存平台,进行缓存管理. 考虑到很多公司都会自己造或者直接 ...

  10. 5款超实用的.NET性能分析工具

    虽然.NET框架号称永远不会发生内存泄漏,原因是引入了内存回收机制.但在实际应用中,往往我们分配了对象但没有释放指向该对象的引用,导致对象永远无法释放.最常见的情况就是给对象添加了事件处理函数,但当不 ...