概述
在 silky 微服务集群中,当服务 A 需要调用服务 B 的接口时,服务 A 内部并不存在服务 B 接口的实现类。这意味着无法直接将服务 B 的接口注入到服务 A 的业务类中使用。
silky 通过 Castle.DynamicProxy(动态代理)解决这一问题:框架在启动时自动为所有远程服务接口(没有本地实现类的 [ServiceRoute] 接口)生成一个运行时代理对象,并将该代理注册到 IoC 容器中。注入方使用接口进行依赖注入时,实际得到的是该代理对象,调用接口方法时由代理内部通过 RPC 完成远程调用。
代理的识别
框架通过 ServiceHelper.FindServiceProxyTypes() 找到需要生成代理的接口类型——即所有没有本地实现类的服务接口:
public static IEnumerable<Type> FindServiceProxyTypes(ITypeFinder typeFinder)
{
// FindAllServiceTypes 返回 (Type, IsLocal) 元组
// 过滤出 IsLocal = false 的接口,即没有本地实现的远程服务接口
var proxyTypes = FindAllServiceTypes(typeFinder)
.Where(p => !p.Item2) // Item2 = IsLocal
.Select(p => p.Item1); // Item1 = 接口类型
return proxyTypes;
}
以订单微服务为例,假设 IAccountAppService 定义在 account.contract 包中,订单微服务依赖了该包但没有实现 IAccountAppService,那么 IAccountAppService 就会被识别为需要生成代理的远程服务接口。
代理注册
RpcProxyCollectionExtensions.AddRpcProxy() 在 RpcProxyModule.ConfigureServices() 阶段完成代理的创建与注册:
public static IServiceCollection AddRpcProxy(this IServiceCollection services)
{
var serviceProxyTypes =
ServiceHelper.FindServiceProxyTypes(EngineContext.Current.TypeFinder);
foreach (var serviceServiceType in serviceProxyTypes)
{
AddAServiceClientProxy(services, serviceServiceType);
}
return services;
}
private static void AddAServiceClientProxy(this IServiceCollection services, Type type)
{
// 代理拦截器类型:RpcClientProxyInterceptor(Castle 拦截器适配器)
var rpcProxyInterceptorType = typeof(RpcClientProxyInterceptor);
services.AddTransient(rpcProxyInterceptorType);
// 将 Castle 拦截器包装为 Autofac/DI 可识别的类型
var rpcInterceptorAdapterType =
typeof(SilkyAsyncDeterminationInterceptor<>).MakeGenericType(rpcProxyInterceptorType);
// 使用 Castle.DynamicProxy 创建接口代理并注册到 DI
services.AddTransient(
type, // 注册的接口类型,例如 IAccountAppService
serviceProvider => ProxyGeneratorInstance
.CreateInterfaceProxyWithoutTarget( // 不需要真实实现目标
type,
(IInterceptor)serviceProvider.GetRequiredService(rpcInterceptorAdapterType)
)
);
}
每个远程服务接口都被注册为**瞬时(Transient)**依赖。代理对象在注入时创建,不持有状态。
代理拦截器(RpcClientProxyInterceptor)
RpcClientProxyInterceptor 是代理的核心,当业务代码调用代理对象的任意方法时,Castle 拦截器会转入 InterceptAsync() 方法执行:
public class RpcClientProxyInterceptor : SilkyInterceptor, ITransientDependency
{
private readonly IIdGenerator _idGenerator;
private readonly IServiceEntryLocator _serviceEntryLocator;
private readonly IServiceKeyExecutor _serviceKeyExecutor;
private readonly IExecutor _executor; // 统一执行器(本地/远程决策)
public override async Task InterceptAsync(ISilkyMethodInvocation invocation)
{
// 1. 根据方法信息生成 ServiceEntryId,定位对应的 ServiceEntry
var serviceEntryId = _idGenerator.GetDefaultServiceEntryId(invocation.Method);
var serviceEntry = _serviceEntryLocator.GetServiceEntryById(serviceEntryId);
if (serviceEntry == null)
{
throw new NotFindServiceEntryException(
$"Could not find service entry with id '{serviceEntryId}'.");
}
try
{
if (invocation.Method.ReturnType == typeof(void)
|| invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty())
{
// 无返回值方法
await _executor.Execute(serviceEntry, invocation.Arguments, _serviceKeyExecutor.ServiceKey);
}
else
{
// 有返回值方法:将返回值写回 ReturnValue 属性
invocation.ReturnValue =
await _executor.Execute(serviceEntry, invocation.Arguments, _serviceKeyExecutor.ServiceKey);
}
}
catch (Exception)
{
// 执行失败时,如果配置了 Fallback,走 Fallback(继续执行调用链)
if (serviceEntry.FallbackMethodExecutor != null && serviceEntry.FallbackProvider != null)
{
await invocation.ProceedAsync();
return;
}
throw;
}
}
}
拦截流程:
业务代码调用 IAccountAppService.GetByIdAsync(id)
│
▼(Castle 拦截)
RpcClientProxyInterceptor.InterceptAsync()
│ 1. 生成 ServiceEntryId
│ 2. 通过 ServiceEntryLocator 查找 ServiceEntry
│ 3. 调用 IExecutor.Execute()
▼
DefaultExecutor.Execute()
│ serviceEntry.IsLocal == false
▼(远程分支)
RemoteExecutor(Polly 策略 → LoadBalance → DotNetty RPC)
│
▼
目标微服务实例处理并返回结果
│
▼
invocation.ReturnValue = result(写回调用方)
完整调用示意
// 订单微服务中注入账户服务接口(无本地实现,实际注入的是代理对象)
public class OrderService : IOrderService, IScopedDependency
{
private readonly IAccountAppService _accountService;
// 构造函数注入,得到的是动态代理对象
public OrderService(IAccountAppService accountService)
{
_accountService = accountService;
}
public async Task<OrderDto> CreateAsync(CreateOrderInput input)
{
// 这行代码看似是普通的本地调用,实际上触发了 Castle 拦截
// 最终通过 DotNetty RPC 发送到账户微服务实例执行
var account = await _accountService.GetByIdAsync(input.AccountId);
// ... 业务逻辑
}
}
代理模块依赖
RpcProxyModule 依赖 RpcModule 和 CastleModule:
RpcModule:提供IServiceEntryLocator、IExecutor等 RPC 核心组件CastleModule:提供 Castle DynamicProxy 的 Autofac 适配层(SilkyAsyncDeterminationInterceptor)
包含 RpcProxyModule 的启动模块(DefaultGeneralHostModule、DefaultWebHostModule 等)在应用启动时自动完成代理注册,无需开发者显式配置。
ServiceKey — 多实现路由
当远程服务有多个实现(通过 [ServiceKey] 标注),调用方可通过 IServiceKeyExecutor 指定目标实现:
// 在 HTTP 请求头中传入 ServiceKey
// ServiceKey: v2
// 或在代码中手动指定(适用于服务内部调用)
using (serviceKeyExecutor.Change("v2"))
{
var result = await _accountService.GetByIdAsync(id);
}
RpcClientProxyInterceptor 中的 _serviceKeyExecutor.ServiceKey 会读取当前 AsyncLocal 上下文中的 ServiceKey,并在 RpcContext 中透传到服务端,由服务端根据 ServiceKey 选择对应的实现类执行。
与直接使用 HTTP 客户端的区别
| 对比项 | silky RPC 代理 | HttpClient |
|---|---|---|
| 协议 | DotNetty TCP(二进制帧) | HTTP/1.1 |
| 性能 | 更低延迟,长连接复用 | 相对较高延迟 |
| 服务发现 | 框架自动,基于注册中心 | 需手动配置或引入服务发现组件 |
| 调用方式 | 强类型接口注入,透明调用 | 手动构造请求,需处理序列化 |
| 治理能力 | 内置超时/重试/熔断/降级 | 需手动集成 Polly |
| 适用场景 | 微服务内部通信 | 对接外部 HTTP API |
