You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scientist4J疑难:故意将候选路径设为Null仍无法触发Mismatch

问题:Scientist4J实验无法触发Mismatch检测

我在使用Scientist4J验证新旧代码路径的响应是否存在不匹配(Mismatch),但即使故意将候选路径的响应设为null,也无法触发Mismatch检测,不清楚哪里配置或逻辑有问题。


调用实验的代码示例

//调用候选路径时传入startTime、startTime(而非endtime)以观察Mismatch表现
callWithExperiment(() -> getAnalyticsInfos(startTime, startTime), "Running candidate `getAnalyticsInfoByTime`");

private <T> List<T> callWithExperiment(Callable<List<T>> callable, String logMessage) {
        try {
            final DropwizardMetricsProvider dropwizardMetricsProvider = new DropwizardMetricsProvider();
            Experiment<List<T>> experiment = new Experiment<>("analytics-experiment", dropwizardMetricsProvider);
            experiment.run(
                    () -> callable.call(), // control路径
                    () -> {
                        log.info(logMessage);
                        Random random = new Random();
                        Thread.sleep(random.nextInt(5) * 100);
                        return callable.call(); // candidate路径
             });

              ConsoleReporter reporter = ConsoleReporter.forRegistry((MetricRegistry) experiment.getMetricsProvider().getRegistry())
                .convertRatesTo(TimeUnit.SECONDS)
                .convertDurationsTo(TimeUnit.MILLISECONDS)
                .build();
             reporter.start(10, TimeUnit.MINUTES);
             reporter.report();
             reporter.stop();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

故意引入不匹配的代码

private List<AnalyticsInfo> getAnalyticsInfos(long startTime, long endTime) {
        return withClient(client -> {
            GetAnalyticsInfoRequest req = new GetAnalyticsInfoRequest().setStartTime(startTime).setEndTime(endTime);
            GetAnalyticsInfoResponse resp = client.geAnalyticsInfo(req);
            List<AnalyticsInfo> results = resp.getInfo();
//当startTime == endTime时故意设置错误响应
            if(startTime == endTime){
                log.info("Updating results to null");
                results = null;
            }
            if(results != null){
                return results.parallelStream()
                        .map(Transformer::toAnalyticsInfo)
                        .collect(Collectors.toList());
            }
            return null;
        });
    }

实验结果

3/31/23 9:39:50 PM =============================================================
-- Counters --------------------------------------------------------------------
scientist.analytics-experiment.candidate.exception
count = 0
scientist.analytics-experiment.mismatch
count = 0
scientist.analytics-experiment.total
count = 1
-- Timers ----------------------------------------------------------------------
scientist.analytics-experiment.candidate
count = 1
sum = 24135238
mean rate = 9.77 calls/second
1-minute rate = 0.00 calls/second
5-minute rate = 0.00 calls/second
15-minute rate = 0.00 calls/second
min = 24.14 milliseconds
max = 24.14 milliseconds
mean = 24.14 milliseconds
stddev = 0.00 milliseconds
median = 24.14 milliseconds
75% <= 24.14 milliseconds
95% <= 24.14 milliseconds
98% <= 24.14 milliseconds
99% <= 24.14 milliseconds
99.9% <= 24.14 milliseconds
scientist.analytics-experiment.control
count = 1
sum = 76235890
mean rate = 9.73 calls/second
1-minute rate = 0.00 calls/second
5-minute rate = 0.00 calls/second
15-minute rate = 0.00 calls/second
min = 76.24 milliseconds
max = 76.24 milliseconds
mean = 76.24 milliseconds
stddev = 0.00 milliseconds
median = 76.24 milliseconds
75% <= 76.24 milliseconds
95% <= 76.24 milliseconds
98% <= 76.24 milliseconds
99% <= 76.24 milliseconds
99.9% <= 76.24 milliseconds

问题排查与解决方案

1. 核心问题:Control和Candidate路径执行了相同逻辑

当前代码中,experiment.run()的control和candidate都调用了同一个callable.call(),也就是都会执行getAnalyticsInfos(startTime, startTime)。当startTime == endTime时,两者都会返回null,结果完全一致,自然不会触发Mismatch。

2. 修正方案:分离Control和Candidate的代码路径

你需要让control调用旧的正确逻辑,candidate调用你要测试的(或故意修改的)逻辑。修改callWithExperiment方法,让它接收两个Callable分别对应control和candidate:

private <T> List<T> callWithExperiment(Callable<List<T>> controlCallable, Callable<List<T>> candidateCallable, String logMessage) {
        try {
            final DropwizardMetricsProvider dropwizardMetricsProvider = new DropwizardMetricsProvider();
            Experiment<List<T>> experiment = new Experiment<>("analytics-experiment", dropwizardMetricsProvider);
            experiment.run(
                    controlCallable, // 旧代码路径(正常逻辑)
                    () -> {
                        log.info(logMessage);
                        Random random = new Random();
                        Thread.sleep(random.nextInt(5) * 100);
                        return candidateCallable.call(); // 新代码路径(或修改后的逻辑)
             });

            // 上报代码保持不变...
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

调用时传入不同的逻辑:

// Control用正确的endTime,Candidate用startTime==endTime触发null
callWithExperiment(
    () -> getAnalyticsInfos(startTime, endTime), 
    () -> getAnalyticsInfos(startTime, startTime), 
    "Running candidate `getAnalyticsInfoByTime`"
);

3. 额外检查:确认Scientist4J的匹配器配置

默认情况下,Scientist4J使用Objects.equals()比较结果,null和null会被判定为相等,只有当control和candidate返回不同结果(比如正常List和null)时,才会触发Mismatch。只要路径分离后结果不同,就能被检测到。


内容的提问来源于stack exchange,提问作者user1751510

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 14:44:55