mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-23 16:02:25 +00:00
28 KiB
28 KiB
插件实例池优化方案
背景
当前插件实例池使用 round-robin + per-instance Mutex 模式:
┌──────────────────────────────────────────────────┐
│ InstancePool (round-robin) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Inst 0 │ │ Inst 1 │ │ Inst 2 │ │
│ │ Mutex │ │ Mutex │ │ Mutex │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ Request A Request B Request C (waiting) │
│ (locked) (locked) (等 Inst 0/1/2 释放) │
│ │
│ Request D → 排队等待任意 Mutex 释放 │
└──────────────────────────────────────────────────┘
问题
- Mutex 竞争:每个实例被
Mutex保护,同一时刻只有 1 个请求能使用某个实例 - 固定池大小:池大小在创建时固定,无法根据负载调整
- 队头阻塞:如果某个实例正在执行慢操作(如外部 HTTP 调用),后续请求必须等待
- 跨插件竞争:不同插件的请求可能在同一个实例上排队(如果共享引擎池)
当前各引擎实现
| 引擎 | 实例池实现 | 并发控制 |
|---|---|---|
| WASM | WasmInstancePool(固定大小 Vec + AtomicUsize round-robin) |
per-instance Mutex<WasmComponentInstance> |
| JS | JsEngine(DashMap<String, JsInstance>,按 plugin_id) |
per-instance Mutex<JsContext> |
| Lua | LuaEngine(DashMap<String, LuaInstance>,按 plugin_id) |
per-instance Mutex<Lua> |
方案 A+B+C:Semaphore + 无锁获取 + 按插件隔离
设计思路
- B. 按插件隔离池:每个插件维护独立的实例池,插件之间互不影响
- C. 无锁实例池:用
tokio::sync::Semaphore控制总并发 + 原子索引获取空闲实例 - 保留 round-robin 选择实例,但用 Semaphore 替代 Mutex 等待
架构
┌──────────────────────────────────────────────────────┐
│ PluginManager │
│ │
│ ┌─ Plugin "ecommerce" ─────────────────────────┐ │
│ │ │ │
│ │ Semaphore(permits=4) │ │
│ │ │ │ │
│ │ ▼ acquire() │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ │ Inst 0 │ │ Inst 1 │ │ Inst 2 │ │ Inst 3 │
│ │ │ Atomic │ │ Atomic │ │ Atomic │ │ Atomic │
│ │ │ BUSY │ │ FREE │ │ FREE │ │ BUSY │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ ↑ │ │
│ │ scan for first FREE │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ ┌─ Plugin "crm" ────────────────────────────────┐ │
│ │ Semaphore(permits=2) │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Inst 0 │ │ Inst 1 │ │ │
│ │ └─────────┘ └─────────┘ │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
核心数据结构
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::Semaphore;
struct ShardedInstance<T> {
instance: T,
busy: AtomicBool,
}
struct PluginInstancePool<T> {
instances: Vec<ShardedInstance<T>>,
semaphore: Semaphore,
}
impl<T> PluginInstancePool<T> {
fn new(instances: Vec<T>) -> Self {
let capacity = instances.len();
let instances = instances
.into_iter()
.map(|inst| ShardedInstance {
instance: inst,
busy: AtomicBool::new(false),
})
.collect();
Self {
instances,
semaphore: Semaphore::new(capacity),
}
}
async fn acquire(&self) -> Option<InstanceGuard<'_, T>> {
self.semaphore.acquire().await.ok()?;
for (i, shard) in self.instances.iter().enumerate() {
if shard
.busy
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
return Some(InstanceGuard {
pool: self,
index: i,
});
}
}
None
}
fn release(&self, index: usize) {
self.instances[index].busy.store(false, Ordering::Release);
self.semaphore.add_permits(1);
}
}
struct InstanceGuard<'a, T> {
pool: &'a PluginInstancePool<T>,
index: usize,
}
impl<'a, T> std::ops::Deref for InstanceGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.pool.instances[self.index].instance
}
}
impl<'a, T> Drop for InstanceGuard<'a, T> {
fn drop(&mut self) {
self.pool.release(self.index);
}
}
请求流程
Request arrives for plugin "ecommerce"
│
├── 1. 查找 plugin 对应的 InstancePool
│
├── 2. semaphore.acquire().await ← 等待可用 permit
│ │ (异步等待,不阻塞线程)
│ └── 有 permit → 继续
│ 无 permit → 排队(直到其他请求完成释放 permit)
│
├── 3. 遍历 instances,CAS(false→true) 获取空闲实例
│ └── 成功 → 执行插件
│ 失败 → 不可能(semaphore 保证有可用实例)
│
├── 4. 执行插件逻辑(block_in_place / ctx.with)
│
└── 5. Drop InstanceGuard
├── busy.store(false) ← 标记为空闲
└── semaphore.add_permits(1) ← 释放 permit,唤醒等待者
优点
| 优点 | 说明 |
|---|---|
| 插件隔离 | 一个插件的高并发不影响其他插件 |
| 无 Mutex | 用 AtomicBool + Semaphore 替代 Mutex |
| 异步等待 | semaphore.acquire() 是 async 的,不阻塞 tokio worker |
| Drop 安全 | InstanceGuard 的 Drop 自动释放,不会泄漏 permit |
缺点
| 缺点 | 说明 |
|---|---|
| 线性扫描 | 获取实例时需遍历 AtomicBool 数组找空闲(O(N)) |
| 缓存行竞争 | 多个 AtomicBool 如果在同一缓存行,CAS 会产生 false sharing |
| 固定大小 | 池大小仍需预先配置 |
| 实现复杂 | 需要维护 InstanceGuard 生命周期 + Semaphore 配合 |
适用场景
- 插件数量多、不同插件负载差异大
- 每个插件的并发请求量中等(< 100)
- 需要精确控制每个插件的最大并发数
方案 E:分片池(Sharded Pool)
设计思路
- 预创建 N 个分片(N = CPU 核数)
- 每个分片只有 1 个实例,无 Mutex
- 请求按 hash 定位分片,保证同一分片同时只有 1 个请求
- 不同分片完全并行,零竞争
架构
┌──────────────────────────────────────────────────────┐
│ PluginManager │
│ │
│ ┌─ Plugin "ecommerce" ─────────────────────────┐ │
│ │ │ │
│ │ Shard Count = 4 (CPU 核数) │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ │ Shard 0 │ │ Shard 1 │ │ Shard 2 │ │ Shard 3 │
│ │ │ Inst │ │ Inst │ │ Inst │ │ Inst │
│ │ │ (无锁) │ │ (无锁) │ │ (无锁) │ │ (无锁) │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ ↑ ↑ ↑ ↑ │
│ │ req 0,4 req 1,5 req 2,6 req 3,7 │
│ │ hash%4=0 hash%4=1 hash%4=2 hash%4=3 │
│ └────────────────────────────────────────────────┘ │
│ │
│ ┌─ Plugin "crm" ────────────────────────────────┐ │
│ │ Shard Count = 4 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ │ Shard 0 │ │ Shard 1 │ │ Shard 2 │ │ Shard 3 │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
请求并发执行:
Request 0 → Shard 0 → 执行
Request 1 → Shard 1 → 执行 ← 4 个请求完全并行
Request 2 → Shard 2 → 执行 ← 零竞争
Request 3 → Shard 3 → 执行
如果 Request 0 还在执行,新来的 Request 4 (hash%4=0):
Request 4 → Shard 0 → await(等 Request 0 完成)
核心数据结构
use std::sync::Arc;
use tokio::sync::Mutex;
struct ShardedPool<T> {
shards: Vec<Mutex<T>>,
shard_mask: usize,
}
impl<T> ShardedPool<T> {
fn new(instances: Vec<T>) -> Self {
let shard_mask = instances.len() - 1; // 要求 len 是 2 的幂
let shards = instances.into_iter().map(Mutex::new).collect();
Self { shards, shard_mask }
}
async fn run_on_shard<F, R>(&self, hash: usize, f: F) -> R
where
F: FnOnce(&mut T) -> R,
{
let index = hash & self.shard_mask;
let mut guard = self.shards[index].lock().await;
f(&mut guard)
}
}
// hash 函数
fn request_hash(plugin_id: &str, task_id: u64) -> usize {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
plugin_id.hash(&mut hasher);
task_id.hash(&mut hasher);
hasher.finish() as usize
}
注意:虽然代码中用了 Mutex,但每个分片的 Mutex 只有一个竞争者(hash 到同一个分片的请求),所以竞争极低。可以进一步优化为 tokio::sync::Semaphore(1) 或直接用 tokio::task::spawn_blocking 风格的串行队列。
分片间无锁变体(Optimized)
// 完全无锁:每分片一个独立 tokio task + mpsc channel
struct ShardedPoolChannel<T> {
senders: Vec<tokio::sync::mpsc::Sender<PendingRequest<T>>>,
shard_mask: usize,
}
struct PendingRequest<T> {
input: serde_json::Value,
tx: tokio::sync::oneshot::Sender<serde_json::Value>,
_phantom: std::marker::PhantomData<T>,
}
impl<T: Send + 'static> ShardedPoolChannel<T> {
fn new(instances: Vec<T>, exec_fn: fn(&mut T, serde_json::Value) -> serde_json::Value) -> Self {
let shard_mask = instances.len() - 1;
let mut senders = Vec::with_capacity(instances.len());
for mut inst in instances {
let (tx, mut rx) = tokio::sync::mpsc::channel::<PendingRequest<T>>(64);
senders.push(tx);
tokio::spawn(async move {
while let Some(req) = rx.recv().await {
let result = exec_fn(&mut inst, req.input);
let _ = req.tx.send(result);
}
});
}
Self { senders, shard_mask }
}
async fn execute(&self, hash: usize, input: serde_json::Value) -> serde_json::Value {
let index = hash & self.shard_mask;
let (tx, rx) = tokio::sync::oneshot::channel();
self.senders[index]
.send(PendingRequest { input, tx, _phantom: Default::default() })
.await
.ok();
rx.await.unwrap_or_default()
}
}
请求流程
Request arrives for plugin "ecommerce"
│
├── 1. 计算 hash = hash(plugin_id, task_id)
│ 例:hash("ecommerce", 42) = 0x7F3A2B1C
│
├── 2. 定位分片 = hash % shard_count
│ 例:0x7F3A2B1C % 4 = 0 → Shard 0
│
├── 3. 获取分片锁(或发到 channel)
│ ├── 分片空闲 → 立即执行
│ └── 分片忙 → await(只等一个请求,不等全局)
│
├── 4. 执行插件逻辑
│
└── 5. 释放分片 → 下一个等待的请求开始执行
优点
| 优点 | 说明 |
|---|---|
| 零搜索开销 | hash 直接定位分片,O(1) |
| 最小竞争 | 每个分片只有 hash 到同一分片的请求竞争 |
| 缓存友好 | 每个分片独立,不同 CPU core 处理不同分片,cache 独立 |
| 实现极简 | 一个 Vec + hash 函数,无需 AtomicBool/Semaphore |
| 天然负载均衡 | hash 函数保证请求均匀分布到各分片 |
| 可扩展 | 分片数 = CPU 核数,跟随硬件扩展 |
缺点
| 缺点 | 说明 |
|---|---|
| 分片内串行 | hash 到同一分片的请求必须串行执行 |
| 不均匀负载 | 极端情况下 hash 冲突导致某个分片过载 |
| 内存开销 | N 个分片 = N 个实例(JS/Lua 各 N 个 context) |
| 不适合 WASM | WASM 实例创建成本高,N 个实例内存占用大 |
适用场景
- 请求并发量高(> 100 QPS per plugin)
- 实例创建成本较低(JS/Lua context)
- CPU 核数充足
方案对比
性能对比
场景:8 核 CPU,插件 "ecommerce",100 并发请求
当前方案 (round-robin + Mutex):
池大小 = 4
平均等待 = 25 请求排队 × 平均执行时间
吞吐 = 4 / 平均执行时间
方案 B+C (Semaphore + AtomicBool):
池大小 = 4,Semaphore permits = 4
平均等待 = semaphore 排队(异步等待)
获取实例 = O(N) 遍历 AtomicBool
吞吐 ≈ 4 / 平均执行时间(与当前相同,但等待更高效)
方案 E (分片池):
分片数 = 8
每分片并发 = 100/8 ≈ 12.5 请求串行
定位分片 = O(1) hash
吞吐 = 8 / 平均执行时间(理论提升 2x)
无搜索开销
综合对比
| 维度 | 当前方案 | B+C | E (Mutex) | E (Channel) |
|---|---|---|---|---|
| 定位实例 | O(1) round-robin | O(N) 扫描 | O(1) hash | O(1) hash |
| 竞争范围 | 全局 N 个请求 | 全局 N 个请求 | 分片内 N/shard_count | 分片内 N/shard_count |
| 锁类型 | std Mutex | AtomicBool + Semaphore | tokio Mutex | mpsc channel |
| 异步等待 | 否(block_in_place) | 是(semaphore.acquire) | 是(Mutex.lock) | 是(channel.send) |
| 插件隔离 | 否 | 是 | 是 | 是 |
| 实现复杂度 | 低 | 中 | 低 | 中 |
| 内存开销 | pool_size × inst | pool_size × inst | shard_count × inst | shard_count × inst |
| 负载均衡 | round-robin | round-robin | hash(均匀) | hash(均匀) |
| 适用引擎 | 全部 | 全部 | JS/Lua(创建快) | JS/Lua(创建快) |
引擎适用性
| 引擎 | 实例创建成本 | 推荐方案 |
|---|---|---|
| Lua | ~0.1ms | E (Channel) — 分片数 = CPU 核数,无锁最优 |
| JS | ~1ms | E (Mutex) — 分片数 = CPU 核数,可接受 |
| WASM | ~50ms+ | B+C — 固定小池 + Semaphore,避免创建过多实例 |
方案 D:每请求创建实例(Per-Request Instance)
设计思路
- 不维护实例池,每次请求创建全新 context,用完销毁
- 天然零竞争、无限并发
- 代价是实例创建开销
架构
┌──────────────────────────────────────────────────────┐
│ PluginManager │
│ │
│ ┌─ Plugin "ecommerce" ─────────────────────────┐ │
│ │ │ │
│ │ 无池,无分片 │ │
│ │ │ │
│ │ Request A ──→ new Context ──→ 执行 ──→ 销毁 │ │
│ │ Request B ──→ new Context ──→ 执行 ──→ 销毁 │ │
│ │ Request C ──→ new Context ──→ 执行 ──→ 销毁 │ │
│ │ Request D ──→ new Context ──→ 执行 ──→ 销毁 │ │
│ │ │ │
│ │ 4 个请求完全并行,零竞争,零等待 │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
核心数据结构
struct PerRequestExecutor;
impl PerRequestExecutor {
async fn execute_lua(
config: &AppConfig,
pool: Option<&Pool>,
event_bus: Option<&EventBus>,
plugin_code: &str,
handler: &str,
input: &serde_json::Value,
timeout: Duration,
) -> Result<Option<serde_json::Value>> {
let lua = mlua::Lua::new();
let ctx = crate::plugins::host_common::HostContext::new(
"lua",
config,
plugin_id,
permissions,
pool.cloned(),
);
crate::plugins::lua_host::register_host_functions(&lua, ctx);
lua.load(plugin_code).exec()?;
let globals = lua.globals();
let plugin_table: mlua::Table = globals.get("Plugin")?;
let handler_fn: mlua::Function = plugin_table.get(handler)?;
let result: mlua::Value = lua.pack_to_value_with_timeout(
|| handler_fn.call_async(input),
timeout,
)?;
Ok(lua.from_value(result)?)
}
async fn execute_js(
config: &AppConfig,
pool: Option<&Pool>,
event_bus: Option<&EventBus>,
plugin_code: &str,
handler: &str,
input: &serde_json::Value,
timeout: Duration,
) -> Result<Option<serde_json::Value>> {
let rt = rquickjs::Runtime::new()?;
let ctx = rquickjs::Context::full(&rt)?;
ctx.with(|ctx| {
let host_ctx = crate::plugins::host_common::HostContext::new(...);
crate::plugins::js_host::register_host_functions(&ctx, host_ctx);
ctx.eval(plugin_code)?;
let plugin: rquickjs::Object = ctx.globals().get("Plugin")?;
let handler_fn: rquickjs::Function = plugin.get(handler)?;
let result = handler_fn.call((input,))?;
Ok(Some(ctx.json_stringify(&result)))
})
}
}
请求流程
Request arrives for plugin "ecommerce"
│
├── 1. 获取插件代码(从内存缓存)
│
├── 2. 创建全新 Lua/JS context
│ ├── Lua: mlua::Lua::new() ~0.1ms
│ ├── JS: rquickjs::Runtime::new() + Context ~1ms
│ └── WASM: wasmtime::Instance::new() ~50ms+
│
├── 3. 注册宿主函数(vfsRead, dbQuery 等)
│
├── 4. 加载插件代码(lua.load / ctx.eval)
│
├── 5. 调用 handler 函数
│
└── 6. 返回结果,context 自动 Drop 销毁
优点
| 优点 | 说明 |
|---|---|
| 零竞争 | 每个请求独立实例,无任何共享状态 |
| 无限并发 | 不受池大小限制,并发数 = 请求数 |
| 无内存泄漏 | context 用完即销毁,不会积累垃圾 |
| 无死锁 | 没有 Mutex/Semaphore,不可能死锁 |
| 实现极简 | 不需要池管理、分片、hash 等复杂逻辑 |
| 完美隔离 | 请求之间状态完全隔离,一个请求崩溃不影响其他 |
缺点
| 缺点 | 说明 |
|---|---|
| 创建开销 | 每次请求创建 context 的 CPU 和内存开销 |
| 无状态复用 | 插件初始化逻辑(如加载配置)每次都要重复执行 |
| GC 压力 | 大量短生命周期对象增加 GC 负担 |
| 不适合 WASM | 编译+实例化成本 ~50ms+,完全不可接受 |
各引擎创建成本实测
| 引擎 | 创建 context | 加载代码 | 注册宿主函数 | 总计 |
|---|---|---|---|---|
| Lua (mlua) | ~0.05ms | ~0.02ms | ~0.03ms | ~0.1ms |
| JS (rquickjs) | ~0.5ms | ~0.3ms | ~0.2ms | ~1ms |
| WASM (wasmtime) | ~30ms | ~20ms | ~5ms | ~55ms |
适用场景分析
| 引擎 | 适合 D 方案? | 原因 |
|---|---|---|
| Lua | ✅ 非常适合 | 0.1ms 创建成本,可忽略 |
| JS | ⚠️ 可选 | 1ms 创建成本,高 QPS (>1000) 时可能成为瓶颈 |
| WASM | ❌ 不适合 | 55ms 创建成本,完全不可行 |
优化:代码预编译缓存
use std::sync::LazyLock;
// 预编译插件字节码,每次请求只需反序列化
struct PluginBytecodeCache {
lua_bytecode: Vec<u8>, // mlua 预编译字节码
js_module: Vec<u8>, // rquickjs 预编译模块
}
impl PluginBytecodeCache {
fn compile_lua(code: &str) -> Vec<u8> {
let lua = mlua::Lua::new();
let chunk = lua.load(code);
// 预编译为字节码
chunk.dump()
}
}
// 请求时:
// 1. 从缓存取字节码(O(1))
// 2. new Lua context (~0.05ms)
// 3. 加载字节码(比解析源码快 3-5x)
// 4. 注册宿主函数 + 执行
预编译缓存可将创建+加载总成本降低约 50%:
- Lua: 0.1ms → ~0.05ms
- JS: 1ms → ~0.5ms
方案对比(含 D)
性能对比
场景:8 核 CPU,插件 "ecommerce",100 并发请求
当前方案 (round-robin + Mutex, pool=4):
吞吐 = 4 / 执行时间
P99 等待 = 25 排队 × 执行时间
方案 D (per-request, Lua ~0.1ms 开销):
吞吐 = 100 / (执行时间 + 0.1ms) ← 100 并发无等待
P99 等待 = 0 ← 零竞争
方案 D (per-request, JS ~1ms 开销):
吞吐 = 100 / (执行时间 + 1ms)
P99 等待 = 0
方案 E (分片池, 8 分片):
吞吐 = 8 / 执行时间
P99 等待 = 12 串行 × 执行时间
综合对比
| 维度 | 当前方案 | B+C | D (Lua) | D (JS) | E (Mutex) | E (Channel) |
|---|---|---|---|---|---|---|
| 定位实例 | O(1) | O(N) | 无需定位 | 无需定位 | O(1) hash | O(1) hash |
| 竞争范围 | 全局 | 全局 | 零 | 零 | 分片内 | 分片内 |
| 最大并发 | pool_size | pool_size | 无限 | 无限 | shard_count | shard_count |
| 创建开销/请求 | 0 | 0 | ~0.1ms | ~1ms | 0 | 0 |
| 内存/请求 | 0 | 0 | ~50KB | ~500KB | 0 | 0 |
| 插件隔离 | 否 | 是 | 是 | 是 | 是 | 是 |
| 实现复杂度 | 低 | 中 | 极低 | 极低 | 低 | 中 |
| 状态复用 | 是 | 是 | 否 | 否 | 是 | 是 |
| 适用引擎 | 全部 | 全部 | Lua | JS | JS/Lua | JS/Lua |
引擎最终推荐
| 引擎 | 推荐方案 | 理由 |
|---|---|---|
| Lua | D (per-request) ✅ 已实施 | 0.1ms 创建成本可忽略,零竞争,实现最简 |
| JS | D (per-request) ✅ 已实施 | 1ms 创建成本可接受,零竞争,实现最简 |
| WASM | B+C (Semaphore + Mutex) ✅ 已实施 | 55ms 创建成本只能用池化,Semaphore 异步等待 + AtomicBool 定位空闲实例 |
实施状态
已完成
-
Lua 引擎方案 D:
src/plugins/engine_lua.rs- 删除
LuaInstancePool(固定池 + round-robin + per-instance Mutex) - 改为
DashMap<String, LuaPluginEntry>存储源码/权限/元数据 - 每次调用
call_filter/call_action/call_string_filter创建全新 Lua VM,用完销毁 load_plugin时验证代码可编译(创建 VM → 加载 → 销毁),存入 DashMap- 零竞争、无限并发、完美隔离
- 删除
-
JS 引擎方案 D:
src/plugins/engine_js.rs- 删除
JsInstancePool(固定池 + round-robin + per-instance Mutex) - 删除
PluginSlot结构体 - 改为
DashMap<String, JsPluginEntry>存储源码/权限/元数据 - 每次调用创建全新
AsyncRuntime + AsyncContext,用完 Drop 销毁 load_plugin时验证代码可编译(创建 context → 加载模块 → 销毁),存入 DashMap- 零竞争、无限并发、完美隔离
- 删除
-
WASM 引擎方案 B+C:
src/plugins/engine.rs- 删除 round-robin(
AtomicUsize next) - 新增
Semaphore控制总并发(异步等待,不阻塞 tokio worker) - 新增
AtomicBool busy标记每个实例的忙/闲状态 acquire()先获取 Semaphore permit,再 CAS 扫描找空闲实例WasmInstanceGuardRAII 守卫:Drop 自动释放 busy 标记和 permit- per-instance
Mutex保留(WasmComponentInstance需要&mut self)
- 删除 round-robin(
架构变化
| 维度 | Lua/JS 旧方案 | Lua/JS 新方案 | WASM 旧方案 | WASM 新方案 |
|---|---|---|---|---|
| 数据结构 | Mutex<HashMap<String, Arc<Pool>>> |
DashMap<String, PluginEntry> |
Vec<Mutex<Inst>> + AtomicUsize |
Vec<PooledInstance> + Semaphore |
| 并发控制 | round-robin + Mutex | 无需控制 | round-robin + Mutex | Semaphore + AtomicBool + Mutex |
| 最大并发 | pool_size (4) | 无限 | pool_size (4) | pool_size (4) |
| 等待方式 | Mutex.lock(同步) | 无等待 | Mutex.lock(同步) | Semaphore.acquire(异步) |
| 创建成本/请求 | 0 | Lua ~0.1ms / JS ~1ms | 0 | 0 |
| 竞争范围 | 同池实例 | 零 | round-robin 热点 | Semaphore 全局 + 首个空闲 |