chore(lastleg): diagnose work fact synchronization phases
This commit is contained in:
+52
-4
@@ -10,6 +10,7 @@ import static com.baiyue.lastleg.appointmentwork.AppointmentWorkPolicy.*;
|
||||
|
||||
@Component
|
||||
public class AppointmentWorkFactAdapter implements AppointmentWorkFactGateway {
|
||||
private static final org.slf4j.Logger LOG=org.slf4j.LoggerFactory.getLogger(AppointmentWorkFactAdapter.class);
|
||||
private final AppointmentWorkFirstlegClient firstleg;
|
||||
private final List<BusinessProvider> providers;
|
||||
private final ObjectProvider<OwnerDirectory> directory;
|
||||
@@ -38,7 +39,10 @@ public class AppointmentWorkFactAdapter implements AppointmentWorkFactGateway {
|
||||
return response.data();
|
||||
} catch(RuntimeException error) { throw unavailable("柜次权威事实不可用",error); }
|
||||
}
|
||||
/** 原顺序读取全部事实;慢调用和失败仅附加无业务内容的局部统计。 */
|
||||
public Map<String,Fact> facts(List<AppointmentWorkState> states) {
|
||||
var timing=new FactTiming();boolean success=false;
|
||||
try {
|
||||
var result=new LinkedHashMap<String,Fact>();
|
||||
var remote=new ArrayList<FactRequest>();
|
||||
var groups=new LinkedHashMap<BusinessProvider,List<FactRequest>>();
|
||||
@@ -53,8 +57,8 @@ public class AppointmentWorkFactAdapter implements AppointmentWorkFactGateway {
|
||||
}
|
||||
}
|
||||
for(int start=0; start<remote.size(); start+=200) {
|
||||
var requests=remote.subList(start,Math.min(start+200,remote.size())); requireToken();
|
||||
try { var response=firstleg.facts(token,requests);
|
||||
var requests=remote.subList(start,Math.min(start+200,remote.size())); timing.stage="FIRSTLEG"; requireToken();
|
||||
try { var response=timing.measure(timing.firstleg,requests.size(),()->firstleg.facts(token,requests));
|
||||
if(response==null || !response.successful()) throw unavailable("提柜及还空事实读取失败");
|
||||
append(result,requests,response.data());
|
||||
} catch(RuntimeException error) { throw unavailable("Firstleg工作事实不可用",error); }
|
||||
@@ -62,11 +66,55 @@ public class AppointmentWorkFactAdapter implements AppointmentWorkFactGateway {
|
||||
groups.forEach((provider,requests)->{
|
||||
for(int start=0; start<requests.size(); start+=200) {
|
||||
var batch=requests.subList(start,Math.min(start+200,requests.size()));
|
||||
try { append(result,batch,provider.read(batch)); }
|
||||
var statistics=timing.provider(provider);
|
||||
try { append(result,batch,timing.measure(statistics,batch.size(),()->provider.read(batch))); }
|
||||
catch(RuntimeException error) { throw unavailable("预约/收货/运输事实不可用",error); }
|
||||
}
|
||||
});
|
||||
return result;
|
||||
success=true;return result;
|
||||
} finally { timing.finish(success); }
|
||||
}
|
||||
/** 一次 facts 调用的计时,不保存原来源或返回值,不跨调用缓存。 */
|
||||
private static final class FactTiming {
|
||||
private final long started=System.nanoTime();
|
||||
private final CallStatistics firstleg=new CallStatistics();
|
||||
private final Map<String,CallStatistics> providers=new LinkedHashMap<>();
|
||||
private String stage="PREPARE";
|
||||
/** 固定诊断桶不使用代理名、业务参数或异常文本。 */
|
||||
private CallStatistics provider(BusinessProvider provider) {
|
||||
String name=org.springframework.util.ClassUtils.getUserClass(provider).getSimpleName();
|
||||
name=switch(name) {
|
||||
case "AppointmentWorkReceivingProvider","AppointmentWorkDeliveryProvider","AppointmentWorkTransportProvider","AppointmentWorkDirectTransportProvider" -> name;
|
||||
default -> "OTHER";
|
||||
};
|
||||
stage=name;
|
||||
return providers.computeIfAbsent(name,ignored->new CallStatistics());
|
||||
}
|
||||
/** 只统计真正尝试的提供端调用;校验及组装耗时保留在 totalMs 中。 */
|
||||
private <T> T measure(CallStatistics statistics,int requests,java.util.function.Supplier<T> operation) {
|
||||
statistics.calls++;statistics.requests+=requests;long begin=System.nanoTime();
|
||||
try { return operation.get(); }
|
||||
finally { statistics.elapsed+=System.nanoTime()-begin; }
|
||||
}
|
||||
/** 成功慢调用或任意失败只输出一次汇总,日志故障不得遮蔽原异常。 */
|
||||
private void finish(boolean success) {
|
||||
long totalMs=java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-started);
|
||||
if(success && totalMs<250) return;
|
||||
try {
|
||||
var summaries=new LinkedHashMap<String,String>();
|
||||
providers.forEach((name,value)->summaries.put(name,value.summary()));
|
||||
LOG.warn("Appointment work facts diagnostic: totalMs={}, firstlegCalls={}, firstlegRequests={}, firstlegMs={}, providers={}, failedStage={}",
|
||||
totalMs,firstleg.calls,firstleg.requests,firstleg.millis(),summaries,success?"NONE":stage);
|
||||
} catch(RuntimeException diagnosticFailure) { /* 仅忽略诊断故障,原事实错误仍沿原路径抛出。 */ }
|
||||
}
|
||||
}
|
||||
/** 仅包含数量和单调时钟耗时的累加器。 */
|
||||
private static final class CallStatistics {
|
||||
private int calls,requests;private long elapsed;
|
||||
/** 输出整毫秒,未计入网络之后的事实完整性校验。 */
|
||||
private long millis() { return java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(elapsed); }
|
||||
/** 固定字段格式便于日志汇总,不包含任何请求或响应内容。 */
|
||||
private String summary() { return "calls="+calls+",requests="+requests+",ms="+millis(); }
|
||||
}
|
||||
/** 每次生成建立独立读取器;没有实际需求读取时不引入原业务依赖。 */
|
||||
public RequirementReader openRequirements() {
|
||||
|
||||
+10
-5
@@ -69,10 +69,11 @@ public class AppointmentWorkSyncService {
|
||||
var facts=gateway.facts(transportItems);
|
||||
timing.next(SyncPhase.RECONCILE);
|
||||
reconcileTransport(all,facts);
|
||||
timing.next(SyncPhase.ACTIVE_FACTS);
|
||||
timing.next(SyncPhase.ACTIVE_STORE);
|
||||
all=new ArrayList<>(store.all());
|
||||
var activeItems=all.stream().filter(s->!s.isRetired()).toList();
|
||||
timing.activeFactsCount=activeItems.size();
|
||||
timing.next(SyncPhase.ACTIVE_FACTS);
|
||||
facts=gateway.facts(activeItems);
|
||||
timing.next(SyncPhase.OBSERVE_CHECKPOINT);
|
||||
observe(all,facts,SYSTEM);
|
||||
@@ -84,7 +85,7 @@ public class AppointmentWorkSyncService {
|
||||
} finally { timing.finish(success); }
|
||||
}
|
||||
/** 固定阶段名用于慢同步定位,不包含来源、游标或异常业务内容。 */
|
||||
private enum SyncPhase { GUARD, DISCOVER, GENERATE, TRANSPORT_FACTS, RECONCILE, ACTIVE_FACTS, OBSERVE_CHECKPOINT }
|
||||
private enum SyncPhase { GUARD, DISCOVER, GENERATE, TRANSPORT_FACTS, RECONCILE, ACTIVE_STORE, ACTIVE_FACTS, OBSERVE_CHECKPOINT }
|
||||
/** 仅统计本次方法执行,不含事务代理提交,不能当作精确锁持有时间。 */
|
||||
private static final class SyncTiming {
|
||||
private final long started=System.nanoTime();
|
||||
@@ -102,9 +103,13 @@ public class AppointmentWorkSyncService {
|
||||
long now=System.nanoTime();elapsed[phase.ordinal()]+=now-phaseStarted;
|
||||
long totalMs=java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(now-started);
|
||||
if(totalMs<250) return;
|
||||
LOG.warn("Appointment work sync slow: totalMs={}, guardMs={}, discoverMs={}, generateMs={}, transportFactsMs={}, reconcileMs={}, activeFactsMs={}, observeCheckpointMs={}, pageContainers={}, transportFactsCount={}, activeFactsCount={}, complete={}, failedStage={}",
|
||||
totalMs,millis(SyncPhase.GUARD),millis(SyncPhase.DISCOVER),millis(SyncPhase.GENERATE),millis(SyncPhase.TRANSPORT_FACTS),millis(SyncPhase.RECONCILE),millis(SyncPhase.ACTIVE_FACTS),millis(SyncPhase.OBSERVE_CHECKPOINT),
|
||||
pageContainers,transportFactsCount,activeFactsCount,complete,success?"NONE":phase.name());
|
||||
try {
|
||||
// 旧 activeFactsMs 仍包含工作表读取、解析及适配;新增明细不能改变旧指标口径。
|
||||
long activeMs=java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(elapsed[SyncPhase.ACTIVE_STORE.ordinal()]+elapsed[SyncPhase.ACTIVE_FACTS.ordinal()]);
|
||||
LOG.warn("Appointment work sync slow: totalMs={}, guardMs={}, discoverMs={}, generateMs={}, transportFactsMs={}, reconcileMs={}, activeFactsMs={}, activeStoreMs={}, activeAdapterMs={}, observeCheckpointMs={}, pageContainers={}, transportFactsCount={}, activeFactsCount={}, complete={}, failedStage={}",
|
||||
totalMs,millis(SyncPhase.GUARD),millis(SyncPhase.DISCOVER),millis(SyncPhase.GENERATE),millis(SyncPhase.TRANSPORT_FACTS),millis(SyncPhase.RECONCILE),activeMs,millis(SyncPhase.ACTIVE_STORE),millis(SyncPhase.ACTIVE_FACTS),millis(SyncPhase.OBSERVE_CHECKPOINT),
|
||||
pageContainers,transportFactsCount,activeFactsCount,complete,success?"NONE":phase.name());
|
||||
} catch(RuntimeException diagnosticFailure) { /* 诊断失败不能替换原业务异常或阻断提交。 */ }
|
||||
}
|
||||
/** 阶段仅输出整数毫秒,不携带业务标识。 */
|
||||
private long millis(SyncPhase value) { return java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(elapsed[value.ordinal()]); }
|
||||
|
||||
Reference in New Issue
Block a user