第四章:应用层高可用设计

应用层的高可用,说白了就是让业务代码本身具备抗风险能力。我见过太多团队把高可用全压在基础设施上,结果应用一重启就雪崩。今天咱们聊聊三个核心点:无状态设计、健康检查与自动摘除、优雅上下线。

4.1 无状态应用设计原则

无状态,是云原生应用的第一条铁律。什么叫无状态?就是应用实例不保存任何业务数据在本地。每次请求都是独立的,换个实例处理也没问题。

我个人习惯把状态分为三类:

  • 会话状态:用户登录信息、购物车数据
  • 业务状态:订单处理进度、任务执行状态
  • 缓存状态:热点数据、计算结果

这三类状态,都不应该留在应用本地。我在项目中遇到过一个大坑:某团队把用户会话存在本地内存里,结果扩容时新实例没有旧会话,用户全被踢下线。嗯,那个晚上大家都没睡好。

核心原则:应用实例 = 纯计算单元。所有状态都交给中间件:Redis存会话,数据库存业务,分布式缓存存热点。

为什么一定要这么做?你想想看,如果应用有状态,Pod重启后数据就丢了。K8s的自动伸缩、滚动更新全都会出问题。说白了,有状态应用在云原生环境里就是个定时炸弹。

4.2 健康检查与自动摘除

健康检查是PaaS平台的"体检医生"。它分三个层次:

检查类型 探测内容 失败后果
存活检查 进程是否活着 重启Pod
就绪检查 能否处理请求 从Service摘除
启动检查 初始化是否完成 延迟其他检查

这里有个常见误区:很多人只配存活检查,不配就绪检查。结果呢?应用还在加载配置,流量就已经打进来了。我曾经见过一个案例,应用启动要30秒,但存活检查5秒就通过了,流量涌入后直接OOM。

我的建议:启动检查用HTTP接口,返回200才算就绪。存活检查用TCP,简单快速。就绪检查要模拟真实请求,比如查一下数据库连接池是否可用。

代码示例,K8s的健康检查配置:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - name: app
    image: my-app:1.0
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /health/ready
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
    startupProbe:
      httpGet:
        path: /health/startup
        port: 8080
      failureThreshold: 30
      periodSeconds: 10

注意看startupProbe的failureThreshold设成了30。为什么?因为应用启动可能很慢,尤其是加载大量数据时。我曾经遇到过启动要3分钟的应用,failureThreshold设小了,Pod一直在重启循环。

4.3 优雅上下线机制

优雅上下线,是应用层的"软着陆"。直接kill Pod会怎么样?正在处理的请求断了,数据库连接没释放,缓存数据没同步。嗯,后果很严重。

优雅下线的标准流程:

  1. 预停止:通知注册中心,本实例要下线了
  2. 停止接收新请求:健康检查返回非200
  3. 处理完存量请求:等待正在处理的请求完成
  4. 释放资源:关闭数据库连接、消息队列等
  5. 退出进程:调用exit(0)

这里有个关键点:K8s的preStop钩子。我习惯在preStop里做两件事:

  • 调用注册中心的注销接口
  • sleep 5秒,给负载均衡器时间同步

避坑指南:我曾经遇到过preStop里sleep时间太短,导致流量还在往旧Pod打。后来我改成sleep 10秒,并且配合readiness检查一起用。记住:preStop + readiness是黄金搭档。

优雅上线的流程类似:

  1. 启动应用:初始化所有组件
  2. 通过启动检查:告诉K8s我准备好了
  3. 延迟注册:等几秒再注册到服务中心
  4. 开始接收流量:就绪检查返回200

代码示例,Spring Boot的优雅关闭配置:

# application.yml
server:
  shutdown: graceful

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

这个配置告诉Spring Boot:收到关闭信号后,先停止接收新请求,然后等待最多30秒让存量请求处理完。如果30秒后还有请求没处理完,就强制关闭。

我个人习惯在应用里加一个/health/shutdown接口,手动触发优雅下线。这样在运维操作时,可以先调用这个接口,确认流量切走后,再操作Pod。你想想看,这比直接删Pod安全多了。

总结一下:无状态是基础,健康检查是保障,优雅上下线是细节。这三者配合好了,应用层的高可用才算真正落地。我见过太多线上事故,都是因为这三个点没做好。嗯,希望你能少踩这些坑。