Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Quartz Job Scheduler - Multi-Tenant Setup

Since I haven't found anything related to that which would have lead me to inspiration, I thought I'd come up with this here.

I recently worked a lot with the Quartz Job Scheduler which I've already implemented within a RESTful Java Servlet that is supposed to offer a Sencha ExtJS-based UI for job creation. We use an Authenticator class in combination with a DatabaseManager that take care of authenticating users and all other database-specific stuff (using Hibernate).

Since we want to use it within the Java enterprise applications we develop, we need to run this thing for every customer. We also require the use of JobStoreTX for persistent data on MySQL databases (obviously for clustering), so volatile RAMJobStore implementation is a taboo. I am aware of the official documentation and that Quartz itself doesn't seem to support multi-tenant implementation (see Quartz Clustering).

Our setup looks kinda like this (simplfied):
- 1+ production Tomcat (serving application logic)
- 1+ production Apache (serving ExtJS front-end and static content)
- n databases (one for each customer)

To add something even more tricky:
we have legacy modules that are customer-specific (each customer gets his own application hosting), while more up-to-date modules are hosted centralized with customer-related access.

In my opinion, it wouldn't be sufficient to created the Quartz-related tables on every customer database, since we want to stick with an easy, straight forward setup (which means e.g. same table prefix for each customer etc) just to not complicate the deployment of Quartz throughout the farm.

I already thought about combining it with MariaDB MaxScale and using filters to route Quartz to each customer database based on RegEx or something similar, with Quartz only talking to the MaxScale proxy, but this seemed a little bit too much overhead for what I try to achieve and I am not even sure if this would work at all.

Is there something that would give me multi-tenancy with Quartz? Could you suggest an approach that would make Quartz being able to handle this, since we need to run jobs for each customer, but on "one" Tomcat (which is actually clustered and loadbalanced)? Or is there another product/framework/library that supports multi-tenancy out of the box?

Any lead, idea or help is very much appreciated!

like image 533
Mosh Pit Avatar asked Nov 09 '22 20:11

Mosh Pit


1 Answers

you can use something called JobDataMap which is just map which you can use as your data container for each job.. What you have to do is:

  • When creating new job, add some tenant identifier
  • Create globalJobListener where you can inject tenant identifier from that map to you tenantContext

Here is my code:

// quartz configuration with custom job listener
@Configuration
public class MultiTenantQuartzAutoConfiguration {

    @Bean
    public SchedulerFactoryBean schedulerFactory() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(jobFactory());
        schedulerFactoryBean.setGlobalJobListeners(new MultiTenantQuartzJobListener());

        return schedulerFactoryBean;
    }

    @Bean
    public SpringBeanJobFactory jobFactory() {
        return new SpringBeanJobFactory();
    }
}

// custom job listener
public class MultiTenantQuartzJobListener implements JobListener {

    private static final String NAME = "tenantContext";

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        TenantContext.setTenantId(context.getJobDetail().getJobDataMap().getString(TenantContext.TENANT_HEADER));
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        TenantContext.clear();
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        TenantContext.clear();
    }
}

// and here is how to create job with filled JobDataMap
Class c = Class.forName(jobInfo.getClassName());

JobDetail job = newJob(c)
        .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
        .withDescription(jobInfo.getDesc())
        .usingJobData(TenantContext.TENANT_HEADER, TenantContext.getTenantId())
        .build();

CronTrigger trigger = newTrigger()
        .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
        .withSchedule(cronSchedule(jobInfo.getCronExpression()))
        .build();

var ft = scheduler.scheduleJob(job, trigger);
like image 192
mychalvlcek Avatar answered Nov 15 '22 07:11

mychalvlcek