fix(紫鸟): 店铺索引刷新加轮级时间预算熔断——单轮超 8 分钟即中断,剩余 key 顺延下一轮(游标续跑),避免紫鸟接口半死时整轮占锁几十分钟阻塞后续定时轮

This commit is contained in:
2026-09-11 17:02:13 +08:00
parent 2a9110ec32
commit 540d6588e6
3 changed files with 25 additions and 0 deletions
@@ -61,4 +61,10 @@ public class ZiniaoProperties {
* null/空/<=0 表示不限制(不推荐)。
*/
private Integer shopIndexRefreshBatchSize;
/**
* 单轮索引刷新的时间预算(秒);超时即中断本轮,剩余 key 顺延下一轮。
* null/<=0 使用内置默认(8 分钟)。
*/
private Integer shopIndexRoundTimeoutSeconds;
}
@@ -57,6 +57,7 @@ public class ZiniaoShopIndexService {
private static final Duration DEFAULT_ENTRY_TTL = Duration.ofHours(12);
private static final Duration DEFAULT_CURSOR_TTL = Duration.ofHours(24);
private static final int DEFAULT_REFRESH_BATCH_SIZE = 200;
private static final long DEFAULT_ROUND_TIMEOUT_MILLIS = Duration.ofMinutes(8).toMillis();
private static final int SHOP_INDEX_LIST_LIMIT = 10000;
private static final String REFRESH_BLOCKED_REASON_IP_WHITELIST = "IP_WHITELIST";
@@ -206,6 +207,14 @@ public class ZiniaoShopIndexService {
int nextOffset = computeNextOffset(allApiKeyAccounts.size(), previousOffset, apiKeyAccounts.size(), refreshBatchSize);
apiKeyLoop:
for (ZiniaoApiKeyProvider.ApiKeyAccount apiKeyAccount : apiKeyAccounts) {
// 轮级熔断:单轮耗时超上限即跳出,剩余 key 顺延下一轮(游标续跑),避免紫鸟接口半死时整轮占锁几十分钟
long roundElapsed = System.currentTimeMillis() - now;
if (refreshRoundTimeoutMillis() > 0 && roundElapsed > refreshRoundTimeoutMillis() && completedApiKeyCount > 0) {
completeCoverage = false;
log.warn("[ziniao-index] refresh round budget exceeded, break early elapsedMs={} budgetMs={} completedApiKeys={} remainingKeys={}",
roundElapsed, refreshRoundTimeoutMillis(), completedApiKeyCount, apiKeyAccounts.size() - completedApiKeyCount - skippedApiKeyCount);
break;
}
String apiKey = apiKeyAccount.apiKey();
String companyName = apiKeyAccount.accountName();
Map<String, List<ZiniaoShopIndexEntryDto>> apiKeyGrouped = new LinkedHashMap<>();
@@ -798,6 +807,15 @@ public class ZiniaoShopIndexService {
return batchSize;
}
/** 轮级刷新预算(毫秒),<=0 表示不限制。 */
private long refreshRoundTimeoutMillis() {
Integer seconds = ziniaoProperties.getShopIndexRoundTimeoutSeconds();
if (seconds == null || seconds <= 0) {
return DEFAULT_ROUND_TIMEOUT_MILLIS;
}
return Duration.ofSeconds(seconds).toMillis();
}
private List<ZiniaoApiKeyProvider.ApiKeyAccount> selectApiKeyBatchByOffset(
List<ZiniaoApiKeyProvider.ApiKeyAccount> allApiKeyAccounts,
int offset,
@@ -347,3 +347,4 @@ aiimage:
shop-index-fresh-minutes: ${AIIMAGE_ZINIAO_SHOP_INDEX_FRESH_MINUTES:20}
shop-index-entry-ttl-hours: ${AIIMAGE_ZINIAO_SHOP_INDEX_ENTRY_TTL_HOURS:12}
shop-index-refresh-batch-size: ${AIIMAGE_ZINIAO_SHOP_INDEX_REFRESH_BATCH_SIZE:200}
shop-index-round-timeout-seconds: ${AIIMAGE_ZINIAO_SHOP_INDEX_ROUND_TIMEOUT_SECONDS:480}