wangyunpeng 5 днів тому
батько
коміт
86faea671e

+ 0 - 1
core/src/main/java/com/tzld/videoVector/config/db/PgVectorDBConfig.java

@@ -47,7 +47,6 @@ public class PgVectorDBConfig {
         org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
         configuration.setMapUnderscoreToCamelCase(true);
         configuration.setUseGeneratedKeys(true);
-        configuration.addInterceptor(new com.tzld.videoVector.config.mybatis.HnswEfSearchInterceptor());
         sessionFactory.setConfiguration(configuration);
         sessionFactory.setTypeAliasesPackage("com.tzld.videoVector");
         return sessionFactory.getObject();

+ 0 - 47
core/src/main/java/com/tzld/videoVector/config/mybatis/HnswEfSearchInterceptor.java

@@ -1,47 +0,0 @@
-package com.tzld.videoVector.config.mybatis;
-
-import org.apache.ibatis.executor.Executor;
-import org.apache.ibatis.mapping.MappedStatement;
-import org.apache.ibatis.plugin.*;
-import org.apache.ibatis.session.ResultHandler;
-import org.apache.ibatis.session.RowBounds;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.sql.Connection;
-import java.sql.Statement;
-import java.util.Properties;
-
-@Intercepts({
-    @Signature(type = Executor.class, method = "query",
-        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
-})
-public class HnswEfSearchInterceptor implements Interceptor {
-
-    private static final Logger log = LoggerFactory.getLogger(HnswEfSearchInterceptor.class);
-
-    private volatile boolean logged;
-
-    @Override
-    public Object intercept(Invocation invocation) throws Throwable {
-        Executor executor = (Executor) invocation.getTarget();
-        Connection connection = executor.getTransaction().getConnection();
-        try (Statement stmt = connection.createStatement()) {
-            stmt.execute("SET hnsw.ef_search = 200");
-        }
-        if (!logged) {
-            logged = true;
-            log.info("HnswEfSearchInterceptor activated: hnsw.ef_search = 200");
-        }
-        return invocation.proceed();
-    }
-
-    @Override
-    public Object plugin(Object target) {
-        return Plugin.wrap(target, this);
-    }
-
-    @Override
-    public void setProperties(Properties properties) {
-    }
-}

+ 54 - 6
server/src/main/java/com/tzld/videoVector/controller/HealthController.java

@@ -1,21 +1,69 @@
 package com.tzld.videoVector.controller;
 
 import com.tzld.videoVector.annotation.NoRequestLog;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.Statement;
+
 @RestController
 @RequestMapping("/")
 public class HealthController {
 
-    /**
-     * 探活
-     * @return
-     */
+    private static final Logger log = LoggerFactory.getLogger(HealthController.class);
+
+    private final DataSource videoVectorDataSource;
+    private final DataSource pgVectorDataSource;
+    private final RedisConnectionFactory redisConnectionFactory;
+
+    public HealthController(
+            @Qualifier("videoVectorDataSource") DataSource videoVectorDataSource,
+            @Qualifier("pgVectorDataSource") DataSource pgVectorDataSource,
+            RedisConnectionFactory redisConnectionFactory) {
+        this.videoVectorDataSource = videoVectorDataSource;
+        this.pgVectorDataSource = pgVectorDataSource;
+        this.redisConnectionFactory = redisConnectionFactory;
+    }
+
     @GetMapping("/healthcheck")
     @NoRequestLog
-    public String healthcheck() {
-        return "ok";
+    public ResponseEntity<String> healthcheck() {
+        if (!checkDB(videoVectorDataSource, "mysql") || !checkDB(pgVectorDataSource, "pgvector")) {
+            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("db not ready");
+        }
+        if (!checkRedis()) {
+            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("redis not ready");
+        }
+        return ResponseEntity.ok("ok");
+    }
+
+    private boolean checkDB(DataSource ds, String name) {
+        try (Connection conn = ds.getConnection();
+             Statement stmt = conn.createStatement()) {
+            stmt.execute("SELECT 1");
+            return true;
+        } catch (Exception e) {
+            log.warn("healthcheck: {} not ready — {}", name, e.getMessage());
+            return false;
+        }
+    }
+
+    private boolean checkRedis() {
+        try {
+            Object result = redisConnectionFactory.getConnection().execute("PING");
+            return "PONG".equals(result);
+        } catch (Exception e) {
+            log.warn("healthcheck: redis not ready — {}", e.getMessage());
+            return false;
+        }
     }
 }

+ 79 - 0
server/src/main/java/com/tzld/videoVector/listener/StartupWarmupListener.java

@@ -0,0 +1,79 @@
+package com.tzld.videoVector.listener;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+@Component
+public class StartupWarmupListener {
+
+    private static final Logger log = LoggerFactory.getLogger(StartupWarmupListener.class);
+
+    private final DataSource videoVectorDataSource;
+    private final DataSource pgVectorDataSource;
+    private final RedisConnectionFactory redisConnectionFactory;
+
+    public StartupWarmupListener(
+            @Qualifier("videoVectorDataSource") DataSource videoVectorDataSource,
+            @Qualifier("pgVectorDataSource") DataSource pgVectorDataSource,
+            RedisConnectionFactory redisConnectionFactory) {
+        this.videoVectorDataSource = videoVectorDataSource;
+        this.pgVectorDataSource = pgVectorDataSource;
+        this.redisConnectionFactory = redisConnectionFactory;
+    }
+
+    @EventListener(ApplicationReadyEvent.class)
+    public void onReady() {
+        log.info("warming up connection pools...");
+        warmUpDB(videoVectorDataSource, "mysql");
+        warmUpPGVector(pgVectorDataSource);
+        warmUpRedis();
+        log.info("connection pools warmed up");
+    }
+
+    private void warmUpDB(DataSource ds, String name) {
+        try (Connection conn = ds.getConnection();
+             Statement stmt = conn.createStatement()) {
+            stmt.execute("SELECT 1");
+            log.info("{} connection pool ready", name);
+        } catch (Exception e) {
+            log.error("{} warmup failed: {}", name, e.getMessage());
+        }
+    }
+
+    private void warmUpPGVector(DataSource ds) {
+        try (Connection conn = ds.getConnection();
+             Statement stmt = conn.createStatement()) {
+            stmt.execute("SELECT 1");
+            // 验证 connection-init-sql 是否生效
+            ResultSet rs = stmt.executeQuery("SHOW hnsw.ef_search");
+            if (rs.next()) {
+                log.info("pgvector connection pool ready, hnsw.ef_search = {}", rs.getString(1));
+            }
+        } catch (Exception e) {
+            log.error("pgvector warmup failed: {}", e.getMessage());
+        }
+    }
+
+    private void warmUpRedis() {
+        try {
+            Object result = redisConnectionFactory.getConnection().execute("PING");
+            if ("PONG".equals(result)) {
+                log.info("redis connection pool ready");
+            } else {
+                log.warn("redis PING unexpected response: {}", result);
+            }
+        } catch (Exception e) {
+            log.error("redis warmup failed: {}", e.getMessage());
+        }
+    }
+}

+ 2 - 4
server/src/main/resources/application.yml

@@ -55,10 +55,8 @@ eureka:
 
 logging:
   level:
-    # 替换为你的 Mapper 接口所在包路径(根据错误日志中的包名调整)
-    com.tzld.videoVector.dao.mapper.videoVector.deconstruct: DEBUG  # 输出该包下所有 Mapper 的 SQL 日志
-    # 可选:HikariCP 连接池日志(调试连接问题时开启)
-    com.zaxxer.hikari: INFO  # 连接池基本信息(避免 DEBUG 级别日志过多)
+    com.tzld.videoVector.dao.mapper: DEBUG  # 输出所有 Mapper SQL 日志到控制台
+    com.zaxxer.hikari: DEBUG  # 连接池生命周期日志,包含 connectionInitSql 执行
 
 oss:
   videoVector:

+ 4 - 0
server/src/main/resources/logback-spring.xml

@@ -153,6 +153,10 @@
     <logger name="org.mybatis.spring.SqlSessionUtils" level="WARN"/>
     <!-- 屏蔽 JDBC Connection will not be managed by Spring 日志 -->
     <logger name="org.mybatis.spring.transaction.SpringManagedTransaction" level="WARN"/>
+    <!-- SQL 日志仅输出到控制台,不发送到阿里云日志 -->
+    <logger name="com.tzld.videoVector.dao.mapper" level="DEBUG" additivity="false">
+        <appender-ref ref="CONSOLE"/>
+    </logger>
     <!--aliyun loghub 为了防止进程退出时,内存中的数据丢失,请加上此选项-->
     <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>