你的停机真的优雅么?第二弹来袭 | 京东云技术团队

1. 前言

之前总结了一篇基于现有业务线在停机重启时会产生RPC和MQ调用强杀导致业务数据不一致文章,文中通过优雅停机改造对RPC服务进行反注册和MQ进行暂停消费,进而可以解决在停机时强制kill掉RPC线程或者MQ线程导致数据不一致现象,具体的原文大家感兴趣可以去看一下。Ok前情提要结束,最近在一些核心应用上线重启的时候又出现了业务订单数据不一致的情况,通过排查定位发现还是因为停机不够优雅,罪魁祸首是定时任务执行时间过长,在上线重启的过程中定时任务没有执行完成而被强行kill,详细分析及处理方案如下。

2. 问题简述

为了便于快速理解上线停机时导致业务数据不一致的的具体环节,简要的绘制一个业务流程图如下,其中以资金前置应用为例,定时任务运行在资金前置应用上,当涉及到资金前置应用的重启或者停机时,之前的优雅停机改造是会先把RPC反注册和MQ暂停消费,然后在预留60s的时间以供存活的线程执行完毕。其实这么做确实还是有缺陷的,就比如60s也不能确保所有的存活线程执行完毕,文中前言中所提到的问题就是这个,由于定时任务执行时间大于60s,所以就会存在在定时任务尚未执行完成的情况下,强行把该定时任务给销毁掉了,恰巧两个数据表的更新还不在一个位置,导致停机完成的第60s正好在其中的一个表更新完成和发送MQ去更新另一个数据表之间,这样就会存在两个表的数据不一致

现有中断问题流程图

3. 解决方案

通过对上述问题的调研及分析,目前有以下几种解决方案。

3.1 拆分子任务

当然这个方案治标不治本,降低定时任务的执行时间只能说会降低数据不一致产生的频次,即使将定时任务优化到执行完成进需要2s,上线的时候只要不刻意避开定时任务的执行的话,还是会存在在倒计时结束的第60s时正好撞上定时任务正在执行的场景。不过还是借此机会排查了一下定时任务耗时的原因,其主要原因如下。其中以目前耗时比较长的定时任务(还款结果查回)为例。

定时任务1

通过梳理还款结果查询定时任务发现,主要的耗时点有两个:数据量大并发量小。其中数据量大这个只能通过优化索引结构来降低耗时,并发量小可以通过拆分子任务或者修改代码批量发送数据到业务方来提速。

定时任务2

同理,该定时任务制约因素同上。通过调研EasyJob平台发现提供了拆分子任务功能,可以通过平台拆分来降低耗时,并且不对现有的业务代码进行修改。emm,然而通过跟下游业务方沟通发现,受业务方并发tps制约,不建议通过此方式来提速。那降低耗时方案不是最佳的,可不可以通过设置一个全局变量呢?通过共享该变量的值,这样可以在停机的时候判断是否还有定时任务在处理。因此,方案二应运而生。

3.2 共享信号量机制

既然定时任务无论执行多少时间都可能会出现这个停机不优雅的问题,那么我们可以尝试增加一个全局变量,其主要作用是在定时任务中和优雅停机任务中共享达到优雅停机的目的。

@Component
@Slf4j
public class ShutDownHook {

    /**
     * 定时任务停止执行标识
     */
    public static volatile int interrupt = 0;

    @PreDestroy
    public void destroyHook() {
         try {
              JobService jobService = schedulerFactoryBean.getObject();
              if (null != jobService) {
                boolean stop = jobService.stop();
                    if (stop) {
                        log.info("停止EasyJob完成。");
                    } else {
                        log.info("停止EasyJob失败");
                    }
                } else {
                    log.info("停止EasyJob没执行");
                }
            } catch (Exception e) {
                log.error("停止EasyJob异常", e);
            }
          interrupt = 1;  //Easyjob停止后修改标识
    }
}



@Component
public class xxxTask implements ScheduleFlowTask {
    @Override
    public TaskResult doTask(ScheduleContext scheduleContext) throws Exception {
        transactionNoList.forEach(transactionNo ->{
            if (0 != ShutDownHook.interrupt) {
                throw new RuntimeException("停机中断");
            }
            // 业务逻辑
            ...
        });
        return TaskResult.SUCCESS_BLANK;
    }
}



通过interrupt标识作为共享变量,这块就涉及到一个问题,如果多个线程同时修改这个变量会不会导致数据错误呢?为了避免出现和这个问题,在优雅停机脚本中我们使用volatile关键字来修饰该变量,通过先初始化停机标识interrupt,利用volatile关键字中的可见性(即:一个线程对其修改立即对其余线程可见)可以在停机脚本反注册掉定时任务服务后,修改该停机标识为1,然后在定时任务执行业务逻辑数据更新时,每次执行前判断停机标识是否为1(即是否已开始进行EasyJob服务反注册),假如停机脚本已经开始EasyJob服务反注册,则不继续进行后续业务逻辑操作,直接抛出运行时异常RuntimeException

那么此时还有一个问题,定时任务在上线期间中断后,能不能在我们无感知的情况下进行重做呢?难不成我们每次都需要去根据异常来确认在上线期间是否存在定时任务中断,如果有的话难不成我们还要手动再次执行么?亦或者是等到定时任务的下一次自动执行的时间点执行?那么有没有一种方案可以自动立即重做呢?

自动失败重做机制

通过调研EasyJob定时任务平台发现提供了自动重做机制,通常我们一般部署的服务都是多台机器,即当我们上线的这台机器上面的定时任务执行中断后,可以通过配置自动重做机制自动选择现有的存活的机器执行,其中自动重做里面中设置的参数如下:可以通过调整参数来控制第一次重做延迟时间、后续的重做时间间隔、以及最多重做次数。为什么要配置重做次数呢?一般机器上线是陆续进行的,极端情况下,定时任务执行时间完全覆盖了所有机器的上线时间,那么定时任务就会分别在每台机器上面中断一次,最终还是回归到最开始的那台机器上面继续执行(此刻该机器已上线完成)。所以针对定时任务时间执行比较长的,需要根据实际机器设置合理的重做次数。

参数详情

3.3 拆分子任务&共享信号量

另一个方案其实就是融合了方案一(拆分子任务)和方案二(共享信号量),这样的好处就是能够解决在3.2节末尾提到的上线期间极端情况下,定时任务执行时间过长而在没台机器上都中断重做场景。但是受限场景和方案一也是一致的,目前受下游业务方能够接受的最大并发限制而暂缓实施。

3.4 包装成事务

因为涉及到更新两个数据表操作,要想避免两个数据表数据不一致,最常用的做法就是把更新两个数据表的操作封装成一个事务操作,这样的话就无需考虑定时任务的执行时间,由于事物的原子性,肯定不会出现两个数据表的数据不一致情况。当然这个方案的缺点就是需要重构现有的业务逻辑,需要把MQ的下游重构,把两个数据表的更新位置重构一下。这块对现有的业务逻辑代码有侵入,并且需要重构现有的业务流程,目前不太建议这么修改。

4. 总结

ok,通过这次优化应该能够使得现有的应用在重启或者是上线停机时,能够避免定时任务中断而导致的数据更新不一致场景。以上是优雅停机第二弹内容。以上。

作者:京东科技 宋慧超

来源:京东云开发者社区 转载请注明来源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/117711.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务号能升级成订阅号吗

服务号和订阅号有什么区别?服务号转为订阅号有哪些作用?一、文章推送的篇数不同服务号在文章的推送篇数上是有所限制的(每月推4次)订阅号则每天可推送一篇文章。二、定义不同服务号主要是为关注用户提供服务使用的;订阅…

SQL数据库使用方法

首先打开sqlite3.exe所在文件夹,如图1 图1 在文件夹路径中将路径改为cmd,如图2所示 图2 在弹出的cmd窗口中输入如图3所示。 图3 sqlite3 tichiceliang.db 其中tichiceliang是数据库名称。然后按enter,再在cmd中输入.table,可以看到文件夹目…

一种使用wireshark快速分析抓包文件amr音频流的思路方法

解决方案: 1. 使用wireshark过滤amr,并导出原始数据文件; 2.使用ue的二进制编辑模式,编辑该文件,添加amr头,6个字节数据“#!AMR”,字节数据为 23 21 41 4D 52 0A 3.修正格式:通过抓包发现&#…

idea 将分支的代码合并到master

idea 将分支的代码合并到master 1. 首先签出到自己的分支 (自己的分支是自己写的代码,需要合并到master分支去) 2. 然后选中master分支,右键选择 签出并变基到“feature_SC” ,完成之后master分支中就已经是完整的代码了。 当…

基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python 计算机竞赛

文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model5 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于生成对抗网络的照片上色动态算法设计与实现 该项目较为新颖&am…

解决vue3父组件执行子组件方法报错:TypeError: Cannot read properties of null

现象: 父组件执行子组件的代码: 原因: Vue3使用的所有变量除了来自父组件传值的props以外,其他的html绑定的所有本地变量都必须通过return导出! 这一点是vue3 最坑爹的一点。很容易忘记。 解决办法:使用t…

如何在苹果Mac系统设置中查看Wi-Fi密码?

在 Mac 上查找保存的 Wi-Fi 密码的最简单方法之一是从系统设置内的高级 Wi-Fi 首选项页面。您可以通过下面的方式访问此页面来查找您保存的 Wi-Fi 密码。 1.在 Mac 上,选取「苹果菜单」选择「系统设置」。 2.从侧边栏中选择「Wi-Fi」,单击「高级」。 3.…

小程序如何设置自动预约快递

小程序通过设置自动预约功能,可以实现自动将订单信息发送给快递公司,快递公司可以自动上门取件。下面具体介绍如何设置。 在小程序管理员后台->配送设置处,选择首选配送公司。为了能够支持自动预约快递,请选择正常的快递公司&…

【Nuxt】Nuxt3 中使用 swiper 并自动滑动、手动滑动、点击滑动

demo 示例 建议先看官网 nuxt-swiper .vue 文件中使用 样式请根据你项目实际来,只展示基础配置 import { Swiper, SwiperSlide } from swiper/vue import { Autoplay } from swiper/modules import swiper/css let useSwiper: any null // swiper实例 // 初始…

【ML】分类问题

分类问题 classification:根据已知样本特征,判断输入样本属于哪种已知样本类。 常用入门案例:垃圾邮件检测、图像分类、手写数字识别、考试通过预测。 分类问题和回归问题的明显区别: 分类问题的结果是非连续型标签&#xff0c…

STM32:I²C通信原理概要

一、IIC通信原理 IIC通信和串口通信有一定的相似之处,都有一根共地线和两根数据线。但是传递外部信息,串口有两根数据线可以进行双向通信,也就是全双工通信。而在IIC通信下,其中一条数据线是用于提供同步时钟脉冲的时钟线(SCL)&am…

oracle-sql语句执行过程

客户端输入sql语句。 sql语句通过网络到达数据库实例。 服务器进程(server process)接收到sql语句。 sql – 解析成执行计划,然后sql才能执行。 会将sql和sql的执行计划缓存到共享池中。解析: 会消耗很多资源。 从数据库找数据,先从buffer cache中找&a…

STM32F4X SDIO(六) 例程讲解-SD_PowerON

STM32F4X SDIO(六) 例程讲解-SD_PowerON 例程讲解-SD_PowerONSDIO引脚初始化和时钟初始化SDIO初始化(单线模式)CMD0:GO_IDLE_STATE命令发送程序命令响应程序 CMD8:SEND_IF_CONDCMD8参数命令发送程序命令响应程序 CMD55:APP_CMDCMD55命令参数命令发送命令…

前端框架Vue学习 ——(二)Vue常用指令

文章目录 常用指令 常用指令 指令: HTML 标签上带有 “v-” 前缀的特殊属性&#xff0c;不同指令具有不同含义。例如: v-if, v-for… 常用指令&#xff1a; v-bind&#xff1a;为 HTML 标签绑定属性值&#xff0c;如设置 href&#xff0c;css 样式等 <a v-bind:href"…

包装印刷行业万界星空科技云MES解决方案

印刷业的机械化程度在国内制造行业内算是比较高的&#xff0c;不算是劳动密集型企业。如书本的装订、包装的模切、烫金、糊盒等都已经有了全自动设备。印刷厂除了部分手工必须采用人工外&#xff0c;大部分都可以采用机器&#xff0c;也就意味着可以由少量工人生产出大量产品。…

CTF工具PDF隐写神器wbStego4open安装和详细使用方法

wbStego4open安装和详细使用方法 1.wbStego4open介绍&#xff1a;2.wbStego4open下载&#xff1a;3.wbStego4open原理图&#xff1a;4.wbStego4open使用教程&#xff1a;第一步&#xff1a;第二步&#xff1a;第三步&#xff1a;第四步&#xff1a;第五步&#xff1a; 5.wbSteg…

创建日期时间类型对象 pendulum.datetime()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 创建日期时间类型对象 pendulum.datetime() 选择题 请问pdl.datetime(2023,10,1,12,0,0)的结果是&#xff1a; import pendulum as pdl print("【执行】pdl.datetime(2023,10,1)") …

最新知识付费变现小程序源码/独立后台知识付费小程序源码/修复登录接口

最新知识付费变现小程序源码&#xff0c;独立后台知识付费小程序源码&#xff0c;最新版修复登录接口。 主要功能 会员系统&#xff0c;用户登录/注册购买记录 收藏记录 基本设置 后台控制导航颜色 字体颜色 标题等设置 流量主广告开关小程序广告显示隐藏 广告主审核过审核…

使用Nokogiri库的Python程序

python import requests from bs4 import BeautifulSoup import os # 设置 proxies {"http": "", "https": ""} # 设置headers headers { User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (K…

[极客大挑战 2019]HardSQL1

提示(最后有我自用的字典, 需要可以自取) 空格等号绕过报错查询当显示不全时left(), right(), substr(), 拿到题目以后直接fuzz 这里发现过滤了 空格以及空格的变式/**/ union 但是updatexml还在还可以使用报错注入, 空格可以用()代替, 而则可以用like代替 payload: …