Quartz's final solution in a cluster environment

In a cluster environment, everyone will encounter the problem that has always been troubled, that is, how to use quartz to coordinate and process automated JOB under multiple APPs.

Imagine, now there are A  , B  , C3  machines as a cluster server to provide SERVICE to the outside world  :

A , B , and C 3 machines each have a QUARTZ , and they will automatically execute their respective tasks according to the predetermined SCHEDULE.

Let's not talk about what functions are implemented, let's say that such an architecture is actually a bit like multi-threading.

Then there will be a problem of "resource competition" in multi-threading, that is, dirty reading and dirty writing may occur. Since there are QUARTZ in the three APP SERVERs, there will be the phenomenon of repeated processing of TASK.

Generally, the external solution is to install QUARTZ on only one APP, and not on the other two, so that the cluster is useless;

Another solution is to move the code, which will affect the originally written QUARTZ JOB code, which is more painful for program developers;

I took a closer look at Spring's structure and QUARTZ's documentation, and found a solution based on the fact that Quartz itself can instantiate data.

Advantages of this program:

1. QUARTZ can be deployed on each APP SERVER as a cluster point;

2. The TASK (12 tables) of QUARTZ is instantiated as a database, based on the database engine and the High-Available strategy (a strategy of the cluster) to automatically coordinate the QUARTZ of each node, when the QUARTZ of any node is shut down abnormally or When an error occurs, QUARTZ of other nodes will start automatically;

3. There is no need for developers to change the already implemented QUARTZ, and use the SPRING+ class reflection mechanism to reconstruct the original program;

I also searched for some information in advance, and found that all the solutions currently provided on GOOGLE or in major forums are either only partially solved, or wrong, or the version is too old, or it is completely copied from others.

Especially when using QUARTZ+SPRING to instantiate the database object, it will throw an error (from a BUG of SPRING). At present, all the solutions on the Internet are wrong or simply don't say it. I will also propose how to solve it in this solution. .

 

solution:

 

1. Instantiate the TASK of QUARTZ into the database. QUARTZ can only be clustered after it is instantiated into the database. The outside solution says that it is all wrong to instantiate in memory. Put quartz-1.8.4/docs/dbTables/tables_oracle. Executing sql in ORACLE9I2 and above will generate 12 tables;

2. Generate the quartz.properties file and place it in the project's src directory so that it can be included in the class path at compile time.

Generally, our developers like to use SPRING+QUARTZ, so this quartz.properties does not need to be written, but quartz.properties must be written in the cluster solution. If quartz is not written, it will call quartz.properties in its own jar package as the default properties file, while modifying the quartz.xml file.

 

Contents of Quartz.xml  file:

 

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd ">

<beans>

                <bean id="mapScheduler" lazy-init="false" autowire="no"

                                class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

                                <property name="configLocation" value="classpath:quartz.properties" />

                                <property name="triggers">

                                                <list>

                                                                <ref bean="cronTrigger" />

                                                </list>

                                </property>

                                <!- ​​is the following sentence, because the bean can only be refactored using class reflection

                                <property name="applicationContextSchedulerContextKey" value="applicationContext" />          

</bean>

 

Contents of the quartz.properties  file:

 

org.quartz.scheduler.instanceName = mapScheduler  

org.quartz.scheduler.instanceId = AUTO 

 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 

 org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate 

 org.quartz.jobStore.dataSource = myXADS 

 org.quartz.jobStore.tablePrefix = QRTZ_ 

 org.quartz.jobStore.isClustered = true 

  

 org.quartz.dataSource.myXADS.jndiURL=jdbc/TestQuartzDS

 org.quartz.dataSource.myXADS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP 

 org.quartz.dataSource.myXADS.java.naming.factory.initial = weblogic.jndi.WLInitialContextFactory 

 org.quartz.dataSource.myXADS.java.naming.provider.url = t3://localhost:7020 

 org.quartz.dataSource.myXADS.java.naming.security.principal = weblogic 

 org.quartz.dataSource.myXADS.java.naming.security.credentials = weblogic 

3. Rewrite QuartzJobBean class of quartz

The reason is that when using quartz+spring to instantiate the task of quartz into the database, it will generate: serializable error, because:

<bean id="jobtask" class="org.springframework.scheduling.quartz. MethodInvokingJobDetailFactoryBean ">

                                <property name="targetObject">

                                                <ref bean="quartzJob"/>

                                </property>

                                <property name="targetMethod">

                                                <value>execute</value>

                                </property>

</bean>

The methodInvoking method in the MethodInvokingJobDetailFactoryBean class does not support serialization, so it will throw an error when serializing the TASK of QUARTZ into the database. It is said on the Internet that you can take the source code of SPRING, modify the program, and then package it into SPRING.jar for release. These are all bad methods and are not safe.

You must rewrite your own class according to QuartzJobBean, and then use SPRING to inject this rewritten class (we named it: MyDetailQuartzJobBean ) into appContext, and then use AOP technology to reflect the original quartzJobx (that is, the developer's original A ready-made execution class for executing QUARTZ's JOB).

Let's look  at the MyDetailQuartzJobBean  class:

 

public class MyDetailQuartzJobBean extends QuartzJobBean {

                protected final Log logger = LogFactory.getLog(getClass());

 

                private String targetObject;

                private String targetMethod;

                private ApplicationContext ctx;

 

                protected void executeInternal(JobExecutionContext context)

                                                throws JobExecutionException {

                                try {

 

                                                logger.info("execute [" + targetObject + "] at once>>>>>>");

                                                Object otargetObject = ctx.getBean(targetObject);

                                                Method m = null;

                                                try {

                                                                m = otargetObject.getClass().getMethod(targetMethod,

                                                                                                new Class[] {});

 

                                                                m.invoke(otargetObject, new Object[] {});

                                                } catch (SecurityException e) {

                                                                logger.error(e);

                                                } catch (NoSuchMethodException e) {

                                                                logger.error(e);

                                                }

 

                                } catch (Exception e) {

                                                throw new JobExecutionException(e);

                                }

 

                }

 

                public void setApplicationContext(ApplicationContext applicationContext){

                                this.ctx=applicationContext;

                }

 

                public void setTargetObject(String targetObject) {

                                this.targetObject = targetObject;

                }

 

                public void setTargetMethod(String targetMethod) {

                                this.targetMethod = targetMethod;

                }

 

}

Let's look at the complete quartz.xml  (note that the red bold part is particularly important):

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd ">

<beans>

                <bean id="mapScheduler" lazy-init="false" autowire="no"

                                class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

                                <property name="configLocation" value="classpath:quartz.properties" />

                                <property name="triggers">

                                                <list>

                                                                <ref bean="cronTrigger" />

                                                </list>

                                </property>

                                <property name=" applicationContextSchedulerContextKey " value=" applicationContext " />

                               

                </bean>

               

 

 

                <bean id="quartzJob" class="com.testcompany.framework.quartz.QuartzJob">

                </bean>

 

                <bean id="jobTask" class="org.springframework.scheduling.quartz.JobDetailBean">

                                <property name="jobClass">

                                                <value>com.testcompany.framework.quartz. MyDetailQuartzJobBean </value>

                                </property>

                                <property name="jobDataAsMap">

                                                <map>

                                                                <entry key="quartzJob" value="quartzJob" />

                                                                <entry key="targetMethod" value="execute" />

                                                </map>

                                </property> 

                </bean>

 

                <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">

                                <property name="jobDetail">

                                                <ref bean="jobTask" />

                                </property>

                                <property name="cronExpression">

                                                <value>0/5 * * * * ?</value>

                                </property>

                </bean>

</beans>

4. Download the latest quartz1.8 version and put the three packages quartz-all-1.8.4.jar, quartz-oracle-1.8.4.jar, quartz-weblogic-1.8.4.jar into web-inf/ lib directory, deploy.

 

 

test:

 

Several nodes have quartz tasks, only one quartz is running at this time, and the quartz on other nodes is not running.

 

At this time, manually shutdown the node running QUARTZ (add system.out.println("execute once...") to the program, the node running quartz will print execute once in the background), after about 7 seconds, another node's Quartz automatically detects that the instance of quartz running in the cluster has been shut down, so the quartz cluster will automatically start a quartz job task on any available APP.

 

Since then, the cluster of QUARTZ using the HA strategy has been successfully completed. We can achieve the cluster and automatic error redundancy of QUARTZ without changing the original code.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324932247&siteId=291194637