2026/1/16 10:48:48
网站建设
项目流程
做个网站 一般费用,国内html5网站建设,网站制作费计入什么科目,外贸中东哪些产品好卖前言
前两次分享#xff0c;我们已经介绍过了k8s节点关机的流程和优雅关机要实现的流程#xff0c;今天我们来一起来看下具体的代码实现#xff0c;主要内容如下#xff1a;
SIGTERM监听逻辑预关机逻辑各个组件的关机逻辑和监控逻辑
实现过程
前置要点
前面我们说了我们已经介绍过了k8s节点关机的流程和优雅关机要实现的流程今天我们来一起来看下具体的代码实现主要内容如下SIGTERM监听逻辑预关机逻辑各个组件的关机逻辑和监控逻辑实现过程前置要点前面我们说了本项目实际是个spring-boot-starter所以你要先创建spring-boot-starter的项目这里就不赘述具体过程了具体可以参考之前的分享 [[编写你的第一个spring-boot-starter]]这里提几个需要注意的点pom依赖这块有两个标签要注意scope和optional组合使用可以确保依赖的灵活性同时避免依赖冲突scope作用域依赖的作用范围控制依赖的使用范围这里的provided表示编译和测试时有效运行时由运行环境提供也就是不会打进包里。这样可以确保我们的starter引入项目后不会影响原项目的pom依赖关系带来未知风险。常见的作用域还有compile默认编译、测试、运行都有效会打包provided编译和测试时有效运行时由容器提供runtime运行时需要编译时不需要test仅测试时使用system类似provided但需要显式指定本地jar路径optional可选依赖当设置为true时不会传递依赖即使其他项目依赖本项目也不会继承这个可选依赖需要显式声明使用者必须主动声明才能使用场景可选功能、有冲突的依赖、特定环境才需要的依赖当然这两个pom标签通常是配合spring-boot的条件配置来使用或者你可以确保代码运行时一定包含对应的依赖否则会导致运行时异常。spring.factories这个文件是starter的核心当我们的项目被引入时springboot会根据我们的spring.factories初始化我们的starter并根据我们的配置类完成加载和配置。这个项目通常位于src/main/resources/META-INF/spring.factories下配置内容如下# src/main/resources/META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration\ io.syske.springboot.starter.config.GracefulShutdownAutoConfiguration,\ io.syske.springboot.starter.compent.SpringContextUtilConfig配置的内容就是我们的配置类有多个配置类用逗号分隔开就行。因为配置类本身是支持import的所以我们通常只需要配置一个主配置类即可其他的直接Import即可我这里配置两个是由于另一个类不能条件注入所以单独配置了具体大家根据自己实际情况看。条件配置这里简单说下本次用到的几个条件注解ConditionalOnProperty这个注解的作用是根据某个配置文件判断类或者方法支付要执行通常和Configuration或者Bean组合使用。在我们上面的截图中实际就是通过判断优雅关机开关是否开启进行条件配置如果条件缺失我们给了默认值true。ConditionalOnMissingBean当缺少某个类的bean时配置类似单例模式ConditionalOnClass当有指定的class的时候触发配置。这个就是我前面说的要和pom那两个标签组合的条件配置。所有的关机组件逻辑都要加上这个判断确保核心类存在也就是依赖存在时才配置对应组件的关机逻辑。ConditionalOnWebApplication这个注解是加在Tomcat的关机组件上的确保它是WebApplicationSIGTERM逻辑核心实现其实就是创建一个bean并在bean初始化逻辑中定义一个处理TERM信号的Signal并在Signal的handle逻辑中加入我们的预关机逻辑即可try { Signal signal new Signal(TERM); Signal.handle(signal, sig - { logger.info(Received SIGTERM signal from K8s, starting graceful shutdown); // 处理关机信号 handleShutdownSignal(); }); logger.info(SIGTERM handler registered successfully); } catch (Exception e) { logger.warn(Failed to register SIGTERM handler: {}, using shutdown hook as fallback, e.getMessage()); setupShutdownHook(); }这里我就直接放截图了处理关机信号逻辑然后就是预关机逻辑这里简单说下:服务标记为不健康这里我自定义了一个healthIndicator实际没太大作用因为我们的服务健康检查并不依赖这个如果你们的服务健康检查依赖的是springboot的健康检查机制那实现自定义健康检查则是需要考虑的。等待负载均衡感知这个实际和网关有关系在本项目中非必须逻辑中只是睡了5秒。执行优雅关机这里就是根据我们自定义的优先级依次关机各个组件。下面我们逐步介绍各组件的实现逻辑。Tomcat监控逻辑tomcat优雅关机要实现TomcatConnectorCustomizer接口并实现customize方法主要是为了关机时能获取到连接器同时设置Executor实现统计活跃请求数的逻辑计数执行器逻辑组件核心逻辑核心逻辑就三步// 1. 暂停接收新请求 pauseConnector(); // 2. 等待活跃请求完成 waitForActiveRequests(); // 3. 关闭连接器 stopConnector();停止接受新请求实际就是调用连接器的暂停方法等待活跃请求完成实际就是等待线程池关闭关闭线程池实际就是调用shutdown方法超时强制关闭实际就是超时后调用shutdownNow然后再等待线程池考虑了org.apache.tomcat.util.threads.ThreadPoolExecutor和java.util.concurrent.ThreadPoolExecutor是分别处理实际两者是继承关系其实可以省略第一个停止连接器也很简单就是调用stop方法不过关闭前需要判断下避免重复关闭springboot本身的关机逻辑也会触发关闭RPC提供者监控逻辑监控这里的逻辑是通过sofa-rpc的Filter来实现的统计请求数量服务注册这里的注册实际是个辅助逻辑确保没有正常注册的提供者在调用过程中注册。真正的服务提供中注册逻辑是基于ApplicationRunner实现的关于拦截器的注册有几个点拦截器只能通过SPI方式注入且要正确配置AutoActive和Extension需要注意的是Extension配置的名称要与com.alipay.sofa.rpc.filter.filter文件中配置的名称一致否则不生效com.alipay.sofa.rpc.filter.filter文件必须正确配置配置的key是Extension配置的值value是拦截器的全类名注意这里还有个比较坑的点sofa-boot的拦截器不能直接使用springboot的bean所以需要借助SpringContextUtil这也是我的spring.factories里面还配置了SpringContextUtilConfig的原因。因为拦截器没法实现条件配置所以SpringContextUtil也不能条件配置。目标发现逻辑Runer核心逻辑如下这里搞了好久一直没有搞定最后是在sofa-boot的代码中找到的然后直接解决了提供者发现问题组件核心逻辑组件核心逻辑就三步标记为关闭状态实际这一步并没有拦截请求取消服务者注册这一步直接调用unExport方法等待处理中的请求完成判断活跃请求的逻辑来自监控逻辑ActiveMQ监控逻辑activeMQ的监控逻辑实际是通过DefaultMessageListenerContainer直接获取的所以监控逻辑本身没什么特殊的目标发现逻辑activeMQ的服务发现逻辑也比较简单核心其实就是将JmsListenerEndpointRegistry注入我们的关机组件关机组件要通过这个对象获取所有的DefaultMessageListenerContainer消费者容器组件核心逻辑核心逻辑如下发现所有监听者并打印容器状态停止容器调用stop方法等待处理中的消息处理完成RocketMQ监控逻辑监控逻辑是根据容器是否是running状态判断的比较简单目标发现逻辑组件的发现逻辑是通过spring的后置处理器来完成的将所有的DefaultRocketMQListenerContainer收集起来。组件核心逻辑核心逻辑如下停止监听器逻辑这里是调用stop方法进行停止等待处理中的容器停止这里直接用的是容器状态判断的因为消费中的消息数量需要调用rpcketMQ的服务端接口实际意义不大而且要依赖外部服务所以直接判断容器状态。线程池监控逻辑没什么监控逻辑实际就是直接打印线程池的活跃的线程数目标发现逻辑发现逻辑也是基于spring的后置处理器完成的根据不通的线程池注册到不通的集合中组件核心逻辑核心逻辑不同的线程池挨个关闭关闭逻辑都差不多线程池关机调用shutdown方法等待完成调用awaitTermination超时后调用shutdownNowForkJoinPool比较特殊只能等待RPC客户端客户端也叫运行时环境这个销毁就比较简单了没有监控直接通过反射调用RpcRuntimeContext的destroy方法即可。至此我们的k8s环境下的spring-boot服务优雅关机方案就完成了。下面我们简单总结下总结我们用了三篇文章和大家聊清楚了 K8s 环境下 Spring Boot 服务“优雅关机”这回事儿第一篇先弄懂 K8s 是怎么关机的带大家梳理了 K8s 节点和 Pod 关闭的基本流程。了解了它的“套路”咱们自己设计方案时才能心里有底。第二篇咱们的方案长啥样知道了 K8s 的流程那我们的 Spring Boot 服务该怎么配合着“优雅退场”呢这一篇就讲了咱们的整体设计思路关机要分几步、每一步要注意啥都给大家掰扯明白了。第三篇动手把代码写出来光说不练假把式。最后一篇咱们直接上代码手把手讲解了怎么监控组件、怎么发现需要关闭的目标再把整个关闭流程像拼积木一样组合起来。让大家能从代码层面真正搞懂怎么写。当然了咱们现在这个方案还不是“终极完美版”。除了之前提到的比如Tomcat和RPC提供者可以同时关这些优化点其实还有一些地方可以做得更好。比如说“重复关闭”的问题虽然代码里加了判断不会因为重复调用而报错但Spring Boot底层的关机钩子 (ShutdownHook) 逻辑其实还在这里未来还有更优雅的解法。我们特意把这个点提出来其实就是想“抛砖引玉”。优雅关机这件事细节很多也欢迎大家一起来思考、讨论看看还有哪些地方可以优化得更好。