v10: SSL断路器指数退避 + 超时统一10秒 + 延迟增到10-12秒

- SSL断路器冷却从固定30秒改为指数退避: 30s→60s→120s→240s→300s(上限)
- 新增sslCircuitOpenCount跟踪连续断路次数,成功时重置
- 请求超时统一10秒(connectTimeout和request timeout一致)
- 任务间随机延迟从8-10秒增到10-12秒,进一步减少锁库
- HTTP降级成功也重置退避计数
This commit is contained in:
2026-06-25 13:27:05 +08:00
parent 10e148be5f
commit eb0b39d8b9
+61 -45
View File
@@ -115,12 +115,14 @@ public class WebManager {
// ★ SSL断路器:连续失败超过阈值后暂停轮询,避免DB队列积压
private static final int SSL_CIRCUIT_THRESHOLD = 5; // 连续失败5次触发断路
private static final long SSL_CIRCUIT_COOLDOWN_MS = 60000; // 断路冷却60秒
private static final long SSL_CIRCUIT_BASE_COOLDOWN_MS = 30000; // 断路基础冷却30秒
private static final long SSL_CIRCUIT_MAX_COOLDOWN_MS = 300000; // 断路最大冷却5分钟
// ★ SSL降级:连续失败3次后降级到HTTP
private static final int SSL_DOWNGRADE_THRESHOLD = 3; // 连续失败3次触发降级
private volatile boolean sslDowngraded = false; // SSL降级标志
private volatile int sslConsecutiveFailures = 0;
private volatile long sslCircuitOpenUntil = 0;
private volatile int sslCircuitOpenCount = 0; // 连续断路次数(用于指数退避)
public WebManager(Main plugin) {
this.plugin = plugin;
@@ -278,7 +280,7 @@ public class WebManager {
sslCircuitOpenUntil = 0;
sslConsecutiveFailures = 0;
sslDowngraded = false; // 恢复降级标志
plugin.getLogger().info("[Web通信] SSL断路器冷却结束,恢复轮询");
plugin.getLogger().info("[Web通信] SSL断路器冷却结束(第" + (sslCircuitOpenCount + 1) + "轮),恢复轮询");
// ★ 重建HttpClient,清除可能损坏的连接状态
rebuildHttpClient();
return false;
@@ -320,6 +322,7 @@ public class WebManager {
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
// HTTP成功 → 重置SSL计数,恢复正常
sslConsecutiveFailures = 0;
sslCircuitOpenCount = 0; // 重置退避计数
sslDowngraded = false;
plugin.getLogger().info("[Web通信] HTTP降级成功,恢复正常HTTPS");
return resp.body();
@@ -383,7 +386,7 @@ public class WebManager {
cfHttpClient = HttpClient.newBuilder()
.sslContext(sc)
.sslParameters(sslParams)
.connectTimeout(Duration.ofSeconds(15))
.connectTimeout(Duration.ofSeconds(10))
.version(HttpClient.Version.HTTP_1_1)
.build();
@@ -412,7 +415,7 @@ public class WebManager {
cfHttpClient = HttpClient.newBuilder()
.sslContext(sc)
.sslParameters(sslParams)
.connectTimeout(Duration.ofSeconds(15))
.connectTimeout(Duration.ofSeconds(10))
.version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
@@ -456,7 +459,7 @@ public class WebManager {
try {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(urlStr))
.timeout(Duration.ofSeconds(15))
.timeout(Duration.ofSeconds(10))
.header("User-Agent", "Sdf1-WebManager/2.8")
.header("Accept", "application/json")
.GET()
@@ -464,6 +467,7 @@ public class WebManager {
HttpResponse<String> resp = cfHttpClient.send(req, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
sslConsecutiveFailures = 0; // 成功 → 重置断路器
sslCircuitOpenCount = 0; // 重置退避计数
sslDowngraded = false; // 恢复正常HTTPS
return resp.body();
}
@@ -482,8 +486,13 @@ public class WebManager {
return doGetHttpFallback(httpUrl);
}
if (sslConsecutiveFailures >= SSL_CIRCUIT_THRESHOLD) {
sslCircuitOpenUntil = System.currentTimeMillis() + SSL_CIRCUIT_COOLDOWN_MS;
plugin.getLogger().warning("[Web通信] SSL断路器触发: 连续失败" + sslConsecutiveFailures + "次,暂停轮询60秒");
// ★ 指数退避:30s → 60s → 120s → 240s → 300s(上限)
long cooldown = Math.min(
SSL_CIRCUIT_BASE_COOLDOWN_MS * (1L << sslCircuitOpenCount),
SSL_CIRCUIT_MAX_COOLDOWN_MS);
sslCircuitOpenUntil = System.currentTimeMillis() + cooldown;
sslCircuitOpenCount++;
plugin.getLogger().warning("[Web通信] SSL断路器触发: 连续失败" + sslConsecutiveFailures + "次,暂停轮询" + (cooldown/1000) + "秒(第" + sslCircuitOpenCount + "轮退避)");
}
plugin.getLogger().warning("[Web通信] GET异常: " + e.getClass().getSimpleName() + ": " + e.getMessage());
return null;
@@ -537,7 +546,7 @@ public class WebManager {
try {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(urlStr))
.timeout(Duration.ofSeconds(15))
.timeout(Duration.ofSeconds(10))
.header("User-Agent", "Sdf1-WebManager/2.8")
.header("Content-Type", "application/json; charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
@@ -575,7 +584,7 @@ public class WebManager {
try {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(urlStr))
.timeout(Duration.ofSeconds(15))
.timeout(Duration.ofSeconds(10))
.header("User-Agent", "Sdf1-WebManager/2.8")
.header("Content-Type", "application/json; charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
@@ -583,6 +592,7 @@ public class WebManager {
HttpResponse<String> resp = cfHttpClient.send(req, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
sslConsecutiveFailures = 0;
sslCircuitOpenCount = 0; // 重置退避计数
sslDowngraded = false; // 恢复正常HTTPS
return resp.body();
}
@@ -601,8 +611,13 @@ public class WebManager {
return doPostHttpFallback(httpUrl, jsonBody);
}
if (sslConsecutiveFailures >= SSL_CIRCUIT_THRESHOLD) {
sslCircuitOpenUntil = System.currentTimeMillis() + SSL_CIRCUIT_COOLDOWN_MS;
plugin.getLogger().warning("[Web通信] SSL断路器触发: 连续失败" + sslConsecutiveFailures + "次,暂停轮询60秒");
// ★ 指数退避:30s → 60s → 120s → 240s → 300s(上限)
long cooldown = Math.min(
SSL_CIRCUIT_BASE_COOLDOWN_MS * (1L << sslCircuitOpenCount),
SSL_CIRCUIT_MAX_COOLDOWN_MS);
sslCircuitOpenUntil = System.currentTimeMillis() + cooldown;
sslCircuitOpenCount++;
plugin.getLogger().warning("[Web通信] SSL断路器触发: 连续失败" + sslConsecutiveFailures + "次,暂停轮询" + (cooldown/1000) + "秒(第" + sslCircuitOpenCount + "轮退避)");
}
plugin.getLogger().warning("[Web通信] POST异常: " + e.getClass().getSimpleName() + ": " + e.getMessage());
return null;
@@ -628,8 +643,9 @@ public class WebManager {
HttpResponse<String> resp = plainHttpClient.send(req, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
sslConsecutiveFailures = 0;
sslCircuitOpenCount = 0; // 重置退避计数
sslDowngraded = false;
plugin.getLogger().info("[Web通信] HTTP降级成功,恢复正常HTTPS");
plugin.getLogger().info("[Web通信] HTTP降级POST成功,恢复正常HTTPS");
return resp.body();
}
plugin.getLogger().warning("[Web通信] HTTP降级失败 POST HTTP " + resp.statusCode() + ": " + urlStr);
@@ -652,7 +668,7 @@ public class WebManager {
try {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(urlStr))
.timeout(Duration.ofSeconds(15))
.timeout(Duration.ofSeconds(10))
.header("User-Agent", "Sdf1-WebManager/2.8")
.header("Content-Type", "application/json; charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
@@ -801,24 +817,24 @@ public class WebManager {
plugin.getLogger().info("[Web通信] 开始首次全量同步(通过DB队列串行化)...");
// 登录相关操作高优先级
submitDbTask("首次-syncUserRegistrations", () -> syncUserRegistrations());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitDbTask("首次-pushWebLoginCredentials", () -> pushWebLoginCredentials());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
// 普通同步操作低优先级(每个任务间随机间隔2-4秒,避免PHP端DB锁)
submitNormalDbTask("首次-syncOnlinePlayers", () -> syncOnlinePlayers());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("首次-syncShopData", () -> syncShopData());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("首次-syncBondBalances", () -> syncBondBalances());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("首次-syncBondTransactions", () -> syncBondTransactions());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("首次-syncAllPlayerIps", () -> syncAllPlayerIps());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("首次-syncServiceProviders", () -> syncServiceProviders());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("首次-syncLandData", () -> syncLandData());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("首次-pollAdminChanges", () -> pollAdminChanges());
}
}.runTaskLaterAsynchronously(plugin, 20L * 10);
@@ -1189,19 +1205,19 @@ public class WebManager {
plugin.getLogger().info("[Web通信] 收到即时同步请求: " + players.toString());
// 通过DB队列串行化执行(每个任务间错开800ms)
submitDbTask("即时-syncOnlinePlayers", () -> syncOnlinePlayers());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitDbTask("即时-pushWebLoginCredentials", () -> pushWebLoginCredentials());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitDbTask("即时-syncUserRegistrations", () -> syncUserRegistrations());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-pullPendingTransactions", () -> pullPendingTransactions());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-pullShopStock", () -> pullShopStock());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-pullBondChanges", () -> pullBondChanges());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-syncAllPlayerIps", () -> syncAllPlayerIps());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-syncServiceProviders", () -> syncServiceProviders());
}
}
@@ -1224,17 +1240,17 @@ public class WebManager {
lastOnlineCheckTime = System.currentTimeMillis();
// 通过DB队列串行化执行最后一轮同步(每个任务间随机间隔2-4秒,避免与定时轮询冲突)
submitDbTask("末轮-syncOnlinePlayers", () -> syncOnlinePlayers());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitDbTask("末轮-pushWebLoginCredentials", () -> pushWebLoginCredentials());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitDbTask("末轮-syncUserRegistrations", () -> syncUserRegistrations());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitDbTask("末轮-syncBondTransactions", () -> syncBondTransactions());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("末轮-pullPendingTransactions", () -> pullPendingTransactions());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("末轮-pullShopStock", () -> pullShopStock());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("末轮-pullBondChanges", () -> pullBondChanges());
plugin.getLogger().info("[Web通信] 检测到全员下线超60秒,执行最后一轮同步(通过DB队列),调度器继续运行(玩家上线自动恢复)");
plugin.getLogger().warning("\n" +
@@ -1285,28 +1301,28 @@ public class WebManager {
// 登录/注册相关(始终执行,随机间隔2-4秒避免PHP端DB锁)
submitDbTask("周期-pushWebLoginCredentials", () -> pushWebLoginCredentials());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitDbTask("周期-syncUserRegistrations", () -> syncUserRegistrations());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
if (online) {
// 在线:执行全量同步(每个任务随机间隔2-4秒)
submitDbTask("周期-syncOnlinePlayers", () -> syncOnlinePlayers());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitDbTask("周期-syncBondTransactions", () -> syncBondTransactions());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-pullPendingTransactions", () -> pullPendingTransactions());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-pullShopStock", () -> pullShopStock());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-pullBondChanges", () -> pullBondChanges());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-syncAllPlayerIps", () -> syncAllPlayerIps());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-syncServiceProviders", () -> syncServiceProviders());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-syncLandData", () -> syncLandData());
try { Thread.sleep(8000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
try { Thread.sleep(10000 + (long)(Math.random() * 2000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-pollAdminChanges", () -> pollAdminChanges());
} else {
// ★ 不在线:跳过非必要同步任务