Kaynağa Gözat

match all 优化

wangyunpeng 2 gün önce
ebeveyn
işleme
dda70c5da2

+ 1 - 1
core/src/main/java/com/tzld/videoVector/service/VideoSearchService.java

@@ -43,7 +43,7 @@ public interface VideoSearchService {
      * @param param 匹配参数
      * @return 匹配结果列表
      */
-    List<VideoMatchResult> matchTopNVideo(MatchTopNVideoParam param);
+    List<VideoMatchResult> matchTopNVideo(MatchTopNVideoParam param, Boolean withDetail);
 
     /**
      * 根据 channelContentId 查询解构结果

+ 76 - 35
core/src/main/java/com/tzld/videoVector/service/impl/VideoSearchServiceImpl.java

@@ -30,6 +30,7 @@ import org.springframework.util.StringUtils;
 
 import javax.annotation.Resource;
 import java.util.*;
+import java.util.concurrent.*;
 import java.util.stream.Collectors;
 
 import static com.tzld.videoVector.common.constant.VectorConstants.DEFAULT_CONFIG_CODE;
@@ -38,6 +39,12 @@ import static com.tzld.videoVector.common.constant.VectorConstants.DEFAULT_CONFI
 @Service
 public class VideoSearchServiceImpl implements VideoSearchService {
 
+    private static final ExecutorService MATCH_EXECUTOR = new ThreadPoolExecutor(
+            10, 10, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(32),
+            new ThreadPoolExecutor.CallerRunsPolicy());
+
+
     @Resource
     private DeconstructService deconstructService;
 
@@ -598,7 +605,7 @@ public class VideoSearchServiceImpl implements VideoSearchService {
      * 匹配TopN视频
      */
     @Override
-    public List<VideoMatchResult> matchTopNVideo(MatchTopNVideoParam param) {
+    public List<VideoMatchResult> matchTopNVideo(MatchTopNVideoParam param, Boolean withDetail) {
         if (param == null) {
             log.error("matchTopNVideo 参数为空");
             return Collections.emptyList();
@@ -638,56 +645,90 @@ public class VideoSearchServiceImpl implements VideoSearchService {
         // 缓存 embeddingModel -> queryVector,避免相同模型重复 embedding
         Map<String, List<Float>> embeddingCache = new HashMap<>();
 
+        // 先串行解析各配置的查询向量(涉及缓存共享,不适合并发)
+        Map<DeconstructVectorConfig, List<Float>> configVectorMap = new LinkedHashMap<>();
         for (DeconstructVectorConfig config : searchConfigs) {
+            List<Float> queryVector = resolveQueryVectorForConfig(param, config, embeddingCache);
+            if (queryVector == null || queryVector.isEmpty()) {
+                log.error("配置 {} 无法获取查询向量,跳过", config.getConfigCode());
+                continue;
+            }
+            configVectorMap.put(config, queryVector);
+        }
+
+        if (configVectorMap.isEmpty()) {
+            log.info("所有配置均无法获取查询向量,返回空结果");
+            return Collections.emptyList();
+        }
+
+        // 多configCode并发搜索
+        int finalTopN = topN;
+        List<CompletableFuture<List<VideoMatchResult>>> futures = new ArrayList<>();
+        for (Map.Entry<DeconstructVectorConfig, List<Float>> entry : configVectorMap.entrySet()) {
+            DeconstructVectorConfig config = entry.getKey();
+            List<Float> queryVector = entry.getValue();
             String cfgCode = config.getConfigCode();
-            try {
-                // 解析查询向量(按配置的 embeddingModel 分组缓存)
-                List<Float> queryVector = resolveQueryVectorForConfig(param, config, embeddingCache);
-                if (queryVector == null || queryVector.isEmpty()) {
-                    log.error("配置 {} 无法获取查询向量,跳过", cfgCode);
-                    continue;
-                }
 
-                log.info("配置 {} 开始搜索 Top-{},向量维度: {}", cfgCode, candidateSize, queryVector.size());
-                List<VideoMatch> matches = vectorStoreService.searchTopN(cfgCode, queryVector, candidateSize);
+            CompletableFuture<List<VideoMatchResult>> future = CompletableFuture.supplyAsync(() -> {
+                try {
+                    log.info("配置 {} 开始搜索 Top-{},向量维度: {}", cfgCode, candidateSize, queryVector.size());
+                    List<VideoMatch> matches = vectorStoreService.searchTopN(cfgCode, queryVector, candidateSize);
 
-                if (matches == null || matches.isEmpty()) {
-                    log.info("配置 {} 无匹配结果", cfgCode);
-                    continue;
-                }
+                    if (matches == null || matches.isEmpty()) {
+                        log.info("配置 {} 无匹配结果", cfgCode);
+                        return Collections.<VideoMatchResult>emptyList();
+                    }
 
-                // 多点模式:同一videoId去重保留最高分
-                boolean multiPoint = VectorUtils.isMultiPointConfig(config);
-                if (multiPoint) {
-                    matches = deduplicateMultiPointMatches(matches, cfgCode);
-                } else {
-                    // 单点模式:直接设置 configCode
-                    for (VideoMatch m : matches) {
-                        m.setConfigCode(cfgCode);
+                    // 多点模式:同一videoId去重保留最高分
+                    boolean multiPoint = VectorUtils.isMultiPointConfig(config);
+                    if (multiPoint) {
+                        matches = deduplicateMultiPointMatches(matches, cfgCode);
+                    } else {
+                        // 单点模式:直接设置 configCode
+                        for (VideoMatch m : matches) {
+                            m.setConfigCode(cfgCode);
+                        }
                     }
-                }
 
-                // 每个配置独立进行审核过滤并取各自的 topN
-                List<VideoMatch> filteredMatches = filterByAuditStatus(matches, topN);
+                    // 每个配置独立进行审核过滤并取各自的 topN
+                    List<VideoMatch> filteredMatches = filterByAuditStatus(matches, finalTopN);
+
+                    // 转化为强类型返回格式
+                    List<VideoMatchResult> configResult = new ArrayList<>(filteredMatches.size());
+                    for (VideoMatch match : filteredMatches) {
+                        configResult.add(new VideoMatchResult(cfgCode, match.getVideoId(), match.getScore(), match.getText()));
+                    }
 
-                // 转化为强类型返回格式
-                for (VideoMatch match : filteredMatches) {
-                    result.add(new VideoMatchResult(cfgCode, match.getVideoId(), match.getScore(), match.getText()));
+                    log.info("配置 {} 搜索完成,返回 {} 条结果", cfgCode, configResult.size());
+                    return configResult;
+                } catch (Exception e) {
+                    log.error("配置 {} 搜索失败: {}", cfgCode, e.getMessage(), e);
+                    return Collections.<VideoMatchResult>emptyList();
                 }
+            }, MATCH_EXECUTOR);
 
-                log.info("配置 {} 搜索完成,返回 {} 条结果", cfgCode, filteredMatches.size());
+            futures.add(future);
+        }
 
+        // 等待所有配置搜索完成并汇总
+        for (CompletableFuture<List<VideoMatchResult>> future : futures) {
+            try {
+                result.addAll(future.get(30, TimeUnit.SECONDS));
+            } catch (TimeoutException e) {
+                log.error("配置搜索超时: {}", e.getMessage());
             } catch (Exception e) {
-                log.error("配置 {} 搜索失败: {}", cfgCode, e.getMessage(), e);
+                log.error("配置搜索异常: {}", e.getMessage(), e);
             }
         }
 
         log.info("匹配完成,configCode: {},共返回 {} 条结果", param.getConfigCode(), result.size());
 
-        // 从 Redis 获取视频基础信息并填充到结果中
-        enrichVideoDetail(result);
-        // 从 Redis 获取视频解构(选题 + 高价值实质点)并填充
-        enrichDeconstruct(result);
+        if (withDetail) {
+            // 从 Redis 获取视频基础信息并填充到结果中
+            enrichVideoDetail(result);
+            // 从 Redis 获取视频解构(选题 + 高价值实质点)并填充
+            enrichDeconstruct(result);
+        }
 
         return result;
     }
@@ -1356,7 +1397,7 @@ public class VideoSearchServiceImpl implements VideoSearchService {
         matchParam.setQueryVector(param.getQueryVector());
         matchParam.setTopN(param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10);
 
-        List<VideoMatchResult> rawMatches = matchTopNVideo(matchParam);
+        List<VideoMatchResult> rawMatches = matchTopNVideo(matchParam, false);
         if (rawMatches == null || rawMatches.isEmpty()) {
             log.info("recallWithScore: 召回结果为空");
             return emptyScoreResult(alpha, rovP95, rovP5, simMin);

+ 2 - 2
core/src/main/java/com/tzld/videoVector/service/recall/impl/VectorRecallTestServiceImpl.java

@@ -106,7 +106,7 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
         matchParam.setConfigCode(param.getConfigCode());
         matchParam.setTopN(param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10);
 
-        List<VideoMatchResult> rawMatches = videoSearchService.matchTopNVideo(matchParam);
+        List<VideoMatchResult> rawMatches = videoSearchService.matchTopNVideo(matchParam, true);
         if (CollectionUtils.isEmpty(rawMatches)) {
             return empty;
         }
@@ -128,7 +128,7 @@ public class VectorRecallTestServiceImpl implements VectorRecallTestService {
         matchParam.setConfigCode(param.getConfigCode());
         matchParam.setTopN(param.getTopN() != null && param.getTopN() > 0 ? param.getTopN() : 10);
 
-        List<VideoMatchResult> rawMatches = videoSearchService.matchTopNVideo(matchParam);
+        List<VideoMatchResult> rawMatches = videoSearchService.matchTopNVideo(matchParam, true);
         if (CollectionUtils.isEmpty(rawMatches)) {
             return empty;
         }

+ 1 - 1
server/src/main/java/com/tzld/videoVector/controller/VideoSearchController.java

@@ -46,7 +46,7 @@ public class VideoSearchController {
 
     @PostMapping("/matchTopNVideo")
     public CommonResponse<List<VideoMatchResult>> matchTopNVideo(@RequestBody MatchTopNVideoParam param) {
-        return CommonResponse.success(videoSearchService.matchTopNVideo(param));
+        return CommonResponse.success(videoSearchService.matchTopNVideo(param, true));
     }
 
     @GetMapping("/getAllConfigCodes")