longz
2026-08-05 e30f03e9e0dc23fad6b5b46900f0c64f3e656da3
fix: 加盟商看板大范围查询读超时(尖峰平谷分块并行反查)

- charge-data 7 个月范围读超时根因:t_charging_order_accounting_strategy 按
charging_order_id 分片且无 site_id,只能按订单 id IN 反查;MyCat/druid 对
IN 列表的解析路由成本随 id 数非线性增长(实测 500 个 5s、5000 个 70s+
触发后端读超时)
- franchiseePhaseRing 改为 100/块 × 16 线程并行反查 + 内存合并:
46k id 实测约 20s(原串行 >70s 超时),典型加盟商 <3s
- 长期方案(待评估):strategy 表冗余 site_id 列 + 回填 + 写入路径,
尖峰平谷可降到亚秒级

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1个文件已修改
37 ■■■■■ 已修改文件
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
@@ -103,6 +103,9 @@
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import com.ruoyi.common.core.utils.DateUtils;
@@ -199,8 +202,11 @@
    @Resource
    private TChargingOrderMapper chargingOrderMapper;
    /** 尖峰平谷反查时分块 IN 的上限,避免超大 IN 触发 MyCat 读超时。 */
    private static final int PHASE_ID_BATCH_SIZE = 5000;
    /** 尖峰平谷反查分块 id 上限:MyCat/druid 对 IN 列表的解析路由成本随 id 数非线性增长
     *  (实测 500 个约 5s、5000 个 70s+ 触发读超时;100 个内约 0.2s/块,约 2ms/个)。 */
    private static final int PHASE_ID_BATCH_SIZE = 100;
    /** 尖峰平谷分块反查并发度:46k id 实测 ~20s,典型加盟商 <3s(串行 5000/块 >70s 读超时)。 */
    private static final ExecutorService PHASE_QUERY_EXECUTOR = Executors.newFixedThreadPool(16);
    @GetMapping("/reservationRecommendation")
    @ApiOperation(value = "获取站点预约充电推荐时段", tags = {"管理后台-充电桩信息"})
@@ -601,13 +607,26 @@
        Map<Integer, BigDecimal> phase = new LinkedHashMap<>(); phase.put(1, BigDecimal.ZERO); phase.put(2, BigDecimal.ZERO); phase.put(3, BigDecimal.ZERO); phase.put(4, BigDecimal.ZERO);
        if (siteIds != null && !siteIds.isEmpty() && range != null && range[0] != null && range[1] != null) {
            List<Long> ids = chargingOrderMapper.franchiseeOrderIds(siteIds, range[0], range[1]);
            for (int from = 0; from < ids.size(); from += PHASE_ID_BATCH_SIZE) {
                List<Long> chunk = ids.subList(from, Math.min(from + PHASE_ID_BATCH_SIZE, ids.size()));
                chargingOrderMapper.franchiseePhaseStatsByOrderIds(chunk).forEach(item -> {
                    if (item != null && item.getType() != null) {
                        phase.computeIfPresent(item.getType(), (key, total) -> total.add(value(item.getCapacity())));
                    }
                });
            if (!ids.isEmpty()) {
                // 分块并行反查 strategy 表(分片键 charging_order_id):MyCat 按 id 路由成本约 2ms/个,
                // 串行 5000/块在大范围下 70s+ 读超时;100/块 × 16 线程实测 46k id 约 20s(代理并发吞吐有限)。
                List<CompletableFuture<Void>> futures = new ArrayList<>();
                for (int from = 0; from < ids.size(); from += PHASE_ID_BATCH_SIZE) {
                    List<Long> chunk = ids.subList(from, Math.min(from + PHASE_ID_BATCH_SIZE, ids.size()));
                    futures.add(CompletableFuture.runAsync(() -> {
                        List<FranchiseePhaseStat> items = chargingOrderMapper.franchiseePhaseStatsByOrderIds(chunk);
                        if (items != null) {
                            synchronized (phase) {
                                items.forEach(item -> {
                                    if (item != null && item.getType() != null) {
                                        phase.computeIfPresent(item.getType(), (key, total) -> total.add(value(item.getCapacity())));
                                    }
                                });
                            }
                        }
                    }, PHASE_QUERY_EXECUTOR));
                }
                CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            }
        }
        String[] names = {"尖", "峰", "平", "谷"}; List<Map<String, Object>> data = new ArrayList<>();