Nacos等名字服务如何解决扩缩容毛刺问题?

78 阅读5分钟

背景

  • 最近工作中发现一例服务扩缩容的毛刺问题,排查后发现是使用了名字服务的自注册能力。遂借助AI简单记录一下

Nacos自注册是什么?

  • 服务自注册是指服务在启动时,主动向Nacos注册中心发送注册请求,表明服务已经准备就绪可以接收流量。之后通过心跳机制,与注册中心保持定期通信以表明服务健康状态。
  • 自注册机制通常会配合优雅下线功能,在服务重启或扩缩容时,避免请求处理中断导致的毛刺问题。例如,资料服务在重启前,会先向Nacos发送下线通知,使注册中心停止向该实例分发新请求。

为什么Nacos关联k8s路由,能解决扩缩容毛刺问题?

  • 将Nacos的服务列表与k8s的pod资源同步,能够让Nacos实时感知到k8s中pods的状态变化
  • k8s扩缩容导致的pod状态变化,会第一时间同步到Nacos,确保服务发现信息的准确性,避免出现毛刺
  • k8s的PreStop钩子,能够让Nacos先停止分发新请求,再完成pod重启,实现真正的无损扩缩容

Nacos与k8s集成的核心机制

1. 使用k8s Operator实现双向数据同步

Nacos提供了k8s-operator组件,可以实现Nacos与k8s之间的服务信息双向同步:

  • k8s到Nacos的同步:当k8s中的Service和Endpoints发生变化时,operator会自动将这些变化同步到Nacos中
  • Nacos到k8s的同步:通过operator也可以将Nacos中的服务信息同步到k8s中,形成完整的服务发现闭环

这种双向同步机制能够使非k8s环境和k8s环境中的服务可以相互发现和调用。

2. 利用k8s生命周期钩子实现优雅下线

在k8s环境中,pod的生命周期管理是通过各种钩子(hooks)完成的。其中PreStop钩子是pod终止前执行的操作,可以用来实现服务的优雅下线:

lifecycle:
  preStop:
    exec:
      command:
      - /bin/sh
      - -c
      - "curl -X PUT 'http://localhost:8848/nacos/v1/ns/instance?serviceName=${SERVICE_NAME}&ip=${POD_IP}&port=${SERVER_PORT}&enabled=false'"

通过上面的配置,在pod被k8s终止前,会先执行一个HTTP请求,向Nacos发送服务下线通知,将该实例标记为不健康,Nacos就会停止向该实例分发流量。

典型配置示例

Deployment配置示例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
    spec:
      containers:
      - name: my-service
        image: my-service:latest
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 3
        lifecycle:
          preStop:
            exec:
              command:
              - /bin/sh
              - -c
              - "curl -X PUT 'http://localhost:8848/nacos/v1/ns/instance?serviceName=${SERVICE_NAME}&ip=${POD_IP}&port=${SERVER_PORT}&enabled=false' && sleep 10"
        env:
        - name: SERVICE_NAME
          value: "my-service"
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: SERVER_PORT
          value: "8080"

Nacos服务发现的Spring Boot配置

spring:
  cloud:
    nacos:
      discovery:
        server-addr: nacos-service:8848
        namespace: default
        group: DEFAULT_GROUP
        # 开启优雅上下线功能
        ephemeral: true
        # 健康检查的时间间隔
        heart-beat-interval: 5000
        # 服务实例心跳超时时间
        heart-beat-timeout: 15000

进阶:解决k8s扩缩容场景下的常见问题

1. 服务扩容场景

在扩容场景中,新pod启动后会自动向Nacos注册。为确保新pod能正常接收流量,需要:

  • 合理配置应用的健康检查和就绪检查
  • 确保应用启动完成后再注册到Nacos

2. 服务缩容场景

缩容场景是最容易出现毛刺的,需要特别注意:

  • 确保k8s的terminationGracePeriodSeconds足够长(建议60秒)
  • 在preStop钩子中设置足够的等待时间,给下线通知在Nacos中生效留出时间
  • 考虑实现应用层面的连接排空机制,确保已有请求都能处理完成

3. 处理网络分区和异常情况

为了提高服务发现的可靠性:

  • 配置Nacos集群,避免单点故障
  • 使用合理的心跳超时机制,避免因临时网络问题导致服务被错误下线
  • 客户端实现本地缓存和失败重试机制,降低对Nacos的强依赖

实战经验:生产环境优化建议

  1. 调整心跳频率和超时时间:根据集群规模和网络特性调整,小型集群可以用更短的心跳间隔和超时时间

  2. 实例注册保护阈值:配置Nacos的protect.threshold(通常设为0.8),当健康实例比例低于该值时,Nacos会保护不健康实例不被剔除.这个机制的目的是防止因为网络波动等问题导致大量服务实例被误判为不健康而被剔除,最终可能导致服务完全不可用. 所以实例注册保护阈值是一种"宁可错杀三千,不可放过一个"的反向思维 - 宁可保留一些可能不健康的实例,也不能因为误判导致服务完全不可用。在生产环境中,这是一种重要的服务可用性保障机制。

  3. 基于权重的负载均衡:在扩缩容过程中,可以考虑动态调整实例权重,新实例初始给予较低权重,慢慢提高

  4. 客户端配置:确保客户端有合理的连接超时和重试策略,避免因单个实例不可用导致整体服务调用失败

  5. 监控告警:建立完善的监控体系,关注实例注册、下线事件和服务调用成功率的变化

总结

Nacos与k8s的结合,通过服务列表同步和生命周期钩子配合,能够有效解决服务扩缩容过程中的毛刺问题。在实际应用中,需要根据具体业务场景和系统规模,调整相关参数,才能达到最佳效果。同时,完善的监控和告警机制,也是保障服务稳定运行的重要手段。