Spring Boot微服务优雅上下线:健康检查与流量平滑迁移方案

微服务上下线的痛点场景

Spring Boot微服务在滚动更新过程中,如果没有正确配置优雅上下线策略,会出现两类典型问题:下线时,已有请求尚未处理完毕就被强制中断,返回502或连接重置;上线时,新实例启动完成但尚未完成资源预热(数据库连接池、JIT编译、缓存填充),过早接收流量导致请求超时或慢响应。这两类问题在Kubernetes滚动更新场景下尤为常见,因为Kubernetes默认的就绪检查机制相对简单,无法准确反映服务的真实可用状态。

优雅上下线的本质是流量管控:下线时让存量请求处理完再关闭进程,上线时等服务真正就绪后再接收流量。下面从Spring Boot应用层面和基础设施层面两个维度展开方案。

优雅下线:Spring Boot应用层配置

Spring Boot 2.3+内置了优雅停机支持,配置后收到SIGTERM信号时,Web服务器会停止接收新请求,等待已有请求处理完毕再关闭。

# application.yml - 优雅停机配置
server:
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

management:
  endpoints:
    web:
      exposure:
        include: health,info,readiness,liveness
  endpoint:
    health:
      show-details: always
  health:
    readinessState:
      enabled: true
    livenessState:
      enabled: true

优雅停机的实现原理:Spring Boot收到SIGTERM后,先将ApplicationContext标记为closing状态,Web服务器拒绝新连接(返回Connection: close头),已建立的连接继续处理。超过timeout-per-shutdown-phase后强制关闭。需要注意的是,如果业务线程持有分布式锁或正在执行数据库事务,超时关闭可能导致锁未释放或事务回滚不完整,需要在应用层注册ShutdownHook做清理。

// 注册停机清理逻辑
@Component
public class GracefulShutdownHandler implements DisposableBean {

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void destroy() throws Exception {
        redissonClient.getLocks().forEach(lock -> {
            try {
                lock.forceUnlock();
            } catch (Exception e) {
                log.warn("Failed to unlock: {}", lock.getName(), e);
            }
        });
        log.info("Shutdown cleanup completed");
    }
}

优雅上线:Readiness Probe预热策略

Kubernetes的readiness probe决定Pod是否接收Service流量。Spring Boot Actuator的/actuator/health/readiness端点与Kubernetes readiness probe对接,但默认行为只是检查ApplicationContext是否就绪,不包含业务预热逻辑。

// 自定义Readiness指示器 - 预热完成后才标记就绪
@Component
public class ServiceReadinessIndicator implements ReadinessIndicator {

    private volatile boolean warmedUp = false;

    @PostConstruct
    public void warmUp() {
        CompletableFuture.runAsync(() -> {
            try {
                warmUpConnectionPool();
                warmUpJit();
                warmUpCache();
                warmedUp = true;
                log.info("Service warm-up completed");
            } catch (Exception e) {
                log.error("Warm-up failed", e);
                warmedUp = true;
            }
        });
    }

    private void warmUpConnectionPool() {
        jdbcTemplate.queryForObject("SELECT 1", Integer.class);
    }

    private void warmUpJit() {
        orderService.calculateTotal(new OrderDto());
    }

    private void warmUpCache() {
        cacheService.preloadHotKeys();
    }

    @Override
    public Health health() {
        if (warmedUp) {
            return Health.up().build();
        }
        return Health.down().withDetail("reason", "warming up").build();
    }
}

Kubernetes侧的优雅上下线配置

应用层配置只是方案的一部分,Kubernetes侧的Pod生命周期管理同样关键。

# Deployment配置 - 完整的优雅上下线策略
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: order-service
        image: order-service:1.2.0
        ports:
        - containerPort: 8080
        startupProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          failureThreshold: 30
          periodSeconds: 2
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          periodSeconds: 10
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          periodSeconds: 5
          failureThreshold: 3
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 5"]

preStop中的sleep 5看似简单,实际解决了关键问题:Kubernetes删除Pod时,先从Endpoints列表移除Pod,再发送SIGTERM。但Endpoint的传播到kube-proxy有延迟,如果立即停机,仍有流量路由到已关闭的Pod。sleep 5秒给kube-proxy足够时间更新iptables规则,确保新流量不再路由到该Pod。

网关层流量切换策略

对于有API网关的架构,可以在网关层实现更精细的流量控制。Spring Cloud Gateway配合Nacos或Consul的服务发现,可以实现权重路由和金丝雀发布。

# Spring Cloud Gateway权重路由配置
spring:
  cloud:
    gateway:
      routes:
      - id: order-service-v1
        uri: lb://order-service-v1
        predicates:
        - Path=/api/orders/**
        - Weight=group1, 90
      - id: order-service-v2
        uri: lb://order-service-v2
        predicates:
        - Path=/api/orders/**
        - Weight=group1, 10

权重路由方案适用于灰度发布场景。在金丝雀发布中,v2版本先接收10%流量观察5分钟,错误率和延迟正常后逐步调高权重至100%。整个过程中,旧版本Pod保持运行,随时可以将流量切回,回滚速度远快于重新部署。

消息中间件场景的优雅上下线

使用RabbitMQ或Kafka消费消息的微服务,上下线时需要额外处理消息消费的完整性。下线前停止消费新消息,等待已拉取的消息处理完毕再关闭。

// Kafka消费者优雅停机
@Component
public class OrderConsumerGracefulShutdown {

    private final KafkaMessageListenerContainer<?, ?> container;

    @EventListener
    public void onApplicationEvent(ContextClosedEvent event) {
        container.stop(() -> {
            log.info("Kafka consumer stopped, waiting for in-flight messages");
        });
    }
}

# Kafka消费者配置
spring:
  kafka:
    consumer:
      auto-offset-reset: earliest
      enable-auto-commit: false
    listener:
      ack-mode: manual_immediate
      concurrency: 3

优雅上下线是微服务稳定性保障的基础能力。Spring Boot的graceful shutdown + Kubernetes的probe + preStop hook + 网关层流量控制的组合,覆盖了绝大多数生产场景。核心原则:下线时先摘流量再停进程,上线时先预热再接流量。把这个原则嵌入到CI/CD流水线和Kubernetes配置模板中,所有微服务默认继承,避免遗漏。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-you-ya-shang-xia-xian-jian-kang-jian/

(0)
小编小编
上一篇 11小时前
下一篇 11小时前

相关推荐