配置中心与动态配置
大约 9 分钟约 2841 字
配置中心与动态配置
简介
微服务架构中,配置中心集中管理所有服务的配置,支持动态更新和环境隔离。理解 Consul KV、Apollo 等配置中心的集成方式,以及 .NET 配置系统的扩展机制,有助于实现零重启的配置变更。
特点
Consul KV 配置中心
集成实现
// dotnet add package Consul
// 1. Consul KV 配置源
public class ConsulConfigurationProvider : ConfigurationProvider
{
private readonly IConsulClient _consulClient;
private readonly string _basePath;
private readonly TimeSpan _refreshInterval;
private readonly ILogger? _logger;
public ConsulConfigurationProvider(
IConsulClient consulClient,
string basePath,
TimeSpan refreshInterval,
ILogger? logger = null)
{
_consulClient = consulClient;
_basePath = basePath.TrimEnd('/');
_refreshInterval = refreshInterval;
_logger = logger;
}
public override void Load() => LoadAsync().GetAwaiter().GetResult();
private async Task LoadAsync()
{
try
{
var response = await _consulClient.KV.List(_basePath);
if (response.Response == null) return;
var data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var kv in response.Response.Where(k => k.Value != null))
{
// 将 Consul 路径转换为配置键
// config/myapp/database/connectionString → Database:ConnectionString
var key = kv.Key.Substring(_basePath.Length + 1)
.Replace('/', ':')
.Replace('\\', ':');
data[key] = Encoding.UTF8.GetString(kv.Value);
}
Data = data;
_logger?.LogInformation("从 Consul 加载了 {Count} 个配置项", data.Count);
}
catch (Exception ex)
{
_logger?.LogError(ex, "从 Consul 加载配置失败");
}
}
// 后台轮询刷新
public void StartBackgroundRefresh(CancellationToken ct)
{
Task.Run(async () =>
{
while (!ct.IsCancellationRequested)
{
await Task.Delay(_refreshInterval, ct);
var oldData = new Dictionary<string, string?>(Data);
await LoadAsync();
// 检查配置是否变更
if (!Data.SequenceEqual(oldData))
{
OnReload(); // 触发变更通知
_logger?.LogInformation("配置已更新");
}
}
}, ct);
}
}
// 2. Consul 配置源
public class ConsulConfigurationSource : IConfigurationSource
{
public IConsulClient ConsulClient { get; set; } = null!;
public string BasePath { get; set; } = "config";
public TimeSpan RefreshInterval { get; set; } = TimeSpan.FromMinutes(1);
public ILogger? Logger { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new ConsulConfigurationProvider(ConsulClient, BasePath, RefreshInterval, Logger);
}
}
// 3. 扩展方法
public static class ConsulConfigurationExtensions
{
public static IConfigurationBuilder AddConsul(
this IConfigurationBuilder builder,
string consulAddress,
string basePath,
TimeSpan? refreshInterval = null)
{
var consulClient = new ConsulClient(c => c.Address = new Uri(consulAddress));
builder.Add(new ConsulConfigurationSource
{
ConsulClient = consulClient,
BasePath = basePath,
RefreshInterval = refreshInterval ?? TimeSpan.FromMinutes(1)
});
return builder;
}
}
// 4. 使用
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddConsul(
consulAddress: "http://localhost:8500",
basePath: $"config/{builder.Environment.EnvironmentName}/myapp",
refreshInterval: TimeSpan.FromSeconds(30));
// Consul KV 存储结构:
// config/production/myapp/database/connectionString → "Server=..."
// config/production/myapp/database/name → "mydb"
// config/production/myapp/redis/connection → "localhost:6379"
// config/production/myapp/logging/level → "Information"
// 映射结果:
// Database:ConnectionString = "Server=..."
// Database:Name = "mydb"
// Redis:Connection = "localhost:6379"
// Logging:Level = "Information"动态配置刷新
IOptionsMonitor 与 IOptionsSnapshot
// .NET 配置刷新机制:
// IOptions<T> — 单例,不刷新(启动时读取一次)
// IOptionsMonitor<T> — 单例,支持刷新(配置变更时自动更新)
// IOptionsSnapshot<T> — Scoped,每次请求读取最新值
// 配置模型
public class DynamicSettings
{
public int MaxRetryCount { get; set; } = 3;
public TimeSpan CacheDuration { get; set; } = TimeSpan.FromMinutes(5);
public bool EnableFeatureX { get; set; }
public List<string> AllowedOrigins { get; set; } = new();
public RateLimitSettings RateLimit { get; set; } = new();
}
public class RateLimitSettings
{
public int RequestsPerMinute { get; set; } = 100;
public int BurstSize { get; set; } = 20;
}
// 注册
builder.Services.Configure<DynamicSettings>(
builder.Configuration.GetSection("DynamicSettings"));
// 使用 IOptionsMonitor(推荐 — 支持动态刷新)
public class OrderService
{
private readonly IOptionsMonitor<DynamicSettings> _settingsMonitor;
public OrderService(IOptionsMonitor<DynamicSettings> settingsMonitor)
{
_settingsMonitor = settingsMonitor;
// 监听配置变更
_settingsMonitor.OnChange(settings =>
{
Console.WriteLine($"配置已更新: MaxRetry={settings.MaxRetryCount}");
});
}
public async Task ProcessOrderAsync(Order order, CancellationToken ct)
{
// 获取最新配置
var settings = _settingsMonitor.CurrentValue;
var retryCount = settings.MaxRetryCount;
// ...
}
}
// 使用 IOptionsSnapshot(每次请求最新)
public class ProductService
{
private readonly DynamicSettings _settings;
public ProductService(IOptionsSnapshot<DynamicSettings> settings)
{
_settings = settings.Value; // 每次请求获取最新
}
}
// 动态更新 CORS(配置变更时自动更新)
builder.Services.Configure<DynamicSettings>(
builder.Configuration.GetSection("DynamicSettings"));
var app = builder.Build();
// 动态 CORS
app.UseCors(policy =>
{
var settings = app.Configuration.GetSection("DynamicSettings").Get<DynamicSettings>();
var origins = settings?.AllowedOrigins ?? new List<string>();
policy.WithOrigins(origins.ToArray());
});
// 手动触发配置刷新端点(生产环境需要鉴权)
app.MapPost("/admin/reload-config", (IConfigurationRoot configRoot) =>
{
configRoot.Reload();
return Results.Ok(new { message = "配置已重新加载" });
});配置加密
敏感配置保护
// 使用 Data Protection API 加密配置
public class EncryptedConfigurationProvider : ConfigurationProvider
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly IConfigurationProvider _innerProvider;
private readonly string[] _encryptedKeys;
public EncryptedConfigurationProvider(
IDataProtectionProvider dataProtectionProvider,
IConfigurationProvider innerProvider,
string[] encryptedKeys)
{
_dataProtectionProvider = dataProtectionProvider;
_innerProvider = innerProvider;
_encryptedKeys = encryptedKeys;
}
public override void Load()
{
_innerProvider.Load();
var protector = _dataProtectionProvider.CreateProtector("ConfigurationEncryption");
foreach (var key in _encryptedKeys)
{
if (_innerProvider.TryGet(key, out var value) && value != null)
{
try
{
// 尝试解密
var decrypted = protector.Unprotect(value);
Data[key] = decrypted;
}
catch
{
// 如果解密失败,说明是明文(首次使用)
Data[key] = value;
}
}
}
}
}
// 更简单的方案:环境变量覆盖
// 生产环境通过 K8s Secret 注入敏感配置
// spec:
// containers:
// - name: api
// envFrom:
// - secretRef:
// name: api-secrets
// env:
// - name: ConnectionStrings__DefaultConnection
// valueFrom:
// secretKeyRef:
// name: db-secret
// key: connection-string多环境配置
分层配置策略
// 配置优先级(从低到高):
// 1. appsettings.json
// 2. appsettings.{Environment}.json
// 3. Consul KV (base)
// 4. Consul KV ({Environment})
// 5. 环境变量
// 6. 命令行参数
var builder = WebApplication.CreateBuilder(args);
// 配置层级
builder.Configuration
.SetBasePath(builder.Environment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json",
optional: true, reloadOnChange: true)
.AddConsul("http://consul:8500", "config/shared/myapp") // 共享配置
.AddConsul("http://consul:8500",
$"config/{builder.Environment.EnvironmentName}/myapp") // 环境特定配置
.AddEnvironmentVariables("MYAPP_") // 环境变量前缀
.AddCommandLine(args); // 命令行参数
// 配置验证
builder.Services.AddOptions<DatabaseSettings>()
.BindConfiguration("Database")
.ValidateDataAnnotations()
.Validate(settings =>
{
return !string.IsNullOrEmpty(settings.ConnectionString);
}, "数据库连接字符串不能为空")
.PostConfigure(settings =>
{
// 后处理:强制覆盖
if (builder.Environment.IsDevelopment())
{
settings.CommandTimeout = 300;
}
});
public class DatabaseSettings
{
[Required]
public string ConnectionString { get; set; } = "";
[Range(1, 300)]
public int CommandTimeout { get; set; } = 30;
public bool EnableSensitiveDataLogging { get; set; }
}配置中心运维
Consul KV 管理操作
# Consul KV 基本操作
# 写入配置
curl -X PUT http://localhost:8500/v1/kv/config/production/myapp/database/connectionString \
-d "Server=prod-db;Database=MyApp;Trusted_Connection=True;"
curl -X PUT http://localhost:8500/v1/kv/config/production/myapp/database/name \
-d "MyApp"
curl -X PUT http://localhost:8500/v1/kv/config/production/myapp/redis/connection \
-d "redis-prod:6379"
curl -X PUT http://localhost:8500/v1/kv/config/production/myapp/logging/level \
-d "Information"
# 读取配置
curl http://localhost:8500/v1/kv/config/production/myapp/database/connectionString
# 读取所有配置(带前缀)
curl http://localhost:8500/v1/kv/config/production/myapp?recurse
# 删除配置
curl -X DELETE http://localhost:8500/v1/kv/config/production/myapp/logging/level
# 批量导入配置(使用 consul-cli 或脚本)
for key in $(cat config.keys); do
value=$(jq -r ".$key" config.json)
curl -X PUT "http://localhost:8500/v1/kv/config/production/myapp/$key" -d "$value"
doneApollo 配置中心集成
// Apollo 是携程开源的分布式配置中心
// dotnet add package Com.Ctrip.Framework.Apollo.Configuration
// Apollo 集成
var builder = WebApplication.CreateBuilder(args);
// 添加 Apollo 配置源
builder.Configuration.AddApollo(builder.Configuration.GetSection("Apollo"))
.AddDefault() // 默认 namespace: application
.AddNamespace("database") // 数据库配置 namespace
.AddNamespace("redis") // Redis 配置 namespace
.AddNamespace("feature-flags"); // 功能开关 namespace
// appsettings.json 中的 Apollo 配置
// {
// "Apollo": {
// "AppId": "myapp-service",
// "Env": "DEV",
// "MetaServer": "http://apollo-config:8080",
// "Secret": "your-app-secret"
// }
// }
// Apollo 管理后台操作:
// 1. 创建应用 -> 获取 AppId
// 2. 创建 Namespace(application, database, redis 等)
// 3. 在各 Namespace 中添加配置项
// 4. 发布配置 -> 客户端实时感知
// 5. 灰度发布 -> 先发布到部分实例验证
// 6. 回滚配置 -> 出问题时一键回滚
// Apollo 灰度发布规则配置
// - 按 IP 灰度:指定具体机器 IP
// - 按百分比灰度:随机分配比例
// - 按标签灰度:基于实例标签配置变更审计
// 配置变更审计日志
public class ConfigurationAuditLogger
{
private readonly ILogger<ConfigurationAuditLogger> _logger;
public ConfigurationAuditLogger(ILogger<ConfigurationAuditLogger> logger)
{
_logger = logger;
}
public void LogChange(string key, string? oldValue, string? newValue, string changedBy)
{
_logger.LogInformation(
"配置变更: Key={Key}, OldValue={OldValue}, NewValue={NewValue}, ChangedBy={ChangedBy}, Time={Time}",
key,
oldValue?.Length > 20 ? oldValue[..20] + "..." : oldValue,
newValue?.Length > 20 ? newValue[..20] + "..." : newValue,
changedBy,
DateTime.UtcNow);
}
}
// 配置变更通知(Webhook)
public class ConfigurationChangeNotifier
{
private readonly IHttpClientFactory _httpClientFactory;
public async Task NotifyAsync(ConfigurationChange change)
{
var client = _httpClientFactory.CreateClient("Webhook");
var payload = new
{
change.Key,
change.OldValue,
change.NewValue,
change.ChangedBy,
Timestamp = DateTime.UtcNow
};
// 发送到企业微信/钉钉/Slack 等
await client.PostAsJsonAsync("https://hooks.example.com/config-changed", payload);
}
}配置中心故障降级
/// <summary>
/// 配置中心不可用时的降级策略
/// </summary>
public class ResilientConfigurationProvider : ConfigurationProvider
{
private readonly IConfigurationProvider _primaryProvider; // Consul/Apollo
private readonly IConfigurationProvider _fallbackProvider; // 本地文件
private readonly ILogger _logger;
public ResilientConfigurationProvider(
IConfigurationProvider primaryProvider,
IConfigurationProvider fallbackProvider,
ILogger logger)
{
_primaryProvider = primaryProvider;
_fallbackProvider = fallbackProvider;
_logger = logger;
}
public override void Load()
{
try
{
_primaryProvider.Load();
Data = _primaryProvider.GetData();
_logger.LogInformation("从配置中心加载配置成功");
// 同步到本地缓存(故障恢复用)
SaveToLocalCache(Data);
}
catch (Exception ex)
{
_logger.LogError(ex, "配置中心不可用,使用降级配置");
// 1. 尝试使用本地缓存
var cachedData = LoadFromLocalCache();
if (cachedData != null)
{
Data = cachedData;
_logger.LogWarning("使用本地缓存配置");
return;
}
// 2. 使用 fallback(appsettings.json)
_fallbackProvider.Load();
Data = _fallbackProvider.GetData();
_logger.LogWarning("使用本地文件配置");
}
}
private void SaveToLocalCache IDictionary<string, string?> data)
{
var cachePath = Path.Combine(Path.GetTempPath(), "config-cache.json");
File.WriteAllText(cachePath, JsonSerializer.Serialize(data));
}
private IDictionary<string, string?>? LoadFromLocalCache()
{
var cachePath = Path.Combine(Path.GetTempPath(), "config-cache.json");
if (!File.Exists(cachePath)) return null;
try
{
var json = File.ReadAllText(cachePath);
return JsonSerializer.Deserialize<Dictionary<string, string?>>(json);
}
catch { return null; }
}
}优点
缺点
总结
配置中心集中管理微服务配置,Consul KV 通过自定义 ConfigurationProvider 集成到 .NET 配置系统。动态刷新通过 IOptionsMonitor<T> 实现,配置变更时自动更新。多环境配置按优先级覆盖:基础文件 → 环境文件 → Consul → 环境变量 → 命令行。敏感配置使用 Data Protection API 加密或 K8s Secret 注入。建议使用配置验证(ValidateDataAnnotations)确保配置正确性。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《配置中心与动态配置》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《配置中心与动态配置》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《配置中心与动态配置》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《配置中心与动态配置》最大的收益和代价分别是什么?
