diff --git a/stress/serp-dualpath-optimize.ts b/stress/serp-dualpath-optimize.ts index c54c523..0f17073 100644 --- a/stress/serp-dualpath-optimize.ts +++ b/stress/serp-dualpath-optimize.ts @@ -6,17 +6,16 @@ * 双路径原形态(runtime=auto,无 sticky,全池公平负载选号): * GET /api/serp/json/search?q=...&num=10 * 令牌内走 go-fetch(全局限流 fetchPerSec),限流外并发走温池浏览器 —— 组合即生产形态。 - * AIMD 控制器 v5.3(dp6 全天 + dp7/dp7b/dp7c 前段实测教训驱动;v4 基础上的关键演进): - * 故障二分(v5.3 核心):我方故障 = 池空/httpErr/netErr → 硬规则(入段/续砍/地板 8); - * Google 侧 = captcha/门壳/空结果 → 只观察,仅灾难波次软砍(captcha ≥24 个/10min - * ≈ dp6 最差小时水平;门壳 ≥20 个/10min),砍速地板 40; - * 硬约束:入段 = 浅破线(<99% 且失败 ≥8 且窗口 ≥200 样本)或深破线(<96% 且失败 ≥4) - * ×0.7/<96% ×0.5;续砍需短窗 <96% 且(我方故障 ≥3 或 captcha ≥5/10min)且全程 - * 增量新失败 ≥2; - * 噪声退段/爬坡(稀释论,dp7 三轮实测:低速小分母下高窗口线被 Google 侧噪声压住是 - * 数学必然,砍速是毒药、爬坡摊薄是解药):零我方故障且联合噪声(captcha+门壳)≤6/8 - * → 可退段(短窗 ≥96%)/可爬坡(短窗 ≥96%),×1.3/拍 向水位回收,水位之上 ×1.08/5 拍; - * 软砍下限 SOFT_FLOOR=40(99% 目标下 25/min 涓流占比已贴线),硬砍下限 HARD_FLOOR=8。 + * AIMD 控制器 v5.4(dp6 全天 + dp7/dp7b/dp7c/dp7d 四轮前段实测定稿;核心=故障二分): + * Google 侧(captcha/门壳/空)→ 零砍速,波次骑行:四轮实测一致证明砍速在任何速率下 + * 都让占比恶化(8/min 时 148/时波次占比 10.5% 窗口 88%;117/min 4.2%;300/min 1.6% + * 窗口 98.4%)—— 唯一出路是分母,宁骑不砍; + * 我方故障(httpErr/netErr;poolEmpty 仅低速 <60/min 计入,高速期池空是需求伴生现象) + * → AIMD:入段(浅破线 <99% 失败 ≥8 且窗口 ≥200 样本 / 深破线 <96% 失败 ≥4)×0.7/ + * ×0.5,续砍需短窗 <96% 且增量新失败 ≥2,地板 HARD_FLOOR=8,低速池空软砍 ×0.85; + * 噪声预算退段/爬坡(稀释论):零我方故障且联合噪声(captcha+门壳)≤6 → 退段(短窗 + * ≥96%)/≤8 → 可爬坡(短窗 ≥96%),水位下 ×1.3/拍,之上 ×1.08/5 拍; + * workers 默认 24(页内 fetch 1.1~2s/请求,12 worker 天花板 ~135/min 是波次稀释瓶颈)。 * 干净爬坡:不在退化段 且 短窗干净 且 无池空 且(验证码 0 个或样本 ≥300)→ 向「干净 * 水位」(30min 窗 ≥99.5% 时的最高速率)以 ×1.3/拍快速回收,水位之上每 5 拍 ×1.08。 * v5 修 dp6 实测「低速恢复锁死」:短窗成功率 ≥99.5% 在低速涓流下永不可达 @@ -45,8 +44,9 @@ const RATE0 = Number(argOf("--rate0", "15")); /** # 成员变量 速率下限/上限(req/min) **/ const RATE_MIN = Number(argOf("--min", "3")); const RATE_MAX = Number(argOf("--max", "400")); -/** # 成员变量 并发 worker 数 **/ -const WORKERS = Number(argOf("--workers", "12")); +/** # 成员变量 并发 worker 数(v5.4:默认 12→24 —— 页内 fetch 后浏览器单请求 1.1~2s, + * 12 worker 的吞吐天花板(~135/min 实测)成了灾难波次稀释(需 ≥250/min)的瓶颈) **/ +const WORKERS = Number(argOf("--workers", "24")); /** # 成员变量 控制器窗口(分钟)与加速步间隔(分钟) **/ const WINDOW_MIN = 30; const RAMP_EVERY_MIN = 5; @@ -175,49 +175,38 @@ function controlTick(): Record | null { const from = rate; let to = from; let reason = "hold"; - // 故障二分(v5.3,dp7c 1.8h 实测:波次退潮后门壳 2~3 个/窗 + captcha 3~5 个联合噪声 - // 把窗口钉在 96.6~97.9%,nonCapFails===0 退段与 successS≥0.98 爬坡双双卡死在 8/min; - // 门壳/空结果与 captcha 同为 Google 侧供给,稀释论同样适用 —— 我方故障只剩 - // 池空/httpErr/netErr): - // infraFails = 我方故障;googleNoise = captcha+门壳(联合噪声预算) - // ① 退段:窗口 ≥99%,或零我方故障且联合噪声 ≤6 且短窗 ≥96%(低速小分母下窗口 - // 成功率被噪声压住是数学必然,退段只看「波次是否结束」不看窗口线); - // ② 入段:浅破线(<99% 且失败 ≥8 且窗口 ≥200 样本,v5.2 防小样本级联)或深破线 - // (<96% 且失败 ≥4);Google 侧噪声不触发入段(灾难波次由软规则管); - // ③ 续砍:我方故障 ≥3 或 captcha ≥5/10min;砍速地板按故障归属:我方 8 / Google 侧 40 + // 故障二分(v5.4,dp7d 实测定稿:148/时灾难波次下,poolEmpty 3~4 个(高需求伴生)误判 + // 我方故障解锁硬地板 8 → 砸到 8/min 窗口 88%(captcha 占比 10.5%);而同一波次在 + // 117/min 为 95.8%、300/min 为 98.4% —— Google 侧事件引发砍速在任何速率下都是毒药): + // infraFails = 纯我方故障(httpErr/netErr;poolEmpty 仅低速 <60/min 时计入 —— 高速期 + // 池空是需求压力的伴生现象,温池自补,砍速只会加重浏览器饿死); + // Google 侧(captcha/门壳/空)→ 零砍速,波次骑行:当前速率下占比已是最优解, + // 砍速减分母提占比、提速摊薄降占比,唯一出路在分母; + // ① 退段:窗口 ≥99%,或零我方故障且联合噪声 ≤6 且短窗 ≥96%; + // ② 入段/续砍:仅我方故障 ≥3 且(浅破线 <99% 失败 ≥8 窗口 ≥200 样本 / 深破线 <96% + // 失败 ≥4 / 短窗 <96% 增量新失败 ≥2),地板 8; + // ③ 爬坡:零我方故障且联合噪声 ≤8 且短窗 ≥96%(水位下 ×1.3/拍,之上 ×1.08/5 拍) const fails = tot - wH.ok; const allFails = totals.sent - totals.ok; const successS = wS.sent > 0 ? wS.ok / wS.sent : 1; - const infraFails = wH.poolEmpty + wH.httpErr + wH.netErr; + const infraFails = wH.httpErr + wH.netErr + (from < 60 ? wH.poolEmpty : 0); const googleNoiseS = wS.captcha + wS.gate; const trickle = infraFails === 0 && googleNoiseS <= 6; if (success >= 0.99 || (trickle && successS >= 0.96)) degraded = false; - const entry = !degraded && !trickle && ((success < 0.99 && fails >= 8 && tot >= 200) || (success < 0.96 && fails >= 4)); - const ongoing = degraded && wS.sent >= 30 && successS < 0.96 && (infraFails >= 3 || wS.captcha >= 5) && allFails - failsAtLastCut >= 2; + const entry = !degraded && !trickle && infraFails >= 3 && ((success < 0.99 && fails >= 8 && tot >= 200) || (success < 0.96 && fails >= 4)); + const ongoing = degraded && wS.sent >= 30 && successS < 0.96 && infraFails >= 3 && allFails - failsAtLastCut >= 2; if (entry || ongoing) { degraded = true; failsAtLastCut = allFails; to = success < 0.96 ? from * 0.5 : from * 0.7; - to = Math.max(infraFails >= 3 ? HARD_FLOOR : SOFT_FLOOR, to); + to = Math.max(HARD_FLOOR, to); reason = `success=${(success * 100).toFixed(1)}%<${success < 0.96 ? 96 : 99}%(失败 ${fails}${entry ? "" : ";短窗<96%+新失败≥2"}) ×${success < 0.96 ? 0.5 : 0.7}`; } else { - // 软信号(v5.3:只砍灾难波次 —— captcha ≥24 个/10min ≈ 144/时 = dp6 最差小时水平, - // 常规波次 45~90/时不再触发;门壳 ≥20 个/10min ≈ 3× dp6 峰值;涓流/常规波次交给 - // 稀释与爬坡;低速率小分母下的高占比是数学现象,不是信号;dp5/dp7 三轮实测) - if (wS.captcha >= 24 && capPctS > 0.005 && !captchaWave) { - captchaWave = true; - const heavy = wS.captcha >= 40 && capPctS > 0.02; - to = Math.max(SOFT_FLOOR, from * (heavy ? 0.5 : 0.7)); - reason = `验证码 ${wS.captcha} 个占比 ${(capPctS * 100).toFixed(2)}% ${heavy ? "×0.5" : "×0.7"}`; - } else if (wS.captcha === 0) captchaWave = false; - if (to === from && wS.gate >= 20 && gateRatioS > 0.15 && !gateWave) { - gateWave = true; - to = Math.max(SOFT_FLOOR, from * 0.85); - reason = `门壳 ${wS.gate} 占比 ${(gateRatioS * 100).toFixed(1)}%>15% ×0.85`; - } else if (gateRatioS < 0.05) gateWave = false; - if (to === from && wS.poolEmpty > 5 && !poolEmptyWave) { + // 软信号(v5.4:仅剩池空,且仅低速期 —— 高速期池空是需求伴生现象,温池自补, + // 砍速只会加重浏览器饿死;captcha/门壳软砍废除,灾难波次骑行,dp7 四轮实测) + if (from < 100 && wS.poolEmpty > 5 && !poolEmptyWave) { poolEmptyWave = true; - to = Math.max(HARD_FLOOR, from * 0.85); + to = Math.max(SOFT_FLOOR, from * 0.85); reason = `池空 ${wS.poolEmpty} ×0.85`; } else if (wS.poolEmpty === 0) poolEmptyWave = false; }