架构设计
本文档详细说明 Apq.Cfg 的架构设计、核心组件和交互流程。
架构概览
┌─────────────────────────────────────────────────────────────────┐
│ 应用程序层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ cfg.Get() │ │ cfg.Set() │ │ cfg.ConfigChanges │ │
│ │ cfg.GetSection() │ cfg.SaveAsync() │ (Rx Observable) │ │
│ └──────┬──────┘ └──────┬──────┘ └───────────┬─────────────┘ │
└─────────┼────────────────┼─────────────────────┼────────────────┘
│ │ │
┌─────────▼────────────────▼─────────────────────▼────────────────┐
│ ICfgRoot 接口 │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ MergedCfgRoot ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ ││
│ │ │ 层级合并 │ │ 待保存队列 │ │ ChangeCoordinator │ ││
│ │ │ (Level) │ │ (Pending) │ │ (变更协调器) │ ││
│ │ └─────────────┘ └─────────────┘ └─────────────────────┘ ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
│ │ │
┌─────────▼────────────────▼─────────────────────▼────────────────┐
│ 配置源层 (ICfgSource) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ JSON │ │ YAML │ │ Consul │ │ Nacos │ │ Vault │ │
│ │ Level:0 │ │ Level:1 │ │ Level:10│ │ Level:10│ │ Level:15│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼──────────┼──────────┼──────────┼──────────┼─────────────┘
│ │ │ │ │
┌───────▼──────────▼──────────▼──────────▼──────────▼─────────────┐
│ Microsoft.Extensions.Configuration │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ IConfigurationRoot (合并后的配置) ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘核心组件
1. ICfgRoot 接口
配置根接口,是整个系统的入口点。
csharp
public interface ICfgRoot : IDisposable, IAsyncDisposable
{
// 读取操作
string? Get(string key);
T? Get<T>(string key);
bool Exists(string key);
ICfgSection GetSection(string key);
// 写入操作
void Set(string key, string? value, int? targetLevel = null);
void Remove(string key, int? targetLevel = null);
Task SaveAsync(int? targetLevel = null, CancellationToken ct = default);
// 批量操作
IReadOnlyDictionary<string, string?> GetMany(IEnumerable<string> keys);
void GetMany(IEnumerable<string> keys, Action<string, string?> onValue);
void SetMany(IEnumerable<KeyValuePair<string, string?>> values, int? targetLevel = null);
// 转换与事件
IConfigurationRoot ToMicrosoftConfiguration();
IObservable<ConfigChangeEvent> ConfigChanges { get; }
}2. ICfgSection 接口
配置节接口,提供对配置子树的访问。
csharp
public interface ICfgSection
{
string Path { get; }
string? Get(string key);
T? Get<T>(string key);
void Set(string key, string? value, int? targetLevel = null);
ICfgSection GetSection(string key);
IEnumerable<string> GetChildKeys();
}3. ICfgSource 接口
配置源接口,所有配置源的基础抽象。
csharp
public interface ICfgSource
{
int Level { get; } // 层级优先级
bool IsWriteable { get; } // 是否可写
bool IsPrimaryWriter { get; } // 是否为主写入源
IConfigurationSource BuildSource();
}
public interface IWritableCfgSource : ICfgSource
{
void Set(string key, string? value);
void Remove(string key);
Task SaveAsync(CancellationToken ct = default);
}4. MergedCfgRoot 实现
核心实现类,负责多配置源的合并和管理。
csharp
internal sealed class MergedCfgRoot : ICfgRoot
{
// 按层级组织的配置源
private readonly Dictionary<int, LevelData> _levelData;
// 合并后的 Microsoft Configuration
private readonly IConfigurationRoot _merged;
// 变更协调器(处理热重载)
private ChangeCoordinator? _coordinator;
// 配置变更事件流
private readonly Subject<ConfigChangeEvent> _configChangesSubject;
// 缓存的层级数组(性能优化)
private readonly int[] _levelsDescending;
private readonly int[] _levelsAscending;
}5. CfgBuilder 构建器
流式 API 构建器,用于创建配置实例。
csharp
public class CfgBuilder
{
private readonly List<ICfgSource> _sources = new();
private readonly EncodingMappingConfig _encodingConfig = new();
public CfgBuilder AddJson(string path, int level, ...);
public CfgBuilder AddYaml(string path, int level, ...);
public CfgBuilder AddConsul(Action<ConsulCfgOptions> configure, int level, ...);
public CfgBuilder AddVault(Action<VaultCfgOptions> configure, int level, ...);
public ICfgRoot Build();
}层级合并机制
层级优先级
配置源按 Level 属性排序,数值越大优先级越高:
Level 0: config.json (基础配置)
Level 1: config.{env}.json (环境配置)
Level 2: config.local.json (本地覆盖)
Level 10: Consul/Nacos/Etcd (远程配置)
Level 15: Vault (密钥配置)
Level 20: 环境变量 (最高优先级)合并算法
csharp
public string? Get(string key)
{
// 1. 先检查待保存队列(从高层级到低层级)
foreach (var level in _levelsDescending)
{
if (_levelData[level].Pending.TryGetValue(key, out var value))
return value;
}
// 2. 从合并后的配置中获取
return _merged[key];
}写入策略
csharp
public void Set(string key, string? value, int? targetLevel = null)
{
// 1. 确定目标层级
var level = targetLevel ?? GetDefaultWriteLevel();
// 2. 写入待保存队列
_levelData[level].Pending[key] = value;
// 3. 写入配置源(如果支持)
if (_levelData[level].Primary is IWritableCfgSource writable)
{
writable.Set(key, value);
}
}热重载机制
变更协调器 (ChangeCoordinator)
┌─────────────────────────────────────────────────────────────┐
│ ChangeCoordinator │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 防抖处理 │ │ 增量更新 │ │ 变更通知 │ │
│ │ (Debounce) │ │ (Diff) │ │ (ConfigChanges) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
└─────────┼────────────────┼─────────────────────┼────────────┘
│ │ │
▼ ▼ ▼
文件变更事件 配置源重载 Rx Observable热重载流程
- 文件监听:FileSystemWatcher 监听配置文件变更
- 防抖处理:多次快速变更合并为一次处理(默认 100ms)
- 增量更新:只重载发生变化的配置源
- 差异计算:比较新旧配置,生成变更事件
- 通知订阅者:通过
ConfigChanges发布变更事件
csharp
// 订阅配置变更
cfg.ConfigChanges.Subscribe(e =>
{
foreach (var (key, change) in e.Changes)
{
Console.WriteLine($"[{change.Type}] {key}: {change.OldValue} -> {change.NewValue}");
}
});编码处理流程
读取编码检测
┌─────────────────────────────────────────────────────────────┐
│ 编码检测流程 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ 用户指定 │ -> │ 编码映射 │ -> │ 缓存结果 │ │
│ │ (Specified) │ │ (Mapping) │ │ (Cache) │ │
│ └──────┬──────┘ └──────┬──────┘ └───────┬─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ BOM 检测 │ -> │ UTF.Unknown │ -> │ 回退编码 │ │
│ │ │ │ 库检测 │ │ (UTF-8) │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘编码映射优先级
| 匹配类型 | 默认优先级 | 示例 |
|---|---|---|
| ExactPath | 100 | /path/to/config.json |
| Wildcard | 0 | *.ps1 |
| Regex | 0 | logs[/\\].*\.log$ |
| 内置 PowerShell | -100 | *.ps1, *.psm1 |
深入了解
更多编码处理细节请参阅 编码处理流程。
配置源实现
文件配置源基类
csharp
public abstract class FileCfgSourceBase : ICfgSource, IWritableCfgSource
{
protected readonly string FilePath;
protected readonly EncodingOptions EncodingOptions;
protected readonly bool ReloadOnChange;
// 编码检测
protected Encoding DetectEncoding();
// 文件监听
protected void SetupFileWatcher();
// 抽象方法(子类实现)
protected abstract void ParseContent(string content);
protected abstract string SerializeContent();
}远程配置源基类
csharp
public abstract class RemoteCfgSourceBase : ICfgSource, IWritableCfgSource
{
protected readonly bool EnableHotReload;
protected readonly TimeSpan ReconnectInterval;
// 连接管理
protected abstract Task ConnectAsync();
protected abstract Task DisconnectAsync();
// 热重载
protected abstract void SetupWatcher();
protected abstract void OnConfigChanged(Dictionary<string, string?> newData);
}依赖注入集成
服务注册
csharp
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddApqCfg(
this IServiceCollection services,
Action<CfgBuilder> configure)
{
var builder = new CfgBuilder();
configure(builder);
var cfgRoot = builder.Build();
services.AddSingleton<ICfgRoot>(cfgRoot);
services.AddSingleton<IConfiguration>(sp =>
sp.GetRequiredService<ICfgRoot>().ToMicrosoftConfiguration());
return services;
}
public static IServiceCollection ConfigureApqCfg<TOptions>(
this IServiceCollection services,
string sectionPath) where TOptions : class, new()
{
services.AddOptions<TOptions>()
.Configure<ICfgRoot>((options, cfg) =>
{
var section = cfg.GetSection(sectionPath);
// 绑定配置到选项对象
});
return services;
}
}源生成器
编译时代码生成
csharp
[CfgSection("config")]
public partial class AppConfig
{
public string? Name { get; set; }
public int Port { get; set; }
}
// 生成的代码
public partial class AppConfig
{
public static AppConfig BindFrom(ICfgSection section)
{
return new AppConfig
{
Name = section.Get("Name"),
Port = section.Get<int>("Port")
};
}
}支持的类型
- 简单类型:string, int, long, bool, double, decimal, DateTime, Guid, 枚举
- 集合类型:T[], List<T>, HashSet<T>, Dictionary<TKey, TValue>
- 复杂类型:嵌套的 [CfgSection] 标记类
线程安全
并发控制策略
- 读取操作:无锁,使用不可变数据结构
- 写入操作:ConcurrentDictionary 存储待保存数据
- 保存操作:每个配置源独立锁
- 热重载:防抖 + 原子替换
csharp
// 批量读取减少锁竞争
public void GetMany(IEnumerable<string> keys, Action<string, string?> onValue)
{
_coordinator?.EnsureLatest();
foreach (var key in keys)
{
var value = GetInternal(key);
onValue(key, value);
}
}扩展点
自定义配置源
实现 ICfgSource 或 IWritableCfgSource 接口:
csharp
public class CustomCfgSource : ICfgSource, IWritableCfgSource
{
public int Level { get; }
public bool IsWriteable => true;
public bool IsPrimaryWriter { get; }
public IConfigurationSource BuildSource() => new CustomConfigurationSource(this);
public void Set(string key, string? value) { /* ... */ }
public void Remove(string key) { /* ... */ }
public Task SaveAsync(CancellationToken ct) { /* ... */ }
}自定义编码映射
csharp
var cfg = new CfgBuilder()
.ConfigureEncodingMapping(config =>
{
config.AddReadMapping("*.xml", EncodingMappingType.Wildcard, Encoding.UTF8);
config.AddWriteMapping("**/*.log", EncodingMappingType.Wildcard, Encoding.Unicode);
})
.Build();