IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 【mq】从零开始实现 mq-05-实现优雅停机 -> 正文阅读

[网络协议]【mq】从零开始实现 mq-05-实现优雅停机

前景回顾

【mq】从零开始实现 mq-01-生产者、消费者启动

【mq】从零开始实现 mq-02-如何实现生产者调用消费者?

【mq】从零开始实现 mq-03-引入 broker 中间人

【mq】从零开始实现 mq-04-启动检测与实现优化

【mq】从零开始实现 mq-05-实现优雅停机

为什么需要优雅关闭?

我记得多年前,那个时候 rpc 框架主流用的还是 dubbo,每次都是半夜还是上线,上线上完基本都是凌晨 2-3 点。

为什么要半夜上线呢?

因为这个时候一般业务流量最低。

还有就是上线发布,每次都要人工等待一段几分钟。

因为 rpc 调用入口已经关闭了,但是本身可能还没有处理完。

那么有没有方法可以让服务的关闭更加优雅,而不是人工等待呢?

05

实现思路

人工等待几分钟的方式一般可以解决问题,但是大部分情况是无用功,还比较浪费时间。

比较自然的一种方式是引入钩子函数。

当应用准备关闭时,首先判断是否存在处理中的请求,不存在则直接关闭;存在,则等待请求完成再关闭。

实现

生产者和消费者是类似的,我们以生产者为例。

启动实现的调整

@Override
public synchronized void run() {
    this.paramCheck();
    // 启动服务端
    log.info("MQ 生产者开始启动客户端 GROUP: {} brokerAddress: {}",
            groupName, brokerAddress);
    try {
        //0. 配置信息
        ProducerBrokerConfig config = ProducerBrokerConfig.newInstance()
                .groupName(groupName)
                .brokerAddress(brokerAddress)
                .check(check)
                .respTimeoutMills(respTimeoutMills)
                .invokeService(invokeService)
                .statusManager(statusManager);

        //1. 初始化
        this.producerBrokerService.initChannelFutureList(config);
        //2. 连接到服务端
        this.producerBrokerService.registerToBroker();

        //3. 标识为可用
        statusManager.status(true);

        //4. 添加钩子函数
        final DefaultShutdownHook rpcShutdownHook = new DefaultShutdownHook();
        rpcShutdownHook.setStatusManager(statusManager);
        rpcShutdownHook.setInvokeService(invokeService);
        rpcShutdownHook.setWaitMillsForRemainRequest(waitMillsForRemainRequest);
        rpcShutdownHook.setDestroyable(this.producerBrokerService);
        ShutdownHooks.rpcShutdownHook(rpcShutdownHook);
        log.info("MQ 生产者启动完成");
    } catch (Exception e) {
        log.error("MQ 生产者启动遇到异常", e);
        throw new MqException(ProducerRespCode.RPC_INIT_FAILED);
    }
}

状态管理类

这里我们引入 statusManager 管理整体的状态。

默认的如下:

public class StatusManager implements IStatusManager {

    private boolean status;

    @Override
    public boolean status() {
        return this.status;
    }

    @Override
    public IStatusManager status(boolean status) {
        this.status = status;

        return this;
    }

}

就是对一个是否可用的状态进行维护,然后在 channel 获取等地方便于判断当前服务的状态。

钩子函数

DefaultShutdownHook 实现如下:

public class DefaultShutdownHook extends AbstractShutdownHook {

    /**
     * 调用管理类
     * @since 0.0.5
     */
    private IInvokeService invokeService;

    /**
     * 销毁管理类
     * @since 0.0.5
     */
    private Destroyable destroyable;

    /**
     * 状态管理类
     * @since 0.0.5
     */
    private IStatusManager statusManager;

    /**
     * 为剩余的请求等待时间
     * @since 0.0.5
     */
    private long waitMillsForRemainRequest = 60 * 1000;

    //get & set

    /**
     * (1)设置 status 状态为等待关闭
     * (2)查看是否 {@link IInvokeService#remainsRequest()} 是否包含请求
     * (3)超时检测-可以不添加,如果难以关闭成功,直接强制关闭即可。
     * (4)关闭所有线程池资源信息
     * (5)设置状态为成功关闭
     */
    @Override
    protected void doHook() {
        statusManager.status(false);
        // 设置状态为等待关闭
        logger.info("[Shutdown] set status to wait for shutdown.");

        // 循环等待当前执行的请求执行完成
        long startMills = System.currentTimeMillis();
        while (invokeService.remainsRequest()) {
            long currentMills = System.currentTimeMillis();
            long costMills = currentMills - startMills;
            if(costMills >= waitMillsForRemainRequest) {
                logger.warn("[Shutdown] still remains request, but timeout, break.");
                break;
            }

            logger.debug("[Shutdown] still remains request, wait for a while.");
            DateUtil.sleep(10);
        }

        // 销毁
        destroyable.destroyAll();

        // 设置状态为关闭成功
        statusManager.status(false);
        logger.info("[Shutdown] set status to shutdown success.");
    }

}

(1)进行关闭前,首先判断通过 invokeService.remainsRequest() 判断是否有未处理完的消息,有则进行等待。

(2)当然,我们还需要考虑网络消息丢失的场景,不可能一直等待。

所以引入了超时中断,最大等待时间也是可以自行定义的。

if(costMills >= waitMillsForRemainRequest) {
    logger.warn("[Shutdown] still remains request, but timeout, break.");
    break;
}

(3)关闭之后

将 status 设置为 false,标识当前服务不可用。

小结

随着 rpc 技术的成熟,优雅关闭已经成为一个很基本的功能点。

一个小小的改动,可以节约生产发布时间,早点下班陪陪家人。

希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。

我是老马,期待与你的下次重逢。

开源地址

The message queue in java.(java 简易版本 mq 实现) https://github.com/houbb/mq

拓展阅读

rpc-从零开始实现 rpc https://github.com/houbb/rpc

在这里插入图片描述

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-05-07 11:28:02  更:2022-05-07 11:28:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:40:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码