提炼HealthModule至共享模块


在微服务架构中,各服务需统一实现健康检查功能
user微服务中的HealthControllerHealthModule可复用性高,应迁移至共享模块protopkg

1 ) 迁移步骤

  • protopkg/src下创建messages/health目录
  • 复制HealthControllerHealthModule文件至该目录
  • 安装缺失依赖:@nestjs/microservices@grpc/grpc-js
    npm install @nestjs/microservices @grpc/grpc-js
    
  • protopkg/src/index.ts导出模块:
    export * from './messages/health/health.module';
    export * from './messages/health/health.controller';
    

2 ) 验证迁移

  • user服务中替换引用路径:

    // user/src/app.module.ts
    import { HealthModule } from '@app/protopkg';
    
    @Module({
      imports: [HealthModule], // 直接引用共享模块
    })
    export class AppModule {}
    
    • 关键验证:
      • 启动服务后通过gRPC客户端测试健康检查接口
      • 确认响应状态码为SERVING(状态码1)
      • 删除原user服务中的health目录确保无依赖残留

技术细节:共享模块需确保.proto文件路径正确,编译后生成的index.d.ts需包含HealthModule的类型定义,否则会导致运行时依赖解析失败。

集成Terminus实现Consul健康检查


Consul默认HTTP检查不兼容gRPC健康端点,需通过API网关提供统一HTTP健康检查接口:

1 ) 解决方案架构

HTTP请求
gRPC调用
Consul
API Gateway
微服务集群

2 ) 网关层实现

  • 安装依赖:

    npm install @nestjs/terminus 
    
  • 创建HealthController

    // gateway/src/health/health.controller.ts
    import { Controller, Get, Query } from '@nestjs/common';
    import { HealthCheckService, GrpcHealthIndicator } from '@nestjs/terminus';
    
    @Controller('health')
    export class HealthController {
      constructor(
        private health: HealthCheckService,
        private grpc: GrpcHealthIndicator,
      ) {}
    
      @Get()
      async check(@Query('service') service: string) {
        return this.health.check([
          () => this.grpc.checkService(service, {
            package: 'user',
            protoPath: join(__dirname, 'user.proto'),
          }),
        ]);
      }
    }
    

3 ) Consul配置

  • 修改服务注册参数指向网关端点:
    // user/src/main.ts
    const consulService = {
      check: {
        http: `http://gateway:3030/api/v1/health?service=user_service`, // 动态服务标识
        interval: '10s',
      }
    };
    

核心逻辑:

  • GrpcHealthIndicator 向微服务发起GRPC调用验证
  • SERVING状态转换为HTTP 200响应
  • HTTP状态码决定Consul健康状态(200=健康,其他=异常)

动态化健康检查端点配置


多微服务场景需动态管理检查端点,避免硬编码:

1 ) 环境变量配置

.env定义服务端点映射:

# 格式:service_key|grpc_url|package_name|proto_name|service_name
SERVICE_HEALTH_ENDPOINTS=user_grpc|localhost:4001|user|user|user_service, 
auth_grpc|localhost:4002|auth|auth|auth_service

2 ) 动态端点解析

// gateway/src/consul/consul.controller.ts 
@Get()
async check(@Query('service') serviceKey: string, @Res() res) {
 const endpoints = process.env.SERVICE_HEALTH_ENDPOINTS.split(',');
 const configMap = {};

 endpoints.forEach(item => {
   const [key, url, packageName, serviceName] = item.split('|');
   configMap[key] = { url, package: packageName, service: serviceName };
 });

 if (!serviceKey || !configMap[serviceKey]) {
   return res.status(404).json({ message: 'Invalid service key' });
 }

 const { url, package: pkg, service } = configMap[serviceKey];
 return this.health.check([
   () => this.grpc.checkHealth(serviceKey, {
     package: pkg,
     service, // 关键参数!
     protoPath: join(__dirname, `${pkg}.proto`),
     url,
   }),
 ]);
}

技术细节:

  • service 参数必须与GRPC服务的 ServiceDefinition 名称严格匹配
  • 错误示例:proto定义 service HealthService,代码需一致

3 )HTTP状态码处理

try {
  const result = await this.health.check([...]);
  response.status(200).json(result);
} catch (error) {
  response.status(503).json({ status: 'DOWN' }); // Consul识别非200为异常 
}

错误处理优化: 非200状态码触发Consul服务降级:

if (result.status !== 'up') {
  throw new HttpException('Service unhealthy', 503);
}

关键点:Consul仅通过HTTP状态码判断服务健康,必须确保异常时返回非200状态码

技术难点解决方案


问题类型 解决方案 代码示例
gRPC包名冲突 统一.protoservice命名 service Health { rpc Check(...) }
动态proto加载 运行时解析环境变量 protoPath: join(__dirname, ${protoName}.proto)
Consul状态同步 网关层状态码转换 response.status(serviceStatus === 'UP' ? 200 : 503)

关键问题总结

  1. Consul GRPC检查限制
    原生缺陷:Consul客户端SDK 不支持直接配置GRPC检查项,需通过HTTP网关中转。

  2. Terminus配置陷阱

    // 正确配置示例 
    this.grpc.checkHealth('serviceKey', {
      service: 'HealthService', // 必须匹配proto的service名称 
      package: 'user',
      url: 'localhost:4001',
    })
    

    常见错误:忽略 service 参数导致 UNIMPLEMENTED 错误。

  3. 动态端点安全加固
    增加参数校验逻辑防止非法访问:

    // 在动态路由中添加 
    const validKeys = Object.keys(configMap);
    if (!validKeys.includes(serviceKey)) {
      return res.status(400).json({ message: 'Unauthorized service check' });
    }
    

最终效果:

  • 所有微服务移除本地健康检查实现,统一依赖共享模块
  • 各微服务无HTTP端口占用,纯gRPC通信
  • Consul通过单个Gateway端点完成所有GRPC微服务健康检查
  • 新增微服务只需修改.env配置,无需网关代码变更

核心优化总结:

  1. 模块复用:HealthModule抽象至公共层,消除冗余代码
  2. 协议兼容:Terminus解决Consul与gRPC健康检查的协议鸿沟
  3. 动态扩展:通过环境变量实现多服务端点配置,支持无缝扩容
  4. 强健性保障:显式HTTP状态码机制确保Consul正确解析服务状态
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐