elastic-Job 源码解析之事件追踪EventBus
在elastic-job中,有一块很重要的功能,与作业的执行密切相关,但又不影响作业的执行,那就是作业的执行状态和运行轨迹记录,脑子里很容易想到这几个词,观察者模式,发布订阅模式。
在elastic-Job中,是使用guava的EventBus事件总线工具,简单的使用观察者模式来实现。
先看一个简单的demo:
新建一个消息总线的发送者
publicclassEventBusPoster{privateEventBuseventBus=newEventBus();publicvoidpost(Stringmessage){eventBus.post(message);}publicvoidaddListener(EventBusListenereventBusListener){eventBus.register(eventBusListener);}
新建一个监听消息总线的listener
publicclassEventBusListener{@Subscribepublicvoidlistener(Stringmessage){System.out.println("receive eventbus message:"+message);}}
publicclassMain{publicstaticvoidmain(String[]args){EventBusPostereventBusPoster=newEventBusPoster();EventBusListenereventBusListener=newEventBusListener();eventBusPoster.addListener(eventBusListener);eventBusPoster.post("hello world!");eventBusPoster.post("你好,世界!");}}
receive eventbus message:hello world!receive eventbus message:你好,世界!
一个很简单的观察者模式就这样实现了。那么,elastic-Job是怎样实现的?先看一下类图:

event.png
在elastic-Job启动的过程中,初始化JobScheduler时,就已经将JobEventBus初始化进去了,看代码new JobEventBus():
publicJobScheduler(finalCoordinatorRegistryCenterregCenter,finalLiteJobConfigurationliteJobConfig,finalElasticJobListener...elasticJobListeners){this(regCenter,liteJobConfig,newJobEventBus(),elasticJobListeners);}publicJobScheduler(finalCoordinatorRegistryCenterregCenter,finalLiteJobConfigurationliteJobConfig,finalJobEventConfigurationjobEventConfig,finalElasticJobListener...elasticJobListeners){this(regCenter,liteJobConfig,newJobEventBus(jobEventConfig),elasticJobListeners);}
而在作业执行过程中,多次调用jobFacade.postJobStatusTraceEvent(..)和postJobExecutionEvent去推送Event,看代码;
//AbstractElasticJobExecutor 抽象执行器publicfinalvoidexecute(){try{jobFacade.checkJobExecutionEnvironment();}catch(finalJobExecutionEnvironmentExceptioncause){jobExceptionHandler.handleException(jobName,cause);}ShardingContextsshardingContexts=jobFacade.getShardingContexts();if(shardingContexts.isAllowSendJobEvent()){//这里推送EventjobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(),State.TASK_STAGING,String.format("Job '%s' execute begin.",jobName));}jobFacade.failoverIfNecessary();//此处省略很多代码}privatevoidprocess(finalShardingContextsshardingContexts,finalintitem,finalJobExecutionEventstartEvent){if(shardingContexts.isAllowSendJobEvent()){//推送执行情况EventjobFacade.postJobExecutionEvent(startEvent);}log.trace("Job '{}' executing, item is: '{}'.",jobName,item);JobExecutionEventcompleteEvent;try{process(newShardingContext(shardingContexts,item));completeEvent=startEvent.executionSuccess();log.trace("Job '{}' executed, item is: '{}'.",jobName,item);if(shardingContexts.isAllowSendJobEvent()){//推送执行情况EventjobFacade.postJobExecutionEvent(completeEvent);}// CHECKSTYLE:OFF}catch(finalThrowablecause){// CHECKSTYLE:ONcompleteEvent=startEvent.executionFailure(cause);//推送执行情况EventjobFacade.postJobExecutionEvent(completeEvent);itemErrorMessages.put(item,ExceptionUtil.transform(cause));jobExceptionHandler.handleException(jobName,cause);}}
而postJobExecutionEvent和postJobStatusTraceEvent主要是调用jobEventBus去post数据,看代码:
@OverridepublicvoidpostJobExecutionEvent(finalJobExecutionEventjobExecutionEvent){jobEventBus.post(jobExecutionEvent);}@OverridepublicvoidpostJobStatusTraceEvent(finalStringtaskId,finalStatestate,finalStringmessage){TaskContexttaskContext=TaskContext.from(taskId);jobEventBus.post(newJobStatusTraceEvent(taskContext.getMetaInfo().getJobName(),taskContext.getId(),taskContext.getSlaveId(),Source.LITE_EXECUTOR,taskContext.getType(),taskContext.getMetaInfo().getShardingItems().toString(),state,message));if(!Strings.isNullOrEmpty(message)){log.trace(message);}}
在JobEventBus初始化过程中,主要是通过构造线程池初始化一个AsynEventBus,通过register注册监听类,若没有配置类,则不注册监听Listener,且不postEvent。
publicfinalclassJobEventBus{privatefinalJobEventConfigurationjobEventConfig;privatefinalExecutorServiceObjectexecutorServiceObject;privatefinalEventBuseventBus;privatebooleanisRegistered;publicJobEventBus(){jobEventConfig=null;executorServiceObject=null;eventBus=null;}publicJobEventBus(finalJobEventConfigurationjobEventConfig){this.jobEventConfig=jobEventConfig;//线程池线程数量为cpu核数的两倍executorServiceObject=newExecutorServiceObject("job-event",Runtime.getRuntime().availableProcessors()*2);//异步总线eventBus=newAsyncEventBus(executorServiceObject.createExecutorService());register();}privatevoidregister(){try{eventBus.register(jobEventConfig.createJobEventListener());isRegistered=true;}catch(finalJobEventListenerConfigurationExceptionex){log.error("Elastic job: create JobEventListener failure, error is: ",ex);}}/**
* 发布事件.
* 若没有注册则不发布
* @param event 作业事件
*/publicvoidpost(finalJobEventevent){if(isRegistered&&!executorServiceObject.isShutdown()){eventBus.post(event);}}}
而在注册的过程中,主要是注册了一个监听类JobEventRdbListener,看代码:
@OverridepublicJobEventListenercreateJobEventListener()throwsJobEventListenerConfigurationException{try{returnnewJobEventRdbListener(dataSource);}catch(finalSQLExceptionex){thrownewJobEventListenerConfigurationException(ex);}}
而JobEventRdbListener类,主要是订阅Event消息。
publicfinalclassJobEventRdbListenerextendsJobEventRdbIdentityimplementsJobEventListener{privatefinalJobEventRdbStoragerepository;publicJobEventRdbListener(finalDataSourcedataSource)throwsSQLException{repository=newJobEventRdbStorage(dataSource);}@Overridepublicvoidlisten(finalJobExecutionEventexecutionEvent){repository.addJobExecutionEvent(executionEvent);}@Overridepublicvoidlisten(finalJobStatusTraceEventjobStatusTraceEvent){repository.addJobStatusTraceEvent(jobStatusTraceEvent);}}
那post的Event事件,Listener是如何感知到的,看接口定义,有两个标签,@Subscribe,代表订阅消息,只要是该方法参数类型的Event,就能够被监听,如果没有合适的类型的Event,则会是DeadEvent,而@AllowConcurrentEvents字面意思就是允许并发,实际原因在于如果是线程安全的,使用该标签会减少同步开销,具体原因可以看AllowConcurrentEvents分析
publicinterfaceJobEventListenerextendsJobEventIdentity{/**
* 作业执行事件监听执行.
*
* @param jobExecutionEvent 作业执行事件
*/@Subscribe@AllowConcurrentEventsvoidlisten(JobExecutionEventjobExecutionEvent);/**
* 作业状态痕迹事件监听执行.
*
* @param jobStatusTraceEvent 作业状态痕迹事件
*/@Subscribe@AllowConcurrentEventsvoidlisten(JobStatusTraceEventjobStatusTraceEvent);}