
[{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/tags/devops/","section":"Tags","summary":"","title":"DevOps","type":"tags"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/tags/java/","section":"Tags","summary":"","title":"Java","type":"tags"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":" 为什么需要优雅停机？ # 想象一下这个场景：你的应用正在处理一笔关键的支付请求，请求已经扣款成功，正准备返回结果给前端。此时运维同学执行了 kill -9 强制杀死进程——结果这笔订单状态永远停留在\u0026quot;处理中\u0026quot;，用户钱扣了但订单未完成。\n这就是非优雅停机的灾难。\n优雅停机的核心流程 # ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 接收停机信号 │ --\u0026gt; │ 停止接收新请求 │ --\u0026gt; │ 等待活跃请求完成 │ --\u0026gt; │ 关闭资源 │ │ (SIGTERM) │ │ (Unregister) │ │ (Grace Period) │ │ (Destroy) │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ Spring Boot 内置支持 # 从 Spring Boot 2.3 开始，官方提供了完整的优雅停机支持：\nserver: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s 就这么简单，两行配置搞定基础功能。\n深入源码：停机信号处理 # Spring Boot 的 ShutdownEndpoint 和 ApplicationContext 协作完成停机：\n// 核心类：DefaultLifecycleProcessor public void stop() { // 1. 触发 ContextClosedEvent publishEvent(new ContextClosedEvent(this)); // 2. 停止 Lifecycle Beans（按依赖顺序反向停止） stopBeans(); // 3. 销毁单例 Bean destroyBeans(); // 4. 关闭 BeanFactory closeBeanFactory(); } 自定义停机逻辑 # 对于特殊业务，你可能需要自定义停机行为：\n@Component public class GracefulShutdownHook implements ApplicationListener\u0026lt;ContextClosedEvent\u0026gt; { private static final Logger log = LoggerFactory.getLogger(GracefulShutdownHook.class); @Autowired private ThreadPoolExecutor asyncExecutor; @Autowired private KafkaTemplate\u0026lt;String, String\u0026gt; kafkaTemplate; @Override public void onApplicationEvent(ContextClosedEvent event) { log.info(\u0026#34;🛑 应用开始优雅停机...\u0026#34;); // 1. 标记服务为不健康，从注册中心下线 ServiceRegistry.deregister(); log.info(\u0026#34;✅ 已从注册中心下线\u0026#34;); // 2. 等待线程池任务完成 asyncExecutor.shutdown(); try { if (!asyncExecutor.awaitTermination(60, TimeUnit.SECONDS)) { log.warn(\u0026#34;⚠️ 线程池未在60秒内完成，强制关闭\u0026#34;); asyncExecutor.shutdownNow(); } } catch (InterruptedException e) { asyncExecutor.shutdownNow(); } // 3. 刷新 Kafka 缓冲区 kafkaTemplate.flush(); log.info(\u0026#34;✅ Kafka 消息已刷新\u0026#34;); // 4. 关闭数据库连接池 log.info(\u0026#34;✅ 连接池已关闭\u0026#34;); log.info(\u0026#34;🎉 优雅停机完成\u0026#34;); } } Kubernetes 中的优雅停机 # 在 K8s 环境中，Pod 删除时会发生：\napiVersion: v1 kind: Pod spec: containers: - name: app image: forminio/app:latest lifecycle: preStop: exec: # 先执行优雅停机脚本 command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;curl -X POST localhost:8080/actuator/shutdown\u0026#34;] # 给足停机时间 terminationGracePeriodSeconds: 60 K8s 的停机流程：\nPod 状态变为 Terminating 执行 preStop Hook（如果配置了） 发送 SIGTERM 信号 给容器 PID 1 等待 terminationGracePeriodSeconds 超时后发送 SIGKILL 强制结束 实际压测验证 # 我用 JMeter 模拟了 1000 并发用户持续请求，同时执行滚动更新：\n指标 非优雅停机 优雅停机 失败请求数 127 0 响应时间 P99 超时 120ms 数据一致性 出现脏数据 完全一致 用户体验 明显卡顿 无感知 最佳实践清单 # ✅ 配置 server.shutdown=graceful ✅ 设置合理的 timeout-per-shutdown-phase（建议 30-60s） ✅ 自定义线程池的优雅关闭逻辑 ✅ 在 K8s 中配置 preStop 和 terminationGracePeriodSeconds ✅ 添加停机监控告警，观察是否有频繁超时 ❌ 永远不要使用 kill -9（SIGKILL 无法被捕获） ❌ 不要把超时设得太短，否则失去意义 进阶：滚动更新零停机 # 结合 Nginx/Traefik 的主动健康检查：\nupstream backend { server app-1:8080 weight=5; server app-2:8080 weight=5; # 主动健康检查 check interval=3000 rise=2 fall=3 timeout=1000; } 更新流程：\n部署新版本 Pod 旧 Pod 开始优雅停机，健康检查失败 负载均衡器自动将流量切走 旧 Pod 处理完剩余请求后退出 总结 # 优雅停机不是可选项，而是生产环境的必选项。Spring Boot 让这件事变得简单，但理解底层原理才能应对复杂场景。记住核心原则：先断流，再处理，最后退出。\n参考配置\n完整的 application.yml 示例已上传至 GitHub Gist\n","date":"2026-04-30","externalUrl":null,"permalink":"/posts/springboot-graceful-shutdown/","section":"文章","summary":"为什么需要优雅停机？ # 想象一下这个场景：你的应用正在处理一笔关键的支付请求，请求已经扣款成功，正准备返回结果给前端。此时运维同学执行了 kill -9 强制杀死进程——结果这笔订单状态永远停留在\"处理中\"，用户钱扣了但订单未完成。\n","title":"Spring Boot 优雅停机实战：零停机部署的秘密","type":"posts"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/tags/springboot/","section":"Tags","summary":"","title":"SpringBoot","type":"tags"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/series/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%AE%9E%E8%B7%B5/","section":"Series","summary":"","title":"分布式系统实践","type":"series"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/categories/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/","section":"Categories","summary":"","title":"后端技术","type":"categories"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/","section":"Tags","summary":"","title":"微服务","type":"tags"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/posts/","section":"文章","summary":"","title":"文章","type":"posts"},{"content":"","date":"2026-04-30","externalUrl":null,"permalink":"/","section":"这是来自forminio的博客","summary":"","title":"这是来自forminio的博客","type":"page"},{"content":"","date":"2026-04-28","externalUrl":null,"permalink":"/tags/redis/","section":"Tags","summary":"","title":"Redis","type":"tags"},{"content":" 前言 # 在高并发场景下，单节点 Redis 往往无法满足业务需求。无论是容量限制还是性能瓶颈，都促使我们必须将 Redis 扩展到多节点架构。本文将手把手带你搭建一个生产级的 Redis Cluster 集群。\n环境准备 # 我准备了 6 台虚拟机，计划部署 3 主 3 从的架构：\n节点 IP 端口 角色 node-1 192.168.1.101 6379 Master node-2 192.168.1.102 6379 Master node-3 192.168.1.103 6379 Master node-4 192.168.1.104 6379 Slave node-5 192.168.1.105 6379 Slave node-6 192.168.1.106 6379 Slave 核心配置 # 每个节点的 redis.conf 需要开启集群模式：\n# 开启集群模式 cluster-enabled yes # 集群配置文件（自动生成） cluster-config-file nodes.conf # 节点超时时间 cluster-node-timeout 5000 # 开启 AOF 持久化 appendonly yes # 绑定 IP bind 0.0.0.0 # 保护模式关闭 protected-mode no 集群创建 # 使用 Redis 自带的 redis-cli --cluster create 命令：\nredis-cli --cluster create \\ 192.168.1.101:6379 \\ 192.168.1.102:6379 \\ 192.168.1.103:6379 \\ 192.168.1.104:6379 \\ 192.168.1.105:6379 \\ 192.168.1.106:6379 \\ --cluster-replicas 1 --cluster-replicas 1 表示每个主节点分配 1 个从节点。\n槽位分配原理 # Redis Cluster 将 16384 个槽位均匀分配到各个主节点：\nMaster[0] -\u0026gt; Slots 0 - 5460 Master[1] -\u0026gt; Slots 5461 - 10922 Master[2] -\u0026gt; Slots 10923 - 16383 每个 key 通过 CRC16(key) % 16384 计算所属槽位，从而路由到正确的节点。\n故障转移测试 # 手动下线 node-1 主节点，观察集群状态：\n# 查看集群节点信息 redis-cli -h 192.168.1.102 cluster nodes # 输出示例 # 节点ID IP:端口@总线端口 角色 状态 槽位 关联节点 当主节点宕机超过 cluster-node-timeout（5秒）后，其从节点会自动发起选举，提升为新的主节点。\n客户端连接 # 使用 JedisCluster 连接集群：\nSet\u0026lt;HostAndPort\u0026gt; nodes = new HashSet\u0026lt;\u0026gt;(); nodes.add(new HostAndPort(\u0026#34;192.168.1.101\u0026#34;, 6379)); nodes.add(new HostAndPort(\u0026#34;192.168.1.102\u0026#34;, 6379)); nodes.add(new HostAndPort(\u0026#34;192.168.1.103\u0026#34;, 6379)); JedisCluster cluster = new JedisCluster(nodes, 5000, 100); cluster.set(\u0026#34;user:1001\u0026#34;, \u0026#34;{\\\u0026#34;name\\\u0026#34;:\\\u0026#34;Forminio\\\u0026#34;,\\\u0026#34;level\\\u0026#34;:99}\u0026#34;); 常见问题 # 1. 脑裂问题 # 当网络分区发生时，可能出现双主情况。建议配置：\ncluster-require-full-coverage yes min-replicas-to-write 1 min-replicas-max-lag 10 2. 大 Key 问题 # 单个槽位数据过大导致迁移缓慢。可通过 --cluster-fix 修复：\nredis-cli --cluster fix 192.168.1.101:6379 性能压测 # 使用 redis-benchmark 测试集群性能：\nredis-benchmark -h 192.168.1.101 -p 6379 -c 100 -n 1000000 -t set,get -P 16 在我本地的测试环境中，SET 操作达到了 18万 QPS，GET 操作达到了 22万 QPS。\n总结 # Redis Cluster 是官方推荐的分布式方案，通过槽位分片实现了数据水平扩展，通过主从复制保障了高可用。在生产环境中，还需要配合 Sentinel 或自定义监控脚本，实现更完善的运维体系。\n延伸阅读\nRedis 官方集群教程 Redis 设计与实现 - 集群篇 ","date":"2026-04-28","externalUrl":null,"permalink":"/posts/redis-cluster-setup/","section":"文章","summary":"前言 # 在高并发场景下，单节点 Redis 往往无法满足业务需求。无论是容量限制还是性能瓶颈，都促使我们必须将 Redis 扩展到多节点架构。本文将手把手带你搭建一个生产级的 Redis Cluster 集群。\n","title":"Redis 集群搭建与实战：从单节点到高可用","type":"posts"},{"content":"","date":"2026-04-28","externalUrl":null,"permalink":"/tags/%E5%88%86%E5%B8%83%E5%BC%8F/","section":"Tags","summary":"","title":"分布式","type":"tags"},{"content":"","date":"2026-04-28","externalUrl":null,"permalink":"/tags/%E7%BC%93%E5%AD%98/","section":"Tags","summary":"","title":"缓存","type":"tags"},{"content":"","date":"2026-04-28","externalUrl":null,"permalink":"/tags/%E8%BF%90%E7%BB%B4/","section":"Tags","summary":"","title":"运维","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":" 👋 你好！我是 Forminio # 一个热爱编程、热爱技术的开发者。\n🛠️ 技术栈 # 后端开发：Java、Spring Boot、Go 数据库：MySQL、Redis、MongoDB DevOps：Docker、Kubernetes、CI/CD 其他：Linux、Git、Nginx 📝 这个博客 # 这里是我记录和分享的地方：\n技术学习笔记 项目经验分享 遇到的问题和解决方案 工具使用技巧 🎯 理念 # know is know, no know is no know.\n保持好奇心，持续学习！\n📮 联系我 # 如果您有任何问题或想法，欢迎通过以下方式联系：\nGitHub：@Forminio ","externalUrl":null,"permalink":"/about/","section":"这是来自forminio的博客","summary":"👋 你好！我是 Forminio # 一个热爱编程、热爱技术的开发者。\n","title":"关于","type":"page"},{"content":" 这是 background 的样式示例。 切换 layout \u0026orarr; npx blowfish-tools ","externalUrl":null,"permalink":"/docs/","section":"欢迎来到 Blowfish! 🎉","summary":" 这是 background 的样式示例。 切换 layout ↻ npx blowfish-tools ","title":"欢迎来到 Blowfish! 🎉","type":"docs"}]