Elastic-job 启动“假死”的问题分析

问题记录

最近项目引入Elastic Job实现定时任务的分布式调度。引入的版本2.1.5,加入相关的job配置后启动项目,主线程假死,不进行后续逻辑处理和日志输出。

输出的日志如下:


[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.049] [] [StdSchedulerFactory] [Using default implementation for ThreadExecutor]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.130] [] [SchedulerSignalerImpl] [Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.131] [] [QuartzScheduler] [Quartz Scheduler v.2.2.1 created.]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.135] [] [JobShutdownHookPlugin] [Registering Quartz shutdown hook.]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.136] [] [RAMJobStore] [RAMJobStore initialized.]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.139] [] [QuartzScheduler] [Scheduler meta-data: Quartz Scheduler (v2.2.1) 'dailyScanMercReratingJob' with instanceId 'NON_CLUSTERED'

  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.

  NOT STARTED.

  Currently in standby mode.

  Number of jobs executed: 0

  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.

  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.139] [] [StdSchedulerFactory] [Quartz scheduler 'dailyScanMercReratingJob' initialized from an externally provided properties instance.]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.139] [] [StdSchedulerFactory] [Quartz scheduler version: 2.2.1]

解决方案

直接上解决方案:<front color='red'>将项目的curator的框架版本全部调整为2.10.0 ,包括

  • curator-client;

  • curator-recipes;

  • curator-framework

</front>

项目中因为curator-framework引用了2.7.0导致出现了此问题。

问题追踪

在项目的Spring框架的以下位置打断点追踪项目启动过程:

  • ContextLoaderListener.contextInitialized() 方法

  • AbstractApplicationContext.refresh() 方法;

发现代码在AbstractApplicationContext.refresh() 方法里,执行: finishBeanFactoryInitialization(beanFactory) 时陷入等待一直无法跳出继续执行。

根据Spring框架的启动机制,finishBeanFactoryInitialization 是完成单例bean的初始化的方法,这个方法会去真正操作elastic-job 对于job的操作代码。

从日志中发现代码最后一行输出为:


[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 15:53:27.139] [] [StdSchedulerFactory] [Quartz scheduler version: 2.2.1]

StdSchedulerFactory 类中找到关于Quartz scheduler version 相关的日志打断点继续跟踪:


//…………………… 省略之前的代码

jrsf.initialize(scheduler);



            qs.initialize();



            getLog().info(

                    "Quartz scheduler '" + scheduler.getSchedulerName()

                            + "' initialized from " + propSrc);

    //这里断点

            getLog().info("Quartz scheduler version: " + qs.getVersion());



            // prevents the repository from being garbage collected

            qs.addNoGCObject(schedRep);

            // prevents the db manager from being garbage collected

            if (dbMgr != null) {

                qs.addNoGCObject(dbMgr);

            }



            schedRep.bind(scheduler);

            return scheduler;

在上面的位置断点后发现,elastic job 继续执行,持续跟踪最终跟踪到SchedulerFacade类的registerStartUpInfo方法:


  /**

    * 注册作业启动信息.

    *

    * @param enabled 作业是否启用

    */

    public void registerStartUpInfo(final boolean enabled) {

        listenerManager.startAllListeners();

        leaderService.electLeader();

        serverService.persistOnline(enabled);

        instanceService.persistOnline();

        shardingService.setReshardingFlag();

        monitorService.listen();

        if (!reconcileService.isRunning()) {

            reconcileService.startAsync();

        }

    }

代码在leaderService.electLeader(); 陷入等待。

根据以上最终可用得出结论:

  • <font color='red'>elastic-job 在job的选主过程中陷入了无限等待,即无法选出主节点执行任务。</font>

根据对LeaderService 的代码的研究,elastic job 选主使用的是 curator框架的 LeaderLatch 类完成的。

具体时线程wait的操作在:JobNodeStorageexecuteInLeader方法中:


/**

    * 在主节点执行操作.

    *

    * @param latchNode 分布式锁使用的作业节点名称

    * @param callback 执行操作的回调

    */

    public void executeInLeader(final String latchNode, final LeaderExecutionCallback callback) {

        try (LeaderLatch latch = new LeaderLatch(getClient(), jobNodePath.getFullPath(latchNode))) {

            latch.start();

            latch.await();

            callback.execute();

        //CHECKSTYLE:OFF

        } catch (final Exception ex) {

        //CHECKSTYLE:ON

            handleException(ex);

        }

    }

上面的方法调用 latch.await(); 来等待获取 leadership。由于无法获取主节点,导致线程一致wait。

LeaderLatch 大概的机制为:所有客户端向zk的同一个path竞争的写入数据,谁先写入成功谁就获取了leadership

LeaderLatchawait方法如下:


public void await() throws InterruptedException, EOFException

    {

        synchronized(this)

        {

            while ( (state.get() == State.STARTED) && !hasLeadership.get() )

            {

                wait();

            }

        }

        if ( state.get() != State.STARTED )

        {

            throw new EOFException();

        }

    }

如果LeaderLatch无法获取leadership那么就当前的Thread就会一直陷入wait

问题解决

定位到问题的发生点,解决问题的思路就要看为什么无法获取到leadership

登录到ZK上查询节点信息,发现正常项目启动后,elastic job会向zk的写入如下格式的节点内容:


/{job-namespace}/{job-id}/leader/election/latch

但是异常的项目是没有这个节点的,所以应该是ZK的操作发生了问题。具体哪里发生了问题这里还没有发现。

继续将项目日志调整为DEBUG级别会发下有如下的日志输出:


[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 17:51:47.687] [] [RAMJobStore] [RAMJobStore initialized.]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 17:51:47.689] [] [QuartzScheduler] [Scheduler meta-data: Quartz Scheduler (v2.2.1) 'dailyScanMercReratingJob' with instanceId 'NON_CLUSTERED'

  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.

  NOT STARTED.

  Currently in standby mode.

  Number of jobs executed: 0

  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.

  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 17:51:47.689] [] [StdSchedulerFactory] [Quartz scheduler 'dailyScanMercReratingJob' initialized from an externally provided properties instance.]

[INFO] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 17:51:47.689] [] [StdSchedulerFactory] [Quartz scheduler version: 2.2.1]

[DEBUG] [Timer-0] [2018-10-10 17:51:49.553] [] [UpdateChecker] [Checking for available updated version of Quartz...]

[DEBUG] [RMI TCP Connection(2)-127.0.0.1] [2018-10-10 17:51:49.586] [] [LeaderService] [Elect a new leader now.]

[DEBUG] [Curator-TreeCache-0] [2018-10-10 17:51:50.724] [] [RegExceptionHandler] [Elastic job: ignored exception for: KeeperErrorCode = NoNode for /payrisk-job/dailyScanMercReratingJob/leader/election/instance]

[DEBUG] [Curator-TreeCache-0] [2018-10-10 17:51:50.738] [] [RegExceptionHandler] [Elastic job: ignored exception for: KeeperErrorCode = NoNode for /payrisk-job/dailyScanMercReratingJob/leader/election/instance]

[DEBUG] [Curator-TreeCache-0] [2018-10-10 17:51:50.759] [] [RegExceptionHandler] [Elastic job: ignored exception for: KeeperErrorCode = NoNode for /payrisk-job/dailyScanMercReratingJob/leader/election/instance]

[DEBUG] [Curator-TreeCache-0] [2018-10-10 17:51:50.769] [] [RegExceptionHandler] [Elastic job: ignored exception for: KeeperErrorCode = NoNode for /payrisk-job/dailyScanMercReratingJob/leader/election/instance]

[DEBUG] [Curator-TreeCache-0] [2018-10-10 17:51:50.791] [] [RegExceptionHandler] [Elastic job: ignored exception for: KeeperErrorCode = NoNode for /payrisk-job/dailyScanMercReratingJob/leader/election/instance]

[DEBUG] [Curator-TreeCache-0] [2018-10-10 17:51:50.803] [] [RegExceptionHandler] [Elastic job: ignored exception for: KeeperErrorCode = NoNode for /payrisk-job/dailyScanMercReratingJob/leader/election/instance]

[DEBUG] [Curator-TreeCache-0] [2018-10-10 17:51:50.813] [] [RegExceptionHandler] [Elastic job: ignored exception for: KeeperErrorCode = NoNode for /payrisk-job/dailyScanMercReratingJob/leader/election/instance]

[DEBUG] [Curator-TreeCache-0] [2018-10-10 17:51:50.818] [] [LeaderService] [Elect a new leader now.]

[DEBUG] [Timer-0] [2018-10-10 17:51:51.261] [] [UpdateChecker] [Quartz version update check failed: Server returned HTTP response code: 403 for URL: http://www.terracotta.org/kit/reflector?kitID=quartz&pageID=update.properties&id=2130706433&os-name=Mac+OS+X&jvm-name=Java+HotSpot%28TM%29+64-Bit+Server+VM&jvm-version=1.8.0_112&platform=x86_64&tc-version=2.2.1&tc-product=Quartz&source=Quartz&uptime-secs=1&patch=UNKNOWN]

这行日志的输出代码位于:elastic-jobRegExceptionHandler.handleException()方法:




    /**

    * 处理异常.

    *

    * <p>处理掉中断和连接失效异常并继续抛注册中心.</p>

    *

    * @param cause 待处理异常.

    */

    public static void handleException(final Exception cause) {

        if (null == cause) {

            return;

        }

        if (isIgnoredException(cause) || null != cause.getCause() && isIgnoredException(cause.getCause())) {

            log.debug("Elastic job: ignored exception for: {}", cause.getMessage());

        } else if (cause instanceof InterruptedException) {

            Thread.currentThread().interrupt();

        } else {

            throw new RegException(cause);

        }

    }

这里 elastic job ignore了zk的操作异常,导致选主失败但是并没有做兼容处理,主线程陷入 wait()

NoNodeException

根据上面的查询,无法选主是因为curator框架抛出了NoNodeException,通过google很容找到解决这个问题的方法:统一curator的版本。

关于为什么会抛出这个问题,需要深入研究下。留待考察和研究。

最终解决方案

将项目中curator中的jar包版本全部统一为2.10.0问题解决。

NOTE:注意jar包是否完全升级了要去打包后的项目的lib下面观察下,看所有的jar是否全部都是2.10.0并且没有其他版本的jar。

总结

调试此类问题很耗时,经验:

  • 如果对框架比较熟悉,先尝试跟踪看问题代码发送地点;

  • 去框架的github官网的issue中查看是否有同类问题;

  • 如果还是无法解决,将日志级别调整为DEBUG再仔细观察下日志;

此外写框架的时候:

  • 最后不要吞掉异常,不管什么原因,如果要ignore Exception,最好是打印一个INFO或者WARNING级别的日志。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容