# 服务治理标准
服务治理标准用于抽象服务注册、服务发现、键值配置中心和远程配置加载能力。它解决的是“服务在哪里、配置在哪里、公共运行信息在哪里”的问题。
在 Air.Cloud 中,业务代码应优先依赖标准接口,而不是直接依赖 Consul。这样后续即使服务中心或配置中心实现发生变化,业务侧也可以减少改动范围。
# 标准接口
| Standard | 作用 |
|---|---|
IServerCenterStandard | 服务注册、服务发现和服务详情查询标准 |
IKVCenterStandard | 键值对中心标准,例如读取远程配置、写入元数据、按前缀查询配置 |
IConsulServiceOptionsConfigureStandard | Consul 模块配置构建标准,用于补齐服务名、校验 Consul 地址和服务地址 |
提示
IServerCenterStandard 当前接口包含注册、查询和按 Key 获取服务信息;Consul 实现类 ConsulServerCenterDependency 额外提供 Unregister 方法用于注销服务实例。
# 当前实现
| 模块 | 实现内容 |
|---|---|
Air.Cloud.Modules.Consul | 基于 Consul 实现服务中心、KV 中心和远程配置加载 |
# 快速接入
业务项目需要先引用 Consul 模块包:
<PackageReference Include="Air.Cloud.Modules.Consul" Version="1.0.2" />
Web 应用通常使用 WebInjectInConsul() 作为入口:
using Air.Cloud.Modules.Consul.Extensions;
var builder = WebApplication.CreateBuilder(args);
var app = builder.WebInjectInConsul();
app.Run();
该入口会完成以下工作:
- 读取本地
ConsulServiceOptions配置。 - 通过
IConsulServiceOptionsConfigureStandard校验并补齐配置。 - 从 Consul KV 加载当前服务远程配置。
- 按配置加载公共配置。
- 注册
IServerCenterStandard和IKVCenterStandard。 - 非开发环境下将当前服务注册到 Consul。
如果默认配置构建逻辑不能满足业务要求,可以提供自定义构建器:
using Air.Cloud.Modules.Consul.Extensions;
using Air.Cloud.Modules.Consul.Model;
using Air.Cloud.Modules.Consul.Standard;
public sealed class OrderConsulOptionsConfigure : IConsulServiceOptionsConfigureStandard
{
public ConsulServiceOptions Configure(
ConsulServiceOptions options,
Action<ConsulServiceOptions> action = null)
{
options ??= new ConsulServiceOptions();
options.ServiceName ??= "order-service";
action?.Invoke(options);
return options;
}
}
var builder = WebApplication.CreateBuilder(args);
var app = builder.WebInjectInConsul<OrderConsulOptionsConfigure>();
app.Run();
# 配置节点
配置节点名称为 ConsulServiceOptions:
{
"Environment": "Production",
"ConsulServiceOptions": {
"ConsulAddress": "http://127.0.0.1:8500/",
"ServiceAddress": "http://127.0.0.1:5295",
"ServiceName": "order-service",
"HealthCheckRoute": "/Health",
"HealthCheckTimeStep": 5,
"ConnectTimeout": 5,
"DeregisterCriticalServiceAfter": 5,
"EnableCommonConfig": true,
"CommonConfigFileRoute": "Common"
}
}
| 配置项 | 说明 | 默认值 |
|---|---|---|
ConsulAddress | Consul 地址 | 无,必须配置 |
ServiceAddress | 当前服务可被 Consul 健康检查访问的地址 | 无,必须配置 |
ServiceName | 当前服务名称 | 空时按应用名补齐 |
HealthCheckRoute | 健康检查路由 | /Health |
HealthCheckTimeStep | 健康检查间隔,单位秒 | 5 |
ConnectTimeout | 健康检查超时时间,单位秒 | 5 |
DeregisterCriticalServiceAfter | 服务异常后多久注销,单位秒 | 5 |
EnableCommonConfig | 是否加载公共配置 | true |
CommonConfigFileRoute | 公共配置在 Consul KV 中的路径前缀 | Common |
ConsulAddress 和 ServiceAddress 是启动时校验项。前者用于连接 Consul,后者用于服务注册和健康检查;如果部署在容器、网关或反向代理后面,必须确认它是 Consul Agent 能访问到的地址。
# 服务注册与发现示例
业务代码可以依赖 IServerCenterStandard 查询服务列表或服务详情:
using Air.Cloud.Core.Standard.ServerCenter;
using Air.Cloud.Modules.Consul.Model;
public sealed class ServiceRegistryReader
{
private readonly IServerCenterStandard _serverCenter;
public ServiceRegistryReader(IServerCenterStandard serverCenter)
{
_serverCenter = serverCenter;
}
public async Task<IList<ConsulServerCenterServiceOptions>> QueryServicesAsync()
{
return await _serverCenter.QueryAsync<ConsulServerCenterServiceOptions>();
}
public async Task<object> GetOrderServiceAsync()
{
return await _serverCenter.GetAsync("order-service");
}
}
在启动流程、框架辅助代码或暂时不方便构造函数注入的地方,也可以通过 AppRealization 访问当前标准实现:
using Air.Cloud.Core.App;
using Air.Cloud.Modules.Consul.Model;
var services = await AppRealization.ServerCenter
.QueryAsync<ConsulServerCenterServiceOptions>();
var orderService = await AppRealization.ServerCenter.GetAsync("order-service");
手动注册服务时,可以构造 ConsulServerCenterServiceRegisterOptions:
using Air.Cloud.Core.Standard.ServerCenter;
using Air.Cloud.Modules.Consul.Model;
public sealed class ManualServiceRegistration
{
private readonly IServerCenterStandard _serverCenter;
public ManualServiceRegistration(IServerCenterStandard serverCenter)
{
_serverCenter = serverCenter;
}
public async Task RegisterAsync()
{
await _serverCenter.RegisterAsync(new ConsulServerCenterServiceRegisterOptions
{
ServiceName = "order-worker",
ServiceKey = "order-worker-01",
ServiceAddress = "http://127.0.0.1:7001",
HealthCheckRoute = "/health",
Timeout = TimeSpan.FromSeconds(5),
HealthCheckTimeStep = TimeSpan.FromSeconds(5),
DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1)
});
}
}
注意:业务服务通常不需要手动注册当前 Web 应用,WebInjectInConsul() 会在非开发环境下处理当前服务注册。手动注册更适合 Worker、外部服务适配器或测试场景。
# KV 中心示例
IKVCenterStandard 用于 Consul KV 的读写、删除和前缀查询。它适合保存配置片段、轻量元数据和运行时标记,不适合作为高频业务数据库使用。
using Air.Cloud.Core.Standard.KVCenter;
using Air.Cloud.Modules.Consul.Model;
public sealed class FeatureFlagStore
{
private readonly IKVCenterStandard _kvCenter;
public FeatureFlagStore(IKVCenterStandard kvCenter)
{
_kvCenter = kvCenter;
}
public async Task EnableAsync(string tenantId)
{
await _kvCenter.AddOrUpdateAsync(
$"feature/{tenantId}/new-checkout",
"enabled");
}
public async Task<string> GetAsync(string tenantId)
{
var item = await _kvCenter.GetAsync<ConsulKvCenterServiceInformation>(
$"feature/{tenantId}/new-checkout");
return item?.Value;
}
public async Task<IList<ConsulKvCenterServiceInformation>> QueryTenantFlagsAsync(string tenantId)
{
return await _kvCenter.QueryAsync<ConsulKvCenterServiceInformation>(
$"feature/{tenantId}");
}
}
也可以通过 AppRealization.KVCenter 访问:
using Air.Cloud.Core.App;
using Air.Cloud.Modules.Consul.Model;
await AppRealization.KVCenter.AddOrUpdateAsync("system/maintenance", "off");
var item = await AppRealization.KVCenter
.GetAsync<ConsulKvCenterServiceInformation>("system/maintenance");
常用接口:
| 接口 | 适用场景 |
|---|---|
AddOrUpdateAsync(key, value) | 写入或覆盖 KV 值 |
GetAsync<T>(key) | 获取单个 Key,并映射为 IKVCenterServiceOptions 类型 |
QueryAsync<T>(prefix) | 按前缀查询一组 KV |
DeleteAsync(key) | 删除指定 Key |
# 远程配置路径
Consul 模块会按约定从 KV 中加载服务私有配置和公共配置。
服务私有配置路径:
{ServiceName.Replace(".", "/")}/appsettings.{Environment}.json
示例:
order-service/appsettings.Production.json
公共配置路径:
{CommonConfigFileRoute}/appsettings.Common.{Environment}.json
示例:
Common/appsettings.Common.Production.json
远程配置内容应是 JSON:
{
"Feature": {
"NewCheckout": "enabled"
},
"Order": {
"TimeoutSeconds": 30
}
}
加载后可以通过框架配置对象读取:
var enabled = AppConfigurationLoader.InnerConfiguration["Feature:NewCheckout"];
注意
配置热更新只能说明配置对象读到了新值,不代表所有中间件连接都会自动重建。例如数据库、Redis、Kafka、Consul 自身连接是否重连,取决于对应模块是否实现了刷新逻辑。
# 服务治理常见问题
| 问题 | 常见场景 | 表现 | 处理方式 |
|---|---|---|---|
ConsulAddress 未配置 | 本地 appsettings.json 缺少节点 | 启动时报“你需要配置Consul服务运行IP地址” | 补齐 ConsulServiceOptions.ConsulAddress,并确认地址可访问 |
ServiceAddress 未配置 | 需要注册服务但未声明当前服务地址 | 启动时报“你需要配置当前服务运行IP地址” | 配置 Consul Agent 能访问到的服务地址 |
| 服务注册后健康检查失败 | 健康检查路由错误、端口不可达、容器网络不通 | Consul 中服务进入 critical 状态 | 检查 ServiceAddress、HealthCheckRoute、防火墙、容器端口映射 |
| 服务名不符合预期 | 未配置 ServiceName,使用默认应用名推断 | Catalog 中服务名与业务预期不一致 | 显式配置 ServiceName |
| 远程配置未加载 | KV 路径不符合约定或环境名不一致 | 配置项读取为空或仍是本地值 | 检查 {ServiceName}/appsettings.{Environment}.json 路径和 JSON 格式 |
| 公共配置未加载 | EnableCommonConfig = false 或路径写错 | 公共配置项读取不到 | 检查 EnableCommonConfig 和 CommonConfigFileRoute |
| KV 前缀查询结果为空 | Prefix 不匹配、Key 层级写错 | QueryAsync 返回空集合 | 使用 Consul UI 或 CLI 确认实际 Key 路径 |
删除 Key 返回 false | Key 不存在或已被删除 | 调用方误认为 Consul 异常 | 将返回值作为“是否实际删除”的结果处理 |
| ACL 环境无法访问 | Consul 开启 ACL,但当前模块未配置 Token | 请求 Consul 失败或无权限 | 当前模块文档未声明 ACL 支持,需要扩展 Consul 客户端配置 |
# 推荐处理模式
服务调用链路中读取服务列表:
var services = await _serverCenter.QueryAsync<ConsulServerCenterServiceOptions>();
var candidates = services
.Where(service => service.ServiceName == "order-service")
.ToList();
配置中心链路中读取业务开关:
var flag = await _kvCenter.GetAsync<ConsulKvCenterServiceInformation>(
"feature/order/new-checkout");
if (flag?.Value == "enabled")
{
EnableNewCheckout();
}
后台任务写入运行时元数据:
await _kvCenter.AddOrUpdateAsync(
$"jobs/order-cleanup/last-run",
DateTimeOffset.UtcNow.ToString("O"));
# 排查步骤
- 确认项目已引用
Air.Cloud.Modules.Consul,并通过WebInjectInConsul()或模块启动流程完成注册。 - 确认
ConsulServiceOptions.ConsulAddress可以从当前机器访问。 - 确认
ServiceAddress是 Consul Agent 可以访问的地址,不只是应用本机可以访问的地址。 - 在 Consul UI 中检查服务是否注册、健康检查是否通过、服务名是否符合预期。
- 在 Consul KV 中检查远程配置路径和当前
Environment是否匹配。 - 如果配置值未刷新,先确认 KV 内容已经更新,再确认当前代码读取的是同一个
IConfiguration对象。 - 对 KV 写入、删除、查询问题,先使用唯一测试前缀隔离业务数据,避免误判旧数据或其他服务写入。
# 测试验证
当前仓库已覆盖 Consul 服务治理相关单元测试与集成测试:
| 测试范围 | 测试文件 | 结果 |
|---|---|---|
| KV 与配置单元测试 | test/Air.Cloud.UnitTest/Modules/Consul/ConsulMiddlewareContractTests.cs | 5/5 通过 |
| 服务中心单元测试 | test/Air.Cloud.UnitTest/Modules/Consul/ConsulServerCenterContractTests.cs | 5/5 通过 |
| KV 与配置集成测试 | test/Air.Cloud.IntegrationTest/Modules/Consul/ConsulRealKvIntegrationTests.cs | 6/6 通过 |
| 服务中心集成测试 | test/Air.Cloud.IntegrationTest/Modules/Consul/ConsulRealServerCenterIntegrationTests.cs | 4/4 通过 |
独立验证命令:
dotnet test test/Air.Cloud.UnitTest/Air.Cloud.UnitTest.csproj --no-restore --filter "FullyQualifiedName~Air.Cloud.UnitTest.Modules.Consul" -m:1
dotnet test test/Air.Cloud.IntegrationTest/Air.Cloud.IntegrationTest.csproj --no-restore --filter "Module=Consul" -m:1
# 使用建议
- 业务代码优先依赖
IServerCenterStandard和IKVCenterStandard,不要直接依赖 Consul 客户端。 - 本地开发如果只需要本地配置,不要强行接入 Consul;需要验证远程配置时再启用 Consul。
- 生产环境必须明确配置
ServiceAddress和健康检查路由,避免容器、网关或反向代理导致注册地址不可达。 - KV 中心适合轻量配置和元数据,不适合作为高并发业务数据存储。
- 服务注册与远程配置加载属于启动链路能力,调用时机应早于业务服务初始化。
- Consul 模块当前未声明 ACL Token 配置支持,开启 ACL 的环境需要先扩展客户端配置能力。
# 实现文档
- Consul:见
模块下的服务治理标准 / Consul。