- Create MigrationTool console app for exporting DB config to K8s YAML - Support dry-run mode and validation - Add Npgsql and YamlDotNet dependencies
284 lines
6.6 KiB
Markdown
284 lines
6.6 KiB
Markdown
# 阶段 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* |