程序如何优雅停机

背景

	项目里面包含了第三方支付功能。在用户提现过程中,应用程序重启。此时的提现操作的响应,应用程序无法拿到,导致进行用户错误更新过程显示了不该显示的内容。

专题理解

	当我们以命令或脚本的方式停止服务时,一般使用了 kill -9命令把服务进程杀掉,这个命令是非常暴力的,类似于直接按了这个服务的电源,显然这种方式对进行中的服务是很不友善的,当在停机时,正在进行RPC调用、执行批处理、缓存入库等操作,会造成不可挽回的数据损失,增加后期维护成本。

	达到要求:
	让服务在收到停机指令时,从容的拒绝新请求的进入,并执行完当前任务,然后关闭服务
	
	优雅停机达到的要求:
	首先要确保不会再有新的请求进来,所以需要设置一个流量挡板
	保证正常处理已进来的请求线程,可以通过计数方式记录项目中的请求数量
	如果涉及到注册中心,则需要在第一步结束后注销注册中心
	停止项目中的定时任务
	停止线程池
	关闭其他需要关闭资源等等等
	用到的命令:
	当应用接收到kill的信号(15 终止号令)时,会停止接收新的请求,并等待活跃的请求完成后,关闭服务,

参考案例(springboot+tomcat)

	方式一:
	spring-boot-starter-actuator 模块提供了一个 restful 接口 /actuator/shutdown (POST) 用于优雅停机。一般需要限制内网关IP访问权限,而且最好使用Secrety进行登录验证;
	#### 使用endpoints方式需要在配置文件中添加如下配置 
 
server.shutdown=graceful ## 开启优雅停机
spring.lifecycle.timeout-per-shutdown-phase=20s ##设置优雅停机关闭流量挡板后最多等待时间

management.server.port=9090  ## 指定endpoints的访问端口,最好不与server.port一致
management.endpoint.shutdown.enabled=true ## 开启/actuator/shutdown路由
management.endpoints.web.exposure.include=shutdown  ## 暴露/actuator/shutdown路由

	方式二:
	使用 kill -15 pid 发送停机通知进行优雅停机;

	kill -9 pid 可以理解为操作系统从内核级别强行杀死某个进程,直接模拟了一次系统宕机,系统断电,这对于应用来说太不友好.kill -15 pid 则可以理解为发送一个通知,告知应用主动关闭。

实战经验

代码层面

	package com.tianzao.novel.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.ProtocolHandler;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * tomcat 停机
 * @author dmm
 * @since 2022/5/27 14:22
 */
@Slf4j
@Component
public class GracefulShutdownTomcat implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {

    private volatile Connector connector;

    private final int waitTime = 5;

    @Override
    public void customize(Connector connector) {
        this.connector = connector;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
        if (this.connector == null) {
            return;
        }
        log.info("GracefulShutdownTomcat close start waitTime {} curTime {}", waitTime, LocalDateTime.now());
        this.connector.pause();
        log.info("GracefulShutdownTomcat pause company curTime {}", LocalDateTime.now());
        ProtocolHandler protocolHandler = this.connector.getProtocolHandler();
        // 关闭socket, 拒绝接受新的请求
        protocolHandler.closeServerSocketGraceful();
        log.info("GracefulShutdownTomcat Socket Close company curTime {}", LocalDateTime.now());
        // 关闭线程池
        Executor executor = this.connector.getProtocolHandler().getExecutor();
        if (executor instanceof ThreadPoolExecutor) {
            try {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                threadPoolExecutor.shutdown();
                log.info("GracefulShutdownTomcat Executor shutdown company curTime {}", LocalDateTime.now());
                if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
                    log.info("Tomcat thread pool did not shut down gracefully within " + waitTime + " seconds. Proceeding with forceful shutdown");
                }
                log.info("GracefulShutdownTomcat Executor shutdown waitTime over curTime {}", LocalDateTime.now());
            } catch (InterruptedException ex) {
                log.info("GracefulShutdownTomcat close Exception ", ex);
                Thread.currentThread().interrupt();
            }
        } else {
            log.info("GracefulShutdownTomcat Executor is not ThreadPoolExecutor curTime {}", LocalDateTime.now());
        }
    }

}

启动脚本


kill -15 $PID

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值