概述
服务治理(Service Governance) 是 silky 框架保障微服务可靠性的核心机制。框架通过 Polly 弹性库在客户端(调用方)和服务端(提供方)两侧分别实现多层策略保护,主要包括:
- 超时控制:防止慢接口拖垮调用方
- 重试机制:对暂时性故障自动重试
- 熔断器:防止雪崩效应
- 降级(Fallback):熔断或超时后返回兜底结果
- 负载均衡:分流策略决定请求路由到哪个实例
- 并发保护:限制单实例最大并发处理数
这些治理参数均通过 GovernanceOptions 统一配置,支持全局默认值、接口级覆盖和方法级覆盖三层配置。
GovernanceOptions — 治理参数
GovernanceOptions 是所有服务治理参数的数据模型,挂载在 ServiceEntry 和 ServiceEntryDescriptor 上:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ShuntStrategy | ShuntStrategy | Polling | 负载均衡策略 |
TimeoutMillSeconds | int | 5000 | 调用超时时间(毫秒) |
EnableCachingInterceptor | bool | true | 是否启用缓存拦截 |
EnableCircuitBreaker | bool | true | 是否启用熔断 |
ExceptionsAllowedBeforeBreaking | int | 3 | 触发熔断前允许的最大异常次数 |
BreakerSeconds | int | 60 | 熔断持续时间(秒) |
RetryTimes | int | 3 | 最大重试次数 |
RetryIntervalMillSeconds | int | 50 | 重试间隔(毫秒) |
MaxConcurrentHandlingCount | int | 50 | 服务端最大并发处理数(设为 0 表示不限制) |
AddressFuseSleepDurationSeconds | int | 60 | 端点被标识为不健康后的熔断恢复等待时间(秒) |
UnHealthAddressTimesAllowedBeforeRemoving | int | 3 | 端点被标识为不健康多少次后从服务列表移除 |
注意:
ProhibitExtranet(禁止外网访问)不在GovernanceOptions中,只能通过[Governance]特性或[ProhibitExtranet]特性在接口/方法上单独设置,无法在 appsettings 的Governance:配置节内全局配置。
三层配置优先级
治理参数按以下优先级生效(高优先级覆盖低优先级):
方法级([Governance] 特性)
> 接口级([Governance] 特性,继承到所有方法)
> 全局默认值(appsettings 中 Governance 配置节)
全局默认值(配置文件)
# appsettings.yml
Governance:
TimeoutMillSeconds: 10000 # 全局超时 10s
RetryTimes: 0 # 全局关闭重试
EnableCircuitBreaker: false # 全局关闭熔断
接口/方法级覆盖(特性)
[ServiceRoute]
[Governance(TimeoutMillSeconds = 3000, EnableCircuitBreaker = false)] // 接口级:覆盖全局
public interface IOrderAppService
{
// 方法级:覆盖接口级
[Governance(TimeoutMillSeconds = 500, ShuntStrategy = ShuntStrategy.HashAlgorithm)]
Task<OrderDto> GetByIdAsync(long id);
// 使用接口级设置(3000ms 超时)
Task<OrderDto> CreateAsync(CreateOrderInput input);
}
ReConfiguration 逻辑
ServiceEntry 在构造时会调用 ReConfiguration(governanceProvider) 方法,按照优先级将特性中的参数覆盖到 GovernanceOptions 上:
private void ReConfiguration(IGovernanceProvider governanceProvider)
{
if (governanceProvider == null) return;
// 如果方法上标注了 [Governance] 特性,用特性参数覆盖全局配置
if (governanceProvider.TimeoutMillSeconds > 0)
GovernanceOptions.TimeoutMillSeconds = governanceProvider.TimeoutMillSeconds;
if (governanceProvider.ShuntStrategy != ShuntStrategy.Polling)
GovernanceOptions.ShuntStrategy = governanceProvider.ShuntStrategy;
// ... 其他参数类似
}
超时控制
客户端超时
由 DefaultTimeoutInvokePolicyProvider 创建 Polly TimeoutPolicy:
Policy.TimeoutAsync(
TimeSpan.FromMilliseconds(serviceEntryDescriptor.GovernanceOptions.TimeoutMillSeconds),
TimeoutStrategy.Optimistic // 协作式取消(通过 CancellationToken)
);
TimeoutStrategy.Optimistic:通过传递CancellationToken协作取消,性能较好但需要被调用方响应取消信号- 超时触发后,抛出
Polly.Timeout.TimeoutRejectedException,该异常会被计入重试和熔断统计
服务端超时
服务端同样有超时策略(IHandlePolicyProvider),防止业务实现方法无限运行占用服务端线程资源。
重试机制
重试策略构建
由 InvokeFailoverPolicyProviderBase 系列实现,内置两种重试场景:
场景 1:普通故障重试(DefaultRetryInvokePolicyProvider)
Policy<object?>
.Handle<Exception>(ex =>
{
// 只对非业务异常进行重试
return !ex.IsFriendlyException()
&& !ex.IsNotFindServiceError()
&& !ex.IsFrameworkException();
})
.WaitAndRetryAsync(
retryCount: governanceOptions.RetryTimes,
sleepDurationProvider: (retryAttempt, context) =>
TimeSpan.FromMilliseconds(governanceOptions.RetryIntervalMillSeconds),
onRetry: (outcome, timespan, retryAttempt, context) =>
{
Logger.LogWarning("Retry {retryAttempt} for {serviceEntryId}...", retryAttempt, serviceEntryId);
}
);
场景 2:服务端并发溢出故障转移(OverflowServerHandleFailoverPolicyProvider)
当服务端返回 OverflowMaxServerHandleException(并发超限)时,客户端自动切换到另一个健康实例重试:
Policy<object?>
.Handle<OverflowMaxServerHandleException>()
.RetryAsync(
retryCount: availableEndpointCount - 1, // 最多尝试所有可用实例
onRetry: (outcome, retryAttempt, context) =>
{
// 将当前失败的端点加入"本次调用排除列表"
excludeEndpoints.Add(context[PollyContextNames.SelectedEndpoint]);
}
);
重试的注意事项
Warning
重试只对幂等操作安全。对于写操作(如创建订单),应将 RetryTimes = 0 关闭重试,或通过业务层实现幂等控制,否则重试会导致数据重复。
熔断器(Circuit Breaker)
工作原理
熔断器是一个状态机,有三个状态:
N次失败超过阈值
Closed ─────────────────▶ Open(拒绝所有请求)
▲ │
│ BreakerSeconds 过后 │
│ ▼
└─────── Half-Open(放行一个探测请求)
│ 成功
│ 失败 → Open
| 状态 | 说明 |
|---|---|
Closed(关闭) | 正常状态,请求正常通过 |
Open(打开) | 熔断状态,所有请求立即返回 CircuitBreakerException,不发起 RPC |
Half-Open(半开) | 熔断恢复探测,放行一个请求测试服务是否恢复 |
熔断器策略实现
Policy
.Handle<Exception>(ex =>
{
// 只有「真正的」异常才触发熔断
// 友好异常、找不到服务、框架异常、服务端异常 → 不触发熔断
var isNotCircuitBreakerException =
ex.IsFriendlyException() // UserFriendlyException
|| ex.IsNotFindServiceError() // 未找到服务
|| ex.IsFrameworkException() // 框架内部异常
|| ex.IsNotImplemented() // 未实现
|| ex.IsServerException(); // 服务端异常
return !isNotCircuitBreakerException;
})
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: governanceOptions.ExceptionsAllowedBeforeBreaking,
durationOfBreak: TimeSpan.FromSeconds(governanceOptions.BreakerSeconds),
onBreak: (ex, timespan) => { /* 熔断开启事件 */ },
onReset: () => { /* 熔断恢复事件 */ }
);
策略缓存:熔断器策略以 serviceEntryId 为 key 缓存(ConcurrentDictionary),同一服务条目在整个应用生命周期共享同一个熔断器实例,因此熔断状态是持久有效的(不随每次请求重建策略而重置)。
禁用熔断
对于某些明确不需要熔断保护的服务条目(如查询接口),可以关闭熔断:
[Governance(EnableCircuitBreaker = false)]
Task<OrderDto> GetByIdAsync(long id);
降级(Fallback)
当服务调用最终失败(超过重试次数,或熔断器打开)后,框架会尝试调用开发者预先定义的 Fallback 方法,返回兜底结果。
定义 Fallback
// 1. 在接口方法上标注 [Fallback],指定 Fallback 类型
[ServiceRoute]
public interface IOrderAppService
{
[Fallback(typeof(OrderFallback))]
Task<OrderDto> GetByIdAsync(long id);
}
// 2. 实现 Fallback 类(必须注册到 IoC 容器)
public class OrderFallback : IScopedDependency
{
// Fallback 方法签名必须与原方法一致
public Task<OrderDto> GetByIdAsync(long id)
{
// 返回缓存结果、默认值或空对象
return Task.FromResult(new OrderDto { Id = id, Status = "Unknown" });
}
}
Fallback 触发条件
Policy<object>.Handle<Exception>(ex =>
{
// 只对"需要 Fallback"的异常触发降级
// 实现了 INotNeedFallback 的异常不降级(如业务友好异常)
return !(ex is INotNeedFallback);
})
.FallbackAsync(
fallbackAction: async (ctx, token) =>
await _fallbackInvoker.Invoke(serviceEntry, parameters)
);
Fallback 与熔断的组合
Polly 策略通过 WrapAsync 嵌套,执行顺序从外到内:
FallbackPolicy(最外层:兜底)
└── CircuitBreakerPolicy(熔断保护)
└── RetryPolicy(重试)
└── TimeoutPolicy(超时,最内层)
└── 实际 RPC 调用
当内层策略全部失败后,依次向外传播,直到 FallbackPolicy 捕获并调用降级方法。
服务端并发保护
并发数限制
服务端通过 MaxConcurrentHandlingCount 限制单个服务条目的最大并发处理数:
// DefaultServerMessageReceivedHandler 中
if (getServerInstanceHandleInfo.AllowMaxConcurrentCount > 0
&& getServerInstanceHandleInfo.ConcurrentCount > getServerInstanceHandleInfo.AllowMaxConcurrentCount)
{
throw new OverflowMaxServerHandleException(
$"Exceeds the maximum allowable processing concurrency. " +
$"Current: {getServerInstanceHandleInfo.ConcurrentCount}");
}
客户端收到 OverflowMaxServerHandleException 后,由 OverflowServerHandleFailoverPolicyProvider 将请求故障转移到另一个健康实例,而不是计入普通错误重试次数。
服务端自保护熔断(FuseProtection)
当 FuseProtection = true 时,服务端在 BreakerSeconds 内发生 ExceptionsAllowedBeforeBreaking 次未知异常后,触发服务端熔断,临时拒绝该服务条目的所有请求:
Governance:
FuseProtection: true
ExceptionsAllowedBeforeBreaking: 5
FuseSleepDurationSeconds: 30
与客户端熔断的区别:
- 客户端熔断:在调用方,阻止请求发出,快速失败
- 服务端熔断:在提供方,阻止请求进入业务执行,保护数据库、文件系统等下游资源
禁止外网访问(ProhibitExtranet)
部分服务条目只允许内部 RPC 调用,不应通过网关的 HTTP 接口对外暴露。通过 ProhibitExtranet = true 实现:
[Governance(ProhibitExtranet = true)]
Task<string> GetInternalSecretAsync();
或使用专用特性:
[ProhibitExtranet]
Task<string> GetInternalSecretAsync();
网关在路由匹配时,如果请求来自外部(非 RPC 内部调用),且目标服务条目设置了 ProhibitExtranet = true,则返回 403 Forbidden。
全局治理配置示例
Governance:
ShuntStrategy: Polling # 负载均衡策略(Polling/Random/HashAlgorithm)
TimeoutMillSeconds: 5000 # 超时 5 秒
RetryTimes: 3 # 重试 3 次
RetryIntervalMillSeconds: 50 # 重试间隔 50ms
EnableCircuitBreaker: true # 启用熔断
ExceptionsAllowedBeforeBreaking: 3 # 3 次异常触发熔断
BreakerSeconds: 60 # 熔断 60 秒
EnableCachingInterceptor: true # 启用缓存拦截
FuseProtection: false # 关闭服务端自保护熔断
MaxConcurrentHandlingCount: 0 # 不限制并发数
治理能力全景图
调用方(客户端) 提供方(服务端)
┌─────────────────────────────────────┐ ┌─────────────────────────────────┐
│ 服务条目调用 │ │ 接收 RPC 请求 │
│ │ │ │
│ Fallback Policy(兜底降级) │ │ FuseProtection(服务端熔断) │
│ └── CircuitBreaker(客户端熔断) │ │ │
│ └── Retry(重试) │ TCP │ MaxConcurrentCount(并发保护) │
│ └── Timeout │─────▶│ │
│ (超时) │ │ 业务方法执行 │
│ │ │ (LocalInvoker + 过滤器管道) │
│ EndpointSelector(负载均衡) │ │ │
│ (Polling/Random/Hash/Appoint) │ │ 异常分类处理 │
│ │ │ (友好异常/验证异常/未知异常) │
└─────────────────────────────────────┘ └─────────────────────────────────┘
