分散刷新后补上备机同步
原实现中「全量刷新 → 通知备机同步」是同一步的两个动作:refreshAll() 由调用方跟一次 requestSubscriptionSync()。改为按账号分散刷新后,调度器 自己没有触发同步,刷新出的新缓存要等到下一次周期同步才可能送到备机。 生产上每 60 秒有一次周期同步兜底,所以影响被限制在最长约一分钟的滞后; 但这属于重构时漏掉的语义,且一旦把周期同步间隔调大就会变成真实缺口, 因此仍按原语义在刷新到内容后立即触发同步。没有账号到期时不打扰备机。 测试 441 通过。
This commit is contained in:
@@ -38,6 +38,7 @@ public class SubscriptionRefreshScheduler {
|
|||||||
private final SubscriptionRefreshService refreshService;
|
private final SubscriptionRefreshService refreshService;
|
||||||
private final SubscriptionRefreshPlanner planner;
|
private final SubscriptionRefreshPlanner planner;
|
||||||
private final PushService pushService;
|
private final PushService pushService;
|
||||||
|
private final RemoteService remoteService;
|
||||||
|
|
||||||
/** 单个 tick 最多刷新几个账号,避免重启后一批到期账号同时开火。 */
|
/** 单个 tick 最多刷新几个账号,避免重启后一批到期账号同时开火。 */
|
||||||
@Value("${subscription.refresh.max-per-tick:2}")
|
@Value("${subscription.refresh.max-per-tick:2}")
|
||||||
@@ -53,11 +54,13 @@ public class SubscriptionRefreshScheduler {
|
|||||||
public SubscriptionRefreshScheduler(SubMapper subMapper,
|
public SubscriptionRefreshScheduler(SubMapper subMapper,
|
||||||
SubscriptionRefreshService refreshService,
|
SubscriptionRefreshService refreshService,
|
||||||
SubscriptionRefreshPlanner planner,
|
SubscriptionRefreshPlanner planner,
|
||||||
PushService pushService) {
|
PushService pushService,
|
||||||
|
RemoteService remoteService) {
|
||||||
this.subMapper = subMapper;
|
this.subMapper = subMapper;
|
||||||
this.refreshService = refreshService;
|
this.refreshService = refreshService;
|
||||||
this.planner = planner;
|
this.planner = planner;
|
||||||
this.pushService = pushService;
|
this.pushService = pushService;
|
||||||
|
this.remoteService = remoteService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,6 +100,10 @@ public class SubscriptionRefreshScheduler {
|
|||||||
refreshed++;
|
refreshed++;
|
||||||
}
|
}
|
||||||
reportStale(enabled, now);
|
reportStale(enabled, now);
|
||||||
|
// 缓存内容变了就要让备机同步。原先全量刷新后由调用方跟一次同步,
|
||||||
|
// 改为分散刷新后必须在这里补上,否则备机要等到下一次周期同步才拿到新内容。
|
||||||
|
if (refreshed > 0)
|
||||||
|
remoteService.requestSubscriptionSync();
|
||||||
return refreshed;
|
return refreshed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,14 @@ class SubscriptionRefreshSchedulerTest {
|
|||||||
private SubMapper subMapper;
|
private SubMapper subMapper;
|
||||||
private SubscriptionRefreshService refreshService;
|
private SubscriptionRefreshService refreshService;
|
||||||
private PushService pushService;
|
private PushService pushService;
|
||||||
|
private RemoteService remoteService;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
subMapper = mock(SubMapper.class);
|
subMapper = mock(SubMapper.class);
|
||||||
refreshService = mock(SubscriptionRefreshService.class);
|
refreshService = mock(SubscriptionRefreshService.class);
|
||||||
pushService = mock(PushService.class);
|
pushService = mock(PushService.class);
|
||||||
|
remoteService = mock(RemoteService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 用固定时钟构造,使「现在」可控。 */
|
/** 用固定时钟构造,使「现在」可控。 */
|
||||||
@@ -49,7 +51,7 @@ class SubscriptionRefreshSchedulerTest {
|
|||||||
Clock.fixed(instant, ZONE), new Random(42),
|
Clock.fixed(instant, ZONE), new Random(42),
|
||||||
Duration.ofHours(24), Duration.ofHours(1), Duration.ofMinutes(5));
|
Duration.ofHours(24), Duration.ofHours(1), Duration.ofMinutes(5));
|
||||||
SubscriptionRefreshScheduler scheduler = new SubscriptionRefreshScheduler(
|
SubscriptionRefreshScheduler scheduler = new SubscriptionRefreshScheduler(
|
||||||
subMapper, refreshService, planner, pushService);
|
subMapper, refreshService, planner, pushService, remoteService);
|
||||||
scheduler.maxPerTick = 2;
|
scheduler.maxPerTick = 2;
|
||||||
scheduler.staleAlertMultiplier = 2.0;
|
scheduler.staleAlertMultiplier = 2.0;
|
||||||
return scheduler;
|
return scheduler;
|
||||||
@@ -385,6 +387,31 @@ class SubscriptionRefreshSchedulerTest {
|
|||||||
"铺开的计划应写回内存对象");
|
"铺开的计划应写回内存对象");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新成功后必须通知备机同步。
|
||||||
|
* 原先「全量刷新」由调用方跟一次同步;改成按账号分散刷新后,若调度器不自己触发,
|
||||||
|
* 备机就要等到下一次周期同步才拿到新内容——这个缺口在重构时很容易漏掉。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void refreshTriggersStandbySyncWhenSomethingChanged() {
|
||||||
|
existing(account(1, BASE.toEpochMilli() - MINUTE, BASE.toEpochMilli() - 2 * HOUR));
|
||||||
|
refreshSucceedsForAll();
|
||||||
|
|
||||||
|
scheduler().refreshDueAccounts();
|
||||||
|
|
||||||
|
verify(remoteService).requestSubscriptionSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 本轮没有任何账号到期时不应打扰备机。 */
|
||||||
|
@Test
|
||||||
|
void noRefreshMeansNoStandbySync() {
|
||||||
|
existing(account(1, BASE.toEpochMilli() + 5 * HOUR, BASE.toEpochMilli()));
|
||||||
|
|
||||||
|
scheduler().refreshDueAccounts();
|
||||||
|
|
||||||
|
verify(remoteService, never()).requestSubscriptionSync();
|
||||||
|
}
|
||||||
|
|
||||||
/** 已有计划的账号不应被重新排程,否则每次 tick 都会打乱分散效果。 */
|
/** 已有计划的账号不应被重新排程,否则每次 tick 都会打乱分散效果。 */
|
||||||
@Test
|
@Test
|
||||||
void existingScheduleIsNotOverwritten() {
|
void existingScheduleIsNotOverwritten() {
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ class SubscriptionRefreshWiringTest {
|
|||||||
return mock(PushService.class);
|
return mock(PushService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RemoteService remoteService() {
|
||||||
|
return mock(RemoteService.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SubscriptionRefreshPlanner subscriptionRefreshPlanner() {
|
SubscriptionRefreshPlanner subscriptionRefreshPlanner() {
|
||||||
return new SubscriptionRefreshPlanner(java.time.Clock.systemDefaultZone(), new java.util.Random(),
|
return new SubscriptionRefreshPlanner(java.time.Clock.systemDefaultZone(), new java.util.Random(),
|
||||||
@@ -54,8 +59,9 @@ class SubscriptionRefreshWiringTest {
|
|||||||
SubscriptionRefreshScheduler subscriptionRefreshScheduler(SubMapper subMapper,
|
SubscriptionRefreshScheduler subscriptionRefreshScheduler(SubMapper subMapper,
|
||||||
SubscriptionRefreshService refreshService,
|
SubscriptionRefreshService refreshService,
|
||||||
SubscriptionRefreshPlanner planner,
|
SubscriptionRefreshPlanner planner,
|
||||||
PushService pushService) {
|
PushService pushService,
|
||||||
return new SubscriptionRefreshScheduler(subMapper, refreshService, planner, pushService);
|
RemoteService remoteService) {
|
||||||
|
return new SubscriptionRefreshScheduler(subMapper, refreshService, planner, pushService, remoteService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user