stress: 控制器 v5.4 —— Google 侧零砍速(四轮实测:灾难波次下砍速在任何速率都是毒药,宁骑不砍)+ poolEmpty 仅低速计入我方故障 + workers 12→24(页内 fetch 后天花板瓶颈)

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