fengling-gateway/.planning/phases/006-gateway-plugin-research/006-RESEARCH.md
movingsam 52eba07097 feat: add MigrationTool for gateway config migration (IMPL-7)
- Create MigrationTool console app for exporting DB config to K8s YAML
- Support dry-run mode and validation
- Add Npgsql and YamlDotNet dependencies
2026-03-08 00:35:04 +08:00

284 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 阶段 6 研究:网关插件技术调研与实现
**研究日期:** 2026-03-04
**状态:** 已完成
---
## 1. 现有基础设施分析
### 1.1 已有插件抽象层
项目已创建 `Fengling.Gateway.Plugin.Abstractions` 程序集,定义了核心插件接口:
```csharp
// 已定义的接口
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 中间件管道,允许自定义注入点:
```csharp
app.MapReverseProxy(proxyPipeline =>
{
proxyPipeline.Use(async (context, next) =>
{
// 插件前置执行
await pluginFeature.ExecutePreProxyAsync(context);
await next();
// 插件后置执行
await pluginFeature.ExecutePostProxyAsync(context);
});
});
```
### 2.2 Transform 管道(推荐)
Transform 是修改请求/响应的推荐方式:
```csharp
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+ 支持自定义路由元数据,插件可消费:
```json
{
"Routes": {
"api-route": {
"Extensions": {
"PluginConfig": {
"PluginId": "rate-limiter",
"MaxRequests": 100
}
}
}
}
}
```
---
## 3. .NET 插件加载最佳实践
### 3.1 AssemblyLoadContext 隔离
使用 **可卸载的 AssemblyLoadContext** 配合 **AssemblyDependencyResolver**
```csharp
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需要影子复制
```csharp
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 插件句柄模式
```csharp
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 卸载验证
```csharp
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 中。
插件项目配置:
```xml
<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)
```json
{
"id": "rate-limiter",
"name": "Rate Limiter Plugin",
"version": "1.0.0",
"entryPoint": "RateLimiterPlugin.RateLimiterPlugin",
"interfaces": ["IRequestPlugin"],
"dependencies": []
}
```
---
## 6. 验证架构
### 6.1 测试策略
1. **单元测试**PluginLoadContext 隔离验证
2. **集成测试**:插件加载/卸载/热重载
3. **性能测试**:插件执行开销
### 6.2 成功标准验证
| 标准 | 验证方法 |
|------|---------|
| 动态加载插件 | 单元测试:从目录加载并执行 |
| 插件相互隔离 | 单元测试:异常不传播到其他插件 |
| 热加载/卸载 | 集成测试WeakReference 验证卸载 |
---
## 7. 推荐库
| 用途 | 库 |
|------|---|
| 网关核心 | Yarp.ReverseProxy 2.3.0+ |
| 插件加载 | 自定义 AssemblyLoadContext |
| 元数据读取 | System.Reflection.Metadata |
| 依赖注入 | Microsoft.Extensions.DependencyInjection |
---
## 8. 关键注意事项
1. **不要在主机单例中缓存插件类型**
2. **始终用 WeakReference 测试卸载**
3. **Windows 上必须影子复制以支持热重载**
4. **使用仅元数据发现避免仅扫描而加载程序集**
5. **谨慎处理原生依赖**(它们不能干净卸载)
---
*研究完成2026-03-04*