背景

系列目录:【NET CORE微服务一条龙应用】开始篇与目录

在分布式或者微服务系统里,通过配置文件来管理配置内容,是一件比较令人痛苦的事情,再谨慎也有湿鞋的时候,这就是在项目架构发展的过程中,配置中心存在的意义。

其实配置中心的组件已经有非常出名的案例,比如携程的阿波罗配置中心(https://github.com/ctripcorp/apollo

为什么又造轮子,因为不想发布项目的时候到处切管理平台。

基本要求

作为一个通用的配置组件,需要支持如下功能:

1、客户端定时刷新获信最新配置信息并进行热更新

2、配置有更新服务端主动推送重载或更新命令至客户端进行配置获取

所以涉及相对应组件如下:

1、支持广播的消息通知组件,目前使用redis(StackExchange.Redis)、Zookeeper(Rabbit.Zookeeper)实现客户端全局监听服务,服务端可以推送不同组建不同的命令

2、支持定时获取最新配置,目前使用HostedService实现全局统一启动,客户端实现全局启动接口,接口使用Timer进行定时获取配置

3、支持net core原生IConfiguration接口获取配置中心数据

服务端设计

管理服务端主要实现:

1、三表增删改查

2、配置内容表,每次新增或者修改,当前配置信息版本号为,所以配置最大版本号然后加一

3、应用表列表增加主动通知功能

配置查询服务端

主要提供配置信息的查询接口

1、接口入参如下

public class QueryConfigInput
{
[NotEmpty("config_001","AppId不能为空")]
public string AppId { set; get; }
public long Version { set; get; }
[NotEmpty("config_002", "签名不能为空")]
public string Sign { set; get; }
[NotEmpty("config_005", "NamespaceName不能为空")]
public string NamespaceName { set; get; }
public string Env { set; get; }
}

2、查询逻辑

2.1 入参基本验证

2.2 AppId 密钥进行签名验证

2.3 请求配置环境定位

2.4 查询当前请求应用和共有配置应用

2.5 查询大于当前查询版本号的配置信息并返回

配置中心客户端

客户端主要实现原理和功能

1、配置信息请求,当前Http请求,需根据配置信息组合请求url,然后请求获取配置,每次请求带上当前配置最大版本号(在以后请求时只获取有更新的配置)

2、配置信息本地存储(容灾),第一次获取成功后,把配置信息进行版本文件存储,以后的请求中当有配置更新时再进行文件存储。

3、当配置请求失败时进行本地文件配置信息的还原应用。

4、配置定时获取

5、客户端接收更新或者重载命令

6、原生IConfiguration配置查询支持

部分功能介绍

客户端参数

"ConfigServer": {
"AppId": "PinzhiGO",
"AppSercet": "xxxxxxxxxxxxx",
"ServerUrl": "http://10.10.188.136:18081/", // 配置查询服务端地址
"NamespaceName": "Pinzhi.Identity.WebApi",
"Env": "dev",
"RefreshInteval":
},

原生IConfiguration配置查询

查看AddJsonFile源码,可以发现实现自定义配置源,需要集成和实现ConfigurationProvider和IConfigurationSource两个方法

代码如下

public class BucketConfigurationProvider : ConfigurationProvider, IDataChangeListener, IConfigurationSource
{
private readonly ConfigurationHelper _configurationHelper;
public BucketConfigurationProvider(BucketConfigOptions options)
{
_configurationHelper = new ConfigurationHelper(options);
Data = new ConcurrentDictionary<string, string>();
} public override void Load()
{
DataChangeListenerDictionary.Add(this);
Data = _configurationHelper.Get().ConfigureAwait(false).GetAwaiter().GetResult();
} private void SetData(ConcurrentDictionary<string, string> changeData)
{
foreach(var dic in changeData)
{
if (Data.ContainsKey(dic.Key))
Data[dic.Key] = dic.Value;
else
Data.Add(dic);
}
// Data = new Dictionary<string, string>(_configRepository.Data, StringComparer.OrdinalIgnoreCase);
} public void OnDataChange(ConcurrentDictionary<string, string> changeData)
{
SetData(changeData);
OnReload();
} public IConfigurationProvider Build(IConfigurationBuilder builder) => this;
}

当有配置更新时,我们需要更新到ConfigurationProvider的Data中,所以我们需要实现自定义接口IDataChangeListener的OnDataChange方法,当客户端请求发现有配置更新时,会调用接口的OnDataChange把最新的配置信息传递进来。

启用原生IConfiguration方法如下:

 .ConfigureAppConfiguration((hostingContext, _config) =>
{
_config
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddEnvironmentVariables(); // 添加环境变量
var option = new BucketConfigOptions();
_config.Build().GetSection("ConfigServer").Bind(option);
_config.AddBucketConfig(option);
})

定时配置获取

常规做法是写一个hostedservice的方法,然后写一个timer去定时获取,由于其他的组件可能都需要有定时的情况,我们统一处理了一下定时的任务,每个组件实现IExecutionService接口,然后组件会在启动的时候循环调用IExecutionService的StartAsync的方法,组件包Bucket.Config.HostedService,原理比较简单,使用代码如下:

// 添加全局定时任务
services.AddBucketHostedService(builder => {
builder.AddAuthorize().AddConfig().AddErrorCode();
});
public class AspNetCoreHostedService : IBucketAgentStartup
{
private readonly IEnumerable<IExecutionService> _services; public AspNetCoreHostedService(IEnumerable<IExecutionService> services)
{
_services = services;
} public async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
{
foreach (var service in _services)
await service.StartAsync(cancellationToken);
} public async Task StopAsync(CancellationToken cancellationToken = default(CancellationToken))
{
foreach (var service in _services)
await service.StopAsync(cancellationToken);
}
}

组件命令监听

和上面原则一样,也进行了统一的封装,目前监听主要实现了redis和zookeeper,下面举例redis

组件监听需实现接口

public interface IBucketListener
{
string ListenerName { get; }
Task ExecuteAsync(string commandText);
}

命令序列化实体

public class NetworkCommand
{
public string NotifyComponent { set; get; }
public string CommandText { set; get; }
}
public enum NetworkCommandType
{
/// <summary>
/// 更新
/// </summary>
Refresh,
/// <summary>
/// 重载
/// </summary>
Reload,
}

在hostedservice启动时实现

public Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
{
_subscriber = _redisClient.GetSubscriber(_redisListenerOptions.ConnectionString);
return _subscriber.SubscribeAsync(RedisListenerKey, (channel, message) =>
{
var command = JsonConvert.DeserializeObject<Bucket.Values.NetworkCommand>(message);
_extractCommand.ExtractCommandMessage(command);
});
}

在接口IExtractCommand里会根据各个监听组件的ListenerName进行对应的调用

使用方法如下:

// 添加应用监听
services.AddListener(builder => {
//builder.UseRedis();
builder.UseZookeeper();
builder.AddAuthorize().AddConfig().AddErrorCode();
});

所以对应组件实现的命令监听只要关心自身逻辑即可吗,代码如下

 public class BucketConfigListener : IBucketListener
{
public string ListenerName => "Bucket.Config"; private readonly IDataRepository _dataRepository; public BucketConfigListener(IDataRepository dataRepository)
{
_dataRepository = dataRepository;
} public async Task ExecuteAsync(string commandText)
{
if (!string.IsNullOrWhiteSpace(commandText) && commandText == NetworkCommandType.Refresh.ToString())
await _dataRepository.Get();
if (!string.IsNullOrWhiteSpace(commandText) && commandText == NetworkCommandType.Reload.ToString())
await _dataRepository.Get(true);
}
}

配置中心使用配置如下

 .ConfigureAppConfiguration((hostingContext, _config) =>
{
_config
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddEnvironmentVariables(); // 添加环境变量
var option = new BucketConfigOptions();
_config.Build().GetSection("ConfigServer").Bind(option);
_config.AddBucketConfig(option);
}) // ConfigureServices
// 添加配置服务
services.AddConfigServer(Configuration);
// 添加应用监听
services.AddListener(builder => {
//builder.UseRedis();
builder.UseZookeeper();
builder.AddAuthorize().AddConfig().AddErrorCode();
});
// 添加全局定时任务
services.AddBucketHostedService(builder => {
builder.AddAuthorize().AddConfig().AddErrorCode();
}); //使用
private readonly IConfiguration _configuration;
private readonly IConfig _config; public AuthController(IConfiguration configuration, IConfig config)
{
_configuration= configuration;
_config= config;
}
// 获取值
_configuration.GetValue<string>("qqqq");
_config.StringGet("qqqq");

Appsettings.json相关配置信息转移至配置中心

由于配置中心客户端实现了原生的IConfiguration,所以appsetting的相关配置我们完全可以移至配置中心中,由于appsetting使用的是json,所以在配置中心服务端配置信息的Key需要转换,举例:

"BucketListener": {
"Redis": {
"ConnectionString": "127.0.0.1:6379,allowadmin=true",
"ListenerKey": "Bucket.Sample"
},
"Zookeeper": {
"ConnectionString": "localhost:2181",
"ListenerKey": "Bucket.Sample"
}
}

在配置中心key如下:

BucketListener:Redis:ConnectionString

BucketListener:Redis:ListenerKey

......

数组使用如下:

DbConfig:0:Name

DbConfig:0:DbType

DbConfig:1:Name

DbConfig:1:DbType

总结

个人写作水平有限,涉及的东西也很多,篇幅有限所以只做了大体介绍,忘谅解

本章涉及源码
https://github.com/q315523275/FamilyBucket/tree/master/src/Config 客户端组件

https://github.com/q315523275/FamilyBucket/tree/master/%E5%9F%BA%E7%A1%80%E6%9C%8D%E5%8A%A1%E9%A1%B9%E7%9B%AE/Pinzhi.ConfigServer 配置查询服务端

https://github.com/q315523275/FamilyBucket/tree/master/%E5%9F%BA%E7%A1%80%E6%9C%8D%E5%8A%A1%E9%A1%B9%E7%9B%AE/Pinzhi.Platform 综合管理服务接口

【NET CORE微服务一条龙应用】第二章 配置中心使用的更多相关文章

  1. 【NET CORE微服务一条龙应用】开始篇与目录

    简介 随着业务的发展和变更,项目原先的分布式框架应用业务发展已有些不适应,所以18年初开始准备使用微服务框架,当时正好看到了ocelot项目,特意翻看了源码,发现很灵活和易扩展 于是就开始了微服务的开 ...

  2. 【NET CORE微服务一条龙应用】第三章 认证授权与动态权限配置

    介绍 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务中网关和子服务如何使用统一的权限认证 主要介绍内容为: 1.子服务如 ...

  3. 【NET CORE微服务一条龙应用】应用部署

    简介 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 本章主要介绍https://github.com/q315523275/FamilyBucket上微服务一条龙应用,在实际使用中的应用 ...

  4. 【NET CORE微服务一条龙应用】第一章 网关使用与配置

    简介 微服务的系统应用中,网关系统使用的是ocelot,ocelot目前已经比较成熟了 ocelot就不做介绍了,等整体介绍完后再进行各类扩展介绍,ocelot源码地址:https://github. ...

  5. 「 从0到1学习微服务SpringCloud 」06 统一配置中心Spring Cloud Config

    系列文章(更新ing): 「 从0到1学习微服务SpringCloud 」01 一起来学呀! 「 从0到1学习微服务SpringCloud 」02 Eureka服务注册与发现 「 从0到1学习微服务S ...

  6. 微服务深入浅出(8)-- 配置中心Spring Cloud Config

    Config Server从本地读取配置文件 将所有的配置文件统一写带Config Server过程的目录下,Config Server暴露Http API接口,Config Client调用Conf ...

  7. 微服务读取不到config配置中心配置信息,Spring Boot无法找到PropertySource:找不到标签Could not locate PropertySource: label not found

    服务出现报这个错, o.s.c.c.c.ConfigServicePropertySourceLocator - Could not locate PropertySource: label not ...

  8. .NET Core微服务之基于Polly+AspectCore实现熔断与降级机制

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.熔断.降级与AOP 1.1 啥是熔断? 在广义的解释中,熔断主要是指为控制股票.期货或其他金融衍生产品的交易风险,为其单日价格波动幅度 ...

  9. .NET Core微服务之基于Ocelot实现API网关服务(续)

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.负载均衡与请求缓存 1.1 负载均衡 为了验证负载均衡,这里我们配置了两个Consul Client节点,其中ClientServic ...

随机推荐

  1. Eclipse中 *.properties 文件编码设置

    Eclipse 中的默认编码格式为 ISO-8895-1,在此编码下中文的会显示如下的效果 解决方法 Windows --> Preference --> General Types -- ...

  2. 【ZooKeeper】ZooKeeper安装及简单操作

    ZooKeeper介绍 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一 ...

  3. 数据库-SQL语句:删除和修改语句-列类型-列约束

    使用MySQL客户端连接服务器的两种方式: (1)交互模式: ——查 mysql.exe  -h127.0.0.1  -uroot  -p mysql   -uroot (2)脚本模式:——增删改 m ...

  4. spring中aop事务

    一.事务 二.spring封装了事务管理代码 1.事务操作 2.事务操作对象 (1)因为在不同平台,操作事务的代码各不相同.spring提供了一个接口 (2) PlatformTransactionM ...

  5. c++类对象的内存分布

    要想知道c++类对象的内存布局, 可以有多种方式,比如: 1)输出成员变量的偏移, 通过offsetof宏来得到 2)通过调试器查看, 比如常用的VS 1.没有数据成员的对象 class A{ }; ...

  6. MySQL中校验规则(collation)的选取对实际数据筛选的影响

    在mysql中存在着各种utf8编码格式,如下表:1)utf8_bin2)utf8_general_ci utf8_bin将字符串中的每一个字符用二进制数据存储,区分大小写.utf8_genera_c ...

  7. 通过selenium登录网页获取特定信息

    前言 最近有需求要登录网站查询一些信息,然后再修改一些信息,而且这种工作重复性很强,想着通过自动化的方式来做这个东西. 技术选择 自动化的方式开始考虑的是用python的爬虫来做,但是登录的网站有好多 ...

  8. Thinking in Java from Chapter 7

    From Thinking in Java 4th Edition final 1. 对于基本类型,final使数值恒定不变 2. 对于对象引用,final使引用恒定不变,即不能指向别的对象,但指向的 ...

  9. Android 实现手写板技术

    Android手写板和涂鸦的功能,代码如下: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/andro ...

  10. 在Markdown中写注释

    概述 下面是我整理的在Markdown中写注释的几种方法,供自己开发时参考,相信对其他人也有用. html标签 既然Markdown内嵌html语法,那么就可以用可以用隐藏的html标签. 注意:需要 ...