- Create MigrationTool console app for exporting DB config to K8s YAML - Support dry-run mode and validation - Add Npgsql and YamlDotNet dependencies
6.6 KiB
6.6 KiB
阶段 6 研究:网关插件技术调研与实现
研究日期: 2026-03-04 状态: 已完成
1. 现有基础设施分析
1.1 已有插件抽象层
项目已创建 Fengling.Gateway.Plugin.Abstractions 程序集,定义了核心插件接口:
// 已定义的接口
public interface IGatewayPlugin
{
string Name { get; }
string Version { get; }
string? Description { get; }
Task OnLoadAsync();
Task OnUnloadAsync();
}
public interface IRequestPlugin : IGatewayPlugin { ... }
public interface IResponsePlugin : IGatewayPlugin { ... }
public interface IRouteTransformPlugin : IGatewayPlugin { ... }
public interface ILoadBalancePlugin : IGatewayPlugin { ... }
1.2 缺失的组件
- ❌ 插件加载器 - 动态加载程序集的机制
- ❌ 插件生命周期管理 - 加载/卸载/热重载
- ❌ 插件隔离 - AssemblyLoadContext 隔离
- ❌ YARP 集成 - 将插件集成到 YARP 管道
2. YARP 扩展点
2.1 中间件管道
YARP 使用 ASP.NET Core 中间件管道,允许自定义注入点:
app.MapReverseProxy(proxyPipeline =>
{
proxyPipeline.Use(async (context, next) =>
{
// 插件前置执行
await pluginFeature.ExecutePreProxyAsync(context);
await next();
// 插件后置执行
await pluginFeature.ExecutePostProxyAsync(context);
});
});
2.2 Transform 管道(推荐)
Transform 是修改请求/响应的推荐方式:
builder.Services.AddReverseProxy()
.AddTransforms(context =>
{
var plugins = PluginManager.GetTransformPlugins(context.Route);
foreach (var plugin in plugins)
{
context.AddRequestTransform(plugin.ApplyAsync);
}
});
2.3 路由扩展
YARP 2.0+ 支持自定义路由元数据,插件可消费:
{
"Routes": {
"api-route": {
"Extensions": {
"PluginConfig": {
"PluginId": "rate-limiter",
"MaxRequests": 100
}
}
}
}
}
3. .NET 插件加载最佳实践
3.1 AssemblyLoadContext 隔离
使用 可卸载的 AssemblyLoadContext 配合 AssemblyDependencyResolver:
public sealed class PluginLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;
private readonly string _sharedAssemblyName = "Fengling.Gateway.Plugin.Abstractions";
public PluginLoadContext(string pluginPath) : base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly? Load(AssemblyName assemblyName)
{
// 共享契约程序集(防止类型身份问题)
if (assemblyName.Name == _sharedAssemblyName)
return null; // 回退到默认 ALC
var path = _resolver.ResolveAssemblyToPath(assemblyName);
return path != null ? LoadFromAssemblyPath(path) : null;
}
}
3.2 影子复制(热重载必需)
Windows 锁定加载的 DLL,需要影子复制:
public static string CreateShadowCopy(string pluginDirectory)
{
var shadowDir = Path.Combine(
Path.GetTempPath(),
"PluginShadows",
$"{Path.GetFileName(pluginDirectory)}_{Guid.NewGuid():N}"
);
CopyDirectory(pluginDirectory, shadowDir);
return shadowDir;
}
3.3 插件句柄模式
public sealed class PluginHandle : IAsyncDisposable
{
private readonly PluginLoadContext _alc;
private readonly IGatewayPlugin _plugin;
public async ValueTask DisposeAsync()
{
if (_plugin is IAsyncDisposable ad) await ad.DisposeAsync();
_alc.Unload(); // 计划回收
}
}
3.4 卸载验证
public static bool VerifyUnload(WeakReference weakRef, int maxAttempts = 10)
{
for (var i = 0; i < maxAttempts && weakRef.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
return !weakRef.IsAlive;
}
4. 插件隔离模式
4.1 契约边界(最重要的设计决策)
规则:契约必须驻留在 默认 ALC,绝不在插件 ALC 中。
插件项目配置:
<ProjectReference Include="..\Plugin.Abstractions\Plugin.Abstractions.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
4.2 静态缓存问题
问题:主机单例缓存插件类型会阻止卸载。
解决方案:只使用 DTO 跨越边界,不传递插件类型。
5. 实现架构
5.1 组件结构
src/
├── Fengling.Gateway.Plugin.Abstractions/ # 已存在
│ └── IGatewayPlugin.cs
├── yarpgateway/
│ ├── Plugins/
│ │ ├── PluginLoadContext.cs # ALC 隔离
│ │ ├── PluginLoader.cs # 加载逻辑
│ │ ├── PluginHost.cs # 生命周期管理
│ │ └── PluginMiddleware.cs # YARP 集成
│ └── Program.cs
└── plugins/ # 插件目录
└── sample-plugin/
└── SamplePlugin.csproj
5.2 插件目录结构
plugins/
├── rate-limiter/
│ ├── RateLimiterPlugin.dll
│ ├── RateLimiterPlugin.deps.json
│ └── plugin.json # 元数据
└── jwt-transform/
└── ...
5.3 插件元数据 (plugin.json)
{
"id": "rate-limiter",
"name": "Rate Limiter Plugin",
"version": "1.0.0",
"entryPoint": "RateLimiterPlugin.RateLimiterPlugin",
"interfaces": ["IRequestPlugin"],
"dependencies": []
}
6. 验证架构
6.1 测试策略
- 单元测试:PluginLoadContext 隔离验证
- 集成测试:插件加载/卸载/热重载
- 性能测试:插件执行开销
6.2 成功标准验证
| 标准 | 验证方法 |
|---|---|
| 动态加载插件 | 单元测试:从目录加载并执行 |
| 插件相互隔离 | 单元测试:异常不传播到其他插件 |
| 热加载/卸载 | 集成测试:WeakReference 验证卸载 |
7. 推荐库
| 用途 | 库 |
|---|---|
| 网关核心 | Yarp.ReverseProxy 2.3.0+ |
| 插件加载 | 自定义 AssemblyLoadContext |
| 元数据读取 | System.Reflection.Metadata |
| 依赖注入 | Microsoft.Extensions.DependencyInjection |
8. 关键注意事项
- 不要在主机单例中缓存插件类型
- 始终用 WeakReference 测试卸载
- Windows 上必须影子复制以支持热重载
- 使用仅元数据发现避免仅扫描而加载程序集
- 谨慎处理原生依赖(它们不能干净卸载)
研究完成:2026-03-04