stress: 硬约束退化段迟滞 —— 首次跌破 98% 砍一次,段内连砍需 10min 短窗新证据
dp3 实测教训:开局 3 个验证码(3/103=97.1%)触发硬规则,3 个失败在 30min 硬窗滞留, 每分钟重复 ×0.7 连砍(42→10→7→5)——与 dp1 软信号同一「窗口滞后连砍」病灶。 v3:硬规则进退化段(degraded)语义 —— 首次跌破 98%(失败≥2)砍一次;<95% ×0.5; 段内再砍需 10min 短窗成功率也 <98%(新证据);30min 窗恢复 ≥98% 退段; 爬坡要求不在退化段。/goto 真实负载已验证回归:样本页 7-9 链接缓存命中 0-2, 并发解析 4-5 成功 + 2-5 失败(此前近似全命中被洗掉)。
This commit is contained in:
@@ -5,14 +5,16 @@
|
|||||||
* 双路径原形态(runtime=auto,无 sticky,全池 LRU 选号):
|
* 双路径原形态(runtime=auto,无 sticky,全池 LRU 选号):
|
||||||
* GET /api/serp/json/search?q=...&num=10
|
* GET /api/serp/json/search?q=...&num=10
|
||||||
* 令牌内走 go-fetch(全局限流 fetchPerSec),限流外并发走温池浏览器 —— 组合即生产形态。
|
* 令牌内走 go-fetch(全局限流 fetchPerSec),限流外并发走温池浏览器 —— 组合即生产形态。
|
||||||
* AIMD 控制器(每 60s 决策;硬约束看 30min 窗,软信号看 10min 短窗):
|
* AIMD 控制器(每 60s 决策;硬约束看 30min 窗,重复砍需 10min 短窗新证据,退段需 30min 窗恢复):
|
||||||
* 硬约束即时退避:成功率<95%(失败≥3)×0.5;<98%(失败≥2)×0.7;
|
* 硬约束(成功率即约束本身):首次跌破 98%(失败≥2)×0.7、<95% ×0.5;退化段内
|
||||||
|
* 连砍需短窗(10min)成功率也 <98% —— dp3 实测教训:开局 3 个失败在 30min 硬窗
|
||||||
|
* 滞留导致每分钟连砍到下限(窗口滞后放大),迟滞后一段失败只砍一次;
|
||||||
* 软信号按事件段(episode)迟滞,每段波只砍一次(波退去才重置):
|
* 软信号按事件段(episode)迟滞,每段波只砍一次(波退去才重置):
|
||||||
* 验证码占比>2% ×0.5、>0.5% ×0.7(10min 短窗;dp1 实测 0.5% 噪声时成功率
|
* 验证码占比>2% ×0.5、>0.5% ×0.7(10min 短窗;dp1 实测 0.5% 噪声时成功率
|
||||||
* 仍有 99.3%,按占比计而非按次数计,且不逐分钟连砍钉死下限);
|
* 仍有 99.3%,按占比计而非按次数计,且不逐分钟连砍钉死下限);
|
||||||
* 门壳尝试占比>15% ×0.85;池空>5 ×0.85;
|
* 门壳尝试占比>15% ×0.85;池空>5 ×0.85;
|
||||||
* 干净爬坡:短窗成功率≥99.5% 且门壳<2% 且无池空且验证码≤0.3%(高压力下
|
* 干净爬坡:不在退化段 且 短窗成功率≥99.5% 且门壳<2% 且无池空且验证码≤0.3%
|
||||||
* 单 IP 逐个越限的常态噪声不阻止追涨)→ 每 5 拍 ×1.08。
|
* (高压力下单 IP 逐个越限的常态噪声不阻止追涨)→ 每 5 拍 ×1.08。
|
||||||
* 速率区间 [3, 400] req/min;并发 worker 默认 12(真实并发让溢出自然流向浏览器路径)。
|
* 速率区间 [3, 400] req/min;并发 worker 默认 12(真实并发让溢出自然流向浏览器路径)。
|
||||||
* 判定(客户端,JSON 出口):ok = 200 且 organic>0;gate = 200 且 organic=0 且
|
* 判定(客户端,JSON 出口):ok = 200 且 organic>0;gate = 200 且 organic=0 且
|
||||||
* attempts 含 gate;empty = 200 且 organic=0 无门壳尝试;错误按响应 code 分类
|
* attempts 含 gate;empty = 200 且 organic=0 无门壳尝试;错误按响应 code 分类
|
||||||
@@ -136,6 +138,8 @@ function sumWindow(n = WINDOW_MIN): Counter {
|
|||||||
let captchaWave = false;
|
let captchaWave = false;
|
||||||
let gateWave = false;
|
let gateWave = false;
|
||||||
let poolEmptyWave = false;
|
let poolEmptyWave = false;
|
||||||
|
/** # 成员变量 硬约束退化段(进段砍一次;段内连砍需短窗新证据;30min 窗恢复≥98% 退段) **/
|
||||||
|
let degraded = false;
|
||||||
|
|
||||||
/** # 函数
|
/** # 函数
|
||||||
* 控制器决策(每 60s 调用):按窗口成功率/内部压力调整速率,返回决策记录或 null。
|
* 控制器决策(每 60s 调用):按窗口成功率/内部压力调整速率,返回决策记录或 null。
|
||||||
@@ -152,11 +156,18 @@ function controlTick(): Record<string, unknown> | null {
|
|||||||
const from = rate;
|
const from = rate;
|
||||||
let to = from;
|
let to = from;
|
||||||
let reason = "hold";
|
let reason = "hold";
|
||||||
// 硬约束即时退避,最少失败数门槛:低速率下单次偶发失败不该触发(3 发 1 失败 = 67%)
|
// 硬约束:首次跌破即砍;段内连砍需短窗新证据(防 30min 窗内旧失败连砍钉死下限,dp3 教训)
|
||||||
const fails = tot - wH.ok;
|
const fails = tot - wH.ok;
|
||||||
if (success < 0.95 && fails >= 3) { to = from * 0.5; reason = `success=${(success * 100).toFixed(1)}%<95%(失败 ${fails}) ×0.5`; }
|
const successS = wS.sent > 0 ? wS.ok / wS.sent : 1;
|
||||||
else if (success < 0.98 && fails >= 2) { to = from * 0.7; reason = `success=${(success * 100).toFixed(1)}%<98%(失败 ${fails}) ×0.7`; }
|
const failsS = wS.sent - wS.ok;
|
||||||
else {
|
if (success >= 0.98) degraded = false;
|
||||||
|
const entry = !degraded && success < 0.98 && fails >= 2;
|
||||||
|
const ongoing = degraded && wS.sent >= 10 && successS < 0.98 && failsS >= 2;
|
||||||
|
if (entry || ongoing) {
|
||||||
|
degraded = true;
|
||||||
|
to = success < 0.95 ? from * 0.5 : from * 0.7;
|
||||||
|
reason = `success=${(success * 100).toFixed(1)}%<${success < 0.95 ? 95 : 98}%(失败 ${fails}${entry ? "" : ";短窗新证据"}) ×${success < 0.95 ? 0.5 : 0.7}`;
|
||||||
|
} else {
|
||||||
// 软信号:事件段(episode)迟滞 —— 进段砍一次,退段重置;段内不重复砍
|
// 软信号:事件段(episode)迟滞 —— 进段砍一次,退段重置;段内不重复砍
|
||||||
if (capPctS > 0.005 && !captchaWave) {
|
if (capPctS > 0.005 && !captchaWave) {
|
||||||
captchaWave = true;
|
captchaWave = true;
|
||||||
@@ -174,9 +185,8 @@ function controlTick(): Record<string, unknown> | null {
|
|||||||
reason = `池空 ${wS.poolEmpty} ×0.85`;
|
reason = `池空 ${wS.poolEmpty} ×0.85`;
|
||||||
} else if (wS.poolEmpty === 0) poolEmptyWave = false;
|
} else if (wS.poolEmpty === 0) poolEmptyWave = false;
|
||||||
}
|
}
|
||||||
// 干净爬坡:每 RAMP_EVERY_MIN 个「干净节拍」至多一次(验证码≤0.3% 属高压力常态噪声,不阻追涨)
|
// 干净爬坡:每 RAMP_EVERY_MIN 个「干净节拍」至多一次;退化段中不爬(30min 窗未恢复 98%)
|
||||||
if (to === from && wS.sent >= 10) {
|
if (to === from && !degraded && wS.sent >= 10) {
|
||||||
const successS = wS.ok / wS.sent;
|
|
||||||
if (successS >= 0.995 && gateRatioS < 0.02 && wS.poolEmpty === 0 && capPctS < 0.003) {
|
if (successS >= 0.995 && gateRatioS < 0.02 && wS.poolEmpty === 0 && capPctS < 0.003) {
|
||||||
rampSeq++;
|
rampSeq++;
|
||||||
if (rampSeq % RAMP_EVERY_MIN === 0) { to = from * 1.08 + 0.2; reason = `clean(success=${(successS * 100).toFixed(1)}%,gate=${(gateRatioS * 100).toFixed(1)}%) ×1.08`; }
|
if (rampSeq % RAMP_EVERY_MIN === 0) { to = from * 1.08 + 0.2; reason = `clean(success=${(successS * 100).toFixed(1)}%,gate=${(gateRatioS * 100).toFixed(1)}%) ×1.08`; }
|
||||||
|
|||||||
Reference in New Issue
Block a user