实战:用HealthCheckProcessor实现Redis探活插件

用 HealthCheckProcessor 写了一个 Redis 探活插件,3 小时省掉了 1 个运维脚本:自定义健康检查实战


文章目录

  • [用 HealthCheckProcessor 写了一个 Redis 探活插件,3 小时省掉了 1 个运维脚本:自定义健康检查实战](#用 HealthCheckProcessor 写了一个 Redis 探活插件,3 小时省掉了 1 个运维脚本:自定义健康检查实战)

Nacos 默认只能检查 TCP 和 HTTP,但你的服务不止这两种

Nacos 内置的健康检查支持两种协议:TCP 端口探测和 HTTP 接口探活。大部分微服务用这两种就够了。

但你有 Redis 实例、MySQL 实例、RabbitMQ 实例也在 Nacos 里注册了。它们的"健康"不是一个 TCP 端口通了就算------TCP 通了不代表 Redis 能读写,不代表 MySQL 连接池没满。

你需要一种自定义的健康检查方式:连上 Redis 发个 PING,收到 PONG 才算健康。

这篇文章用一个完整的例子走一遍 HealthCheckProcessor 插件的开发、打包、部署、验证全流程。代码可以直接拿去改。

⚠️ 本文基于 Nacos Server 2.3.x。HealthCheckProcessor 接口在 2.0.0 后稳定。如果你用的是 1.4.x,接口签名略有不同(返回值是 HealthCheckResult 而非 CheckResult)。


HealthCheckProcessor 接口定义

java 复制代码
// com.alibaba.nacos.naming.healthcheck.extend.HealthCheckProcessor

public interface HealthCheckProcessor extends SpiExtension {

    /**
     * 执行健康检查
     * @param task 健康检查任务,包含实例信息
     * @param cluster 集群信息
     */
    void process(HealthCheckTask task, Cluster cluster);

    /**
     * 返回这个 Processor 处理的检查类型名称
     * 必须和实例注册时指定的 healthCheckType 一致
     */
    String getType();
}

你需要实现 3 个方法:

方法 作用 你的实现
process(task, cluster) 执行实际的健康检查逻辑 连 Redis 发 PING
getType() 返回检查类型标识 "REDIS"
getOrder()(可选) 排序优先级 默认 1000

完整实现:Redis 探活插件

项目结构

复制代码
nacos-healthcheck-redis/
├── pom.xml
└── src/main/
    ├── java/com/example/nacos/plugin/
    │   └── RedisHealthCheckProcessor.java
    └── resources/META-INF/services/
        └── com.alibaba.nacos.naming.healthcheck.extend.HealthCheckProcessor

pom.xml

xml 复制代码
<project>
    <groupId>com.example</groupId>
    <artifactId>nacos-healthcheck-redis</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!-- Nacos Naming 接口(provided,不打包进去) -->
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-naming</artifactId>
            <version>2.3.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Jedis(轻量 Redis 客户端) -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.4.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 打包成 fat jar,包含 Jedis 依赖 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals><goal>shade</goal></goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

核心实现类

java 复制代码
package com.example.nacos.plugin;

import com.alibaba.nacos.api.naming.pojo.healthcheck.AbstractHealthChecker;
import com.alibaba.nacos.naming.healthcheck.extend.HealthCheckProcessor;
import com.alibaba.nacos.naming.healthcheck.extend.HealthCheckTask;
import com.alibaba.nacos.naming.pojo.Cluster;
import redis.clients.jedis.Jedis;

public class RedisHealthCheckProcessor implements HealthCheckProcessor {

    @Override
    public String getType() {
        return "REDIS";  // 必须和注册实例时的 healthCheckType 一致
    }

    @Override
    public void process(HealthCheckTask task, Cluster cluster) {
        // 从实例元数据中获取 Redis 地址
        String ip = task.getIp();
        int port = task.getPort();

        // 从 metadata 读取密码(可选)
        String password = task.getMetadata().getOrDefault("redis.password", "");
        int timeout = Integer.parseInt(
            task.getMetadata().getOrDefault("redis.timeout", "2000"));

        try (Jedis jedis = new Jedis(ip, port, timeout)) {
            if (!password.isEmpty()) {
                jedis.auth(password);
            }

            String pong = jedis.ping();
            if ("PONG".equals(pong)) {
                // 健康检查通过
                task.setCheckResult(true);
            } else {
                // 响应异常
                task.setCheckResult(false);
            }
        } catch (Exception e) {
            // 连接失败或超时
            task.setCheckResult(false);
        }
    }

    @Override
    public void initialize() {
        // 插件初始化:可以预热连接池
    }

    @Override
    public void shutdown() {
        // 插件销毁:关闭连接池
    }
}

SPI 配置文件

复制代码
# src/main/resources/META-INF/services/com.alibaba.nacos.naming.healthcheck.extend.HealthCheckProcessor
com.example.nacos.plugin.RedisHealthCheckProcessor

打包、部署、验证

打包

bash 复制代码
cd nacos-healthcheck-redis
mvn clean package -DskipTests

# 产出:target/nacos-healthcheck-redis-1.0.0.jar

部署

bash 复制代码
# 把 jar 包复制到 Nacos Server 的 plugins 目录
cp target/nacos-healthcheck-redis-1.0.0.jar /home/nacos/plugins/

# 重启 Nacos(集群模式下逐台重启)
cd /home/nacos/bin
sh shutdown.sh
sh startup.sh -m cluster

注册实例时指定检查类型

bash 复制代码
# 注册一个 Redis 实例到 Nacos,指定 healthCheckType 为 REDIS
curl -X POST "http://nacos:8848/nacos/v1/ns/instance" \
  -d "serviceName=redis-cluster" \
  -d "ip=10.0.1.50" \
  -d "port=6379" \
  -d "ephemeral=false" \
  -d "metadata=healthCheckType=REDIS,redis.password=mypassword" \
  -d "namespaceId=prod"

关键点:

  • ephemeral=false:持久化实例(服务端主动检查健康状态,不走客户端心跳)
  • metadata.healthCheckType=REDIS:告诉 Nacos 用你的 REDIS 检查器
  • metadata.redis.password:你的插件从 metadata 读取密码

验证

bash 复制代码
# 查看实例健康状态
curl -s "http://nacos:8848/nacos/v1/ns/instance" \
  -d "serviceName=redis-cluster" \
  -d "ip=10.0.1.50" \
  -d "port=6379" \
  -d "namespaceId=prod" | jq '.healthy'
# 期望:true(如果 Redis 在线且 PING 成功)

# 停掉 Redis 后再次检查
# 期望:false(30 秒内 Nacos 会检测到健康检查失败)

扩展:HTTP 接口探活插件

如果你的服务有一个自定义的 /health 端点,返回的 JSON 里包含多个组件的状态:

json 复制代码
{
  "status": "UP",
  "components": {
    "db": {"status": "UP"},
    "redis": {"status": "DOWN"},
    "mq": {"status": "UP"}
  }
}

你可以写一个 HTTP 探活插件,解析这个 JSON,任何一个组件 DOWN 就认为不健康:

java 复制代码
public class DeepHealthCheckProcessor implements HealthCheckProcessor {

    @Override
    public String getType() {
        return "DEEP_HTTP";
    }

    @Override
    public void process(HealthCheckTask task, Cluster cluster) {
        String url = "http://" + task.getIp() + ":" + task.getPort() + "/health";

        try {
            // HTTP GET /health
            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(3000);

            if (conn.getResponseCode() == 200) {
                String body = new String(conn.getInputStream().readAllBytes());
                // 检查所有组件状态
                boolean allUp = !body.contains("\"DOWN\"");
                task.setCheckResult(allUp);
            } else {
                task.setCheckResult(false);
            }
        } catch (Exception e) {
            task.setCheckResult(false);
        }
    }
}

一张图带走:健康检查插件开发全流程

#mermaid-svg-HM6h435vbhwQFHKO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HM6h435vbhwQFHKO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HM6h435vbhwQFHKO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HM6h435vbhwQFHKO .error-icon{fill:#552222;}#mermaid-svg-HM6h435vbhwQFHKO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HM6h435vbhwQFHKO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HM6h435vbhwQFHKO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HM6h435vbhwQFHKO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HM6h435vbhwQFHKO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HM6h435vbhwQFHKO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HM6h435vbhwQFHKO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HM6h435vbhwQFHKO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HM6h435vbhwQFHKO .marker.cross{stroke:#333333;}#mermaid-svg-HM6h435vbhwQFHKO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HM6h435vbhwQFHKO p{margin:0;}#mermaid-svg-HM6h435vbhwQFHKO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HM6h435vbhwQFHKO .cluster-label text{fill:#333;}#mermaid-svg-HM6h435vbhwQFHKO .cluster-label span{color:#333;}#mermaid-svg-HM6h435vbhwQFHKO .cluster-label span p{background-color:transparent;}#mermaid-svg-HM6h435vbhwQFHKO .label text,#mermaid-svg-HM6h435vbhwQFHKO span{fill:#333;color:#333;}#mermaid-svg-HM6h435vbhwQFHKO .node rect,#mermaid-svg-HM6h435vbhwQFHKO .node circle,#mermaid-svg-HM6h435vbhwQFHKO .node ellipse,#mermaid-svg-HM6h435vbhwQFHKO .node polygon,#mermaid-svg-HM6h435vbhwQFHKO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HM6h435vbhwQFHKO .rough-node .label text,#mermaid-svg-HM6h435vbhwQFHKO .node .label text,#mermaid-svg-HM6h435vbhwQFHKO .image-shape .label,#mermaid-svg-HM6h435vbhwQFHKO .icon-shape .label{text-anchor:middle;}#mermaid-svg-HM6h435vbhwQFHKO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HM6h435vbhwQFHKO .rough-node .label,#mermaid-svg-HM6h435vbhwQFHKO .node .label,#mermaid-svg-HM6h435vbhwQFHKO .image-shape .label,#mermaid-svg-HM6h435vbhwQFHKO .icon-shape .label{text-align:center;}#mermaid-svg-HM6h435vbhwQFHKO .node.clickable{cursor:pointer;}#mermaid-svg-HM6h435vbhwQFHKO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HM6h435vbhwQFHKO .arrowheadPath{fill:#333333;}#mermaid-svg-HM6h435vbhwQFHKO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HM6h435vbhwQFHKO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HM6h435vbhwQFHKO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HM6h435vbhwQFHKO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HM6h435vbhwQFHKO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HM6h435vbhwQFHKO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HM6h435vbhwQFHKO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HM6h435vbhwQFHKO .cluster text{fill:#333;}#mermaid-svg-HM6h435vbhwQFHKO .cluster span{color:#333;}#mermaid-svg-HM6h435vbhwQFHKO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HM6h435vbhwQFHKO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HM6h435vbhwQFHKO rect.text{fill:none;stroke-width:0;}#mermaid-svg-HM6h435vbhwQFHKO .icon-shape,#mermaid-svg-HM6h435vbhwQFHKO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HM6h435vbhwQFHKO .icon-shape p,#mermaid-svg-HM6h435vbhwQFHKO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HM6h435vbhwQFHKO .icon-shape .label rect,#mermaid-svg-HM6h435vbhwQFHKO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HM6h435vbhwQFHKO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HM6h435vbhwQFHKO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HM6h435vbhwQFHKO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 开发

实现 HealthCheckProcessor

  • getType 返回标识
  1. 配置 SPI

META-INF/services/

写接口→实现类
3. 打包

mvn package

fat jar 含依赖
4. 部署

cp jar → plugins/

重启 Nacos
5. 注册实例

ephemeral=false

metadata.healthCheckType=REDIS
6. 验证

查实例健康状态

停服务看是否变 false
⚠️ nacos-naming

用 provided scope
⚠️ shade 插件

打包依赖
⚠️ 集群模式

逐台重启

6 步走完全流程。每步下面有注意事项:provided scope 不打包 Nacos 依赖、shade 插件打 fat jar、集群模式逐台重启。


你们在 Nacos 里注册过非微服务的组件(Redis、MySQL、MQ)吗?评论区留数字:1=注册过,用 TCP 检查 2=注册过,写了自定义检查插件 3=没注册过,只用 Nacos 管微服务 4=有这个需求,先收藏

相关推荐
my_styles1 个月前
nacos 报错 errCode: 500, errMsg: do metadata operation failed
nacos
染翰1 个月前
Nacos 切换 Namespace 后配置不生效、占位符报错终极复盘
java·后端·spring·nacos
小毕超1 个月前
基于A2A + AgentScope + Nacos 构建分布式高可用异构 Agent 方案
nacos·a2a·agentscope
huipeng9261 个月前
企业级微服务开发实战(二):微服务基础设施搭建与中间件部署
java·redis·mysql·spring cloud·微服务·nacos·rabbitmq
苏渡苇1 个月前
Spring Cloud Alibaba:将 Sentinel 熔断限流规则持久化到 Nacos 配置中心
数据库·spring boot·mysql·spring cloud·nacos·sentinel·持久化
callJJ1 个月前
Nacos 详解——从概念到实战
java·spring boot·spring·spring cloud·微服务·nacos
DN金猿2 个月前
spring.cloud.nacos.discovery.server-addr和spring.cloud.nacos.server-addr区别
java·开发语言·nacos·springcloud·sca
DN金猿2 个月前
SpringCloudAlibaba微服务启动报错
微服务·云原生·nacos·架构·springcloud·sca
jasnet_u2 个月前
SpringBoot服务优雅停机
nacos·springboot·优雅停机