1. 程式人生 > >Springboot quartz叢集(3) — 多節點發送郵件

Springboot quartz叢集(3) — 多節點發送郵件

本期將提供quartz叢集能力

  • 叢集案例分析:
    上一期的郵件傳送功能,若在服務需要部署多節點,但定時任務不支援叢集,因此,多節點定時任務勢必會同時執行,
    若向使用者傳送郵件通知,這種情況下會向用戶傳送兩次一模一樣的郵件,N個節點會發送N次郵件,嚴重不符合業務場景,
    若提供叢集能力,則多節點間應分擔郵件傳送的工作而不是各節點做重複的工作,因此在部署多節點的時候定時任務也需要提供叢集能力。
  • 個人見解:
    1. quartz叢集分為水平叢集和垂直叢集,水平叢集即將定時任務節點部署在不同的伺服器,水平叢集最大的問題就是時鐘同步問題,
      quartz叢集強烈要求時鐘同步,若時鐘不能同步,則會導致叢集中各個節點狀態紊亂,造成不可預知的後果,請自行搜尋伺服器時鐘同步
      ,
      若能保證時鐘同步,水平叢集能保證服務的可靠性,其中一個節點掛掉或其中一個伺服器宕機,其他節點依然正常服務;垂直叢集則是叢集各節點部署在同一臺伺服器,
      時鐘同步自然不是問題,但存在單點故障問題,伺服器宕機會嚴重影響服務的可用性。因此,要結合實際情況來考慮叢集方案
    2. 由於叢集中強烈要求時鐘同步,因此不管是垂直叢集還是水平叢集,本地開發決不能連線線上環境(本地也是叢集模式),這樣的話勢必會破壞叢集,但本地若是非叢集模式,
      則可以依情況來連線線上環境。
    3. quartz叢集和redis這樣的叢集實現方式不一樣,redis叢集需要節點之間通訊,各節點需要知道其他節點的狀況,而quartz叢集的實現
      方式在於11張表,叢集節點相互之間不通訊,而是通過定時任務持久化加鎖的方式來實現叢集。
    4. 破壞集群后果一般是死鎖或者狀態紊亂每個節點都不可用或其中某些節點能用部分或全部的定時任務

1. 建立叢集需要的11張表

t_b_qrtz_blob_triggers
t_b_qrtz_calendars
t_b_qrtz_cron_triggers
t_b_qrtz_fired_triggers
t_b_qrtz_job_details
t_b_qrtz_locks
t_b_qrtz_paused_trigger_grps
t_b_qrtz_scheduler_state
t_b_qrtz_simple_triggers
t_b_qrtz_simprop_triggers
t_b_qrtz_triggers

2. 叢集建表sql

drop table if exists t_b_qrtz_fired_triggers;
drop table if exists t_b_qrtz_paused_trigger_grps;
drop table if exists t_b_qrtz_scheduler_state;
drop table if exists t_b_qrtz_locks;
drop table if exists t_b_qrtz_simple_triggers;
drop table if exists t_b_qrtz_simprop_triggers;
drop table if exists t_b_qrtz_cron_triggers;
drop table if exists t_b_qrtz_blob_triggers;
drop table if exists t_b_qrtz_triggers;
drop table if exists t_b_qrtz_job_details;
drop table if exists t_b_qrtz_calendars;

create table t_b_qrtz_job_details(
  sched_name varchar(120) not null,
  job_name varchar(200) not null,
  job_group varchar(200) not null,
  description varchar(250) null,
  job_class_name varchar(250) not null,
  is_durable varchar(1) not null,
  is_nonconcurrent varchar(1) not null,
  is_update_data varchar(1) not null,
  requests_recovery varchar(1) not null,
  job_data blob null,
  primary key (sched_name,job_name,job_group))
  engine=innodb;

create table t_b_qrtz_triggers (
  sched_name varchar(120) not null,
  trigger_name varchar(200) not null,
  trigger_group varchar(200) not null,
  job_name varchar(200) not null,
  job_group varchar(200) not null,
  description varchar(250) null,
  next_fire_time bigint(13) null,
  prev_fire_time bigint(13) null,
  priority integer null,
  trigger_state varchar(16) not null,
  trigger_type varchar(8) not null,
  start_time bigint(13) not null,
  end_time bigint(13) null,
  calendar_name varchar(200) null,
  misfire_instr smallint(2) null,
  job_data blob null,
  primary key (sched_name,trigger_name,trigger_group),
  foreign key (sched_name,job_name,job_group)
  references t_b_qrtz_job_details(sched_name,job_name,job_group))
  engine=innodb;

create table t_b_qrtz_simple_triggers (
  sched_name varchar(120) not null,
  trigger_name varchar(200) not null,
  trigger_group varchar(200) not null,
  repeat_count bigint(7) not null,
  repeat_interval bigint(12) not null,
  times_triggered bigint(10) not null,
  primary key (sched_name,trigger_name,trigger_group),
  foreign key (sched_name,trigger_name,trigger_group)
  references t_b_qrtz_triggers(sched_name,trigger_name,trigger_group))
  engine=innodb;

create table t_b_qrtz_cron_triggers (
  sched_name varchar(120) not null,
  trigger_name varchar(200) not null,
  trigger_group varchar(200) not null,
  cron_expression varchar(120) not null,
  time_zone_id varchar(80),
  primary key (sched_name,trigger_name,trigger_group),
  foreign key (sched_name,trigger_name,trigger_group)
  references t_b_qrtz_triggers(sched_name,trigger_name,trigger_group))
  engine=innodb;

create table t_b_qrtz_simprop_triggers
(
  sched_name varchar(120) not null,
  trigger_name varchar(200) not null,
  trigger_group varchar(200) not null,
  str_prop_1 varchar(512) null,
  str_prop_2 varchar(512) null,
  str_prop_3 varchar(512) null,
  int_prop_1 int null,
  int_prop_2 int null,
  long_prop_1 bigint null,
  long_prop_2 bigint null,
  dec_prop_1 numeric(13,4) null,
  dec_prop_2 numeric(13,4) null,
  bool_prop_1 varchar(1) null,
  bool_prop_2 varchar(1) null,
  primary key (sched_name,trigger_name,trigger_group),
  foreign key (sched_name,trigger_name,trigger_group)
  references t_b_qrtz_triggers(sched_name,trigger_name,trigger_group))
  engine=innodb;

create table t_b_qrtz_blob_triggers (
  sched_name varchar(120) not null,
  trigger_name varchar(200) not null,
  trigger_group varchar(200) not null,
  blob_data blob null,
  primary key (sched_name,trigger_name,trigger_group),
  index (sched_name,trigger_name, trigger_group),
  foreign key (sched_name,trigger_name,trigger_group)
  references t_b_qrtz_triggers(sched_name,trigger_name,trigger_group))
  engine=innodb;

create table t_b_qrtz_calendars (
  sched_name varchar(120) not null,
  calendar_name varchar(200) not null,
  calendar blob not null,
  primary key (sched_name,calendar_name))
  engine=innodb;

create table t_b_qrtz_paused_trigger_grps (
  sched_name varchar(120) not null,
  trigger_group varchar(200) not null,
  primary key (sched_name,trigger_group))
  engine=innodb;

create table t_b_qrtz_fired_triggers (
  sched_name varchar(120) not null,
  entry_id varchar(95) not null,
  trigger_name varchar(200) not null,
  trigger_group varchar(200) not null,
  instance_name varchar(200) not null,
  fired_time bigint(13) not null,
  sched_time bigint(13) not null,
  priority integer not null,
  state varchar(16) not null,
  job_name varchar(200) null,
  job_group varchar(200) null,
  is_nonconcurrent varchar(1) null,
  requests_recovery varchar(1) null,
  primary key (sched_name,entry_id))
  engine=innodb;

create table t_b_qrtz_scheduler_state (
  sched_name varchar(120) not null,
  instance_name varchar(200) not null,
  last_checkin_time bigint(13) not null,
  checkin_interval bigint(13) not null,
  primary key (sched_name,instance_name))
  engine=innodb;

create table t_b_qrtz_locks (
  sched_name varchar(120) not null,
  lock_name varchar(40) not null,
  primary key (sched_name,lock_name))
  engine=innodb;

create index idx_qrtz_j_req_recovery on t_b_qrtz_job_details(sched_name,requests_recovery);
create index idx_qrtz_j_grp on t_b_qrtz_job_details(sched_name,job_group);

create index idx_qrtz_t_j on t_b_qrtz_triggers(sched_name,job_name,job_group);
create index idx_qrtz_t_jg on t_b_qrtz_triggers(sched_name,job_group);
create index idx_qrtz_t_c on t_b_qrtz_triggers(sched_name,calendar_name);
create index idx_qrtz_t_g on t_b_qrtz_triggers(sched_name,trigger_group);
create index idx_qrtz_t_state on t_b_qrtz_triggers(sched_name,trigger_state);
create index idx_qrtz_t_n_state on t_b_qrtz_triggers(sched_name,trigger_name,trigger_group,trigger_state);
create index idx_qrtz_t_n_g_state on t_b_qrtz_triggers(sched_name,trigger_group,trigger_state);
create index idx_qrtz_t_next_fire_time on t_b_qrtz_triggers(sched_name,next_fire_time);
create index idx_qrtz_t_nft_st on t_b_qrtz_triggers(sched_name,trigger_state,next_fire_time);
create index idx_qrtz_t_nft_misfire on t_b_qrtz_triggers(sched_name,misfire_instr,next_fire_time);
create index idx_qrtz_t_nft_st_misfire on t_b_qrtz_triggers(sched_name,misfire_instr,next_fire_time,trigger_state);
create index idx_qrtz_t_nft_st_misfire_grp on t_b_qrtz_triggers(sched_name,misfire_instr,next_fire_time,trigger_group,trigger_state);

create index idx_qrtz_ft_trig_inst_name on t_b_qrtz_fired_triggers(sched_name,instance_name);
create index idx_qrtz_ft_inst_job_req_rcvry on t_b_qrtz_fired_triggers(sched_name,instance_name,requests_recovery);
create index idx_qrtz_ft_j_g on t_b_qrtz_fired_triggers(sched_name,job_name,job_group);
create index idx_qrtz_ft_jg on t_b_qrtz_fired_triggers(sched_name,job_group);
create index idx_qrtz_ft_t_g on t_b_qrtz_fired_triggers(sched_name,trigger_name,trigger_group);
create index idx_qrtz_ft_tg on t_b_qrtz_fired_triggers(sched_name,trigger_group);

commit;

3. 工程中application.properties新增叢集配置

quartz.scheduler.instanceName=CnlmScheduler
org.quartz.dataSource.myDS.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL=jdbc:mysql://120.77.172.111:3306/touch6?useUnicode=true&characterEncoding=utf8
org.quartz.dataSource.myDS.user=cnlm.me
org.quartz.dataSource.myDS.password=123456
org.quartz.dataSource.myDS.maxConnections=10

4. 排程工廠配置叢集引數支援叢集能力,下面是支援叢集能力的排程工廠類

package me.cnlm.springboot.quartz.config;

import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import java.io.IOException;
import java.util.Properties;

@Configuration
public class SchedulerFactoryBeanConfig {

    @Value("${quartz.scheduler.instanceName}")
    private String quartzInstanceName;

    @Value("${org.quartz.dataSource.myDS.driver}")
    private String myDSDriver;

    @Value("${org.quartz.dataSource.myDS.URL}")
    private String myDSURL;

    @Value("${org.quartz.dataSource.myDS.user}")
    private String myDSUser;

    @Value("${org.quartz.dataSource.myDS.password}")
    private String myDSPassword;

    @Value("${org.quartz.dataSource.myDS.maxConnections}")
    private String myDSMaxConnections;


    /**
     * 定時任務叢集配置
     * 設定屬性
     *
     * @return
     * @throws IOException
     */
    private Properties quartzProperties() throws IOException {
        Properties prop = new Properties();
        prop.put("quartz.scheduler.instanceName", quartzInstanceName);
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        prop.put("org.quartz.scheduler.skipUpdateCheck", "true");
        prop.put("org.quartz.scheduler.jmx.export", "true");

        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
        prop.put("org.quartz.jobStore.dataSource", "quartzDataSource");
        prop.put("org.quartz.jobStore.tablePrefix", "T_B_QRTZ_");
        prop.put("org.quartz.jobStore.isClustered", "true");

        prop.put("org.quartz.jobStore.clusterCheckinInterval", "20000");
        prop.put("org.quartz.jobStore.dataSource", "myDS");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        prop.put("org.quartz.jobStore.misfireThreshold", "120000");
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
        prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE");

        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "10");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        prop.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");

        prop.put("org.quartz.dataSource.myDS.driver", myDSDriver);
        prop.put("org.quartz.dataSource.myDS.URL", myDSURL);
        prop.put("org.quartz.dataSource.myDS.user", myDSUser);
        prop.put("org.quartz.dataSource.myDS.password", myDSPassword);
        prop.put("org.quartz.dataSource.myDS.maxConnections", myDSMaxConnections);

        prop.put("org.quartz.plugin.triggHistory.class", "org.quartz.plugins.history.LoggingJobHistoryPlugin");
        prop.put("org.quartz.plugin.shutdownhook.class", "org.quartz.plugins.management.ShutdownHookPlugin");
        prop.put("org.quartz.plugin.shutdownhook.cleanShutdown", "true");
        return prop;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("sendEmailTrigger") Trigger sendEmailTrigger) throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setOverwriteExistingJobs(true);
        //用於quartz叢集,載入quartz資料來源
        //factory.setDataSource(dataSource);
        factory.setStartupDelay(10);
//        factory.setQuartzProperties(quartzProperties());
        factory.setAutoStartup(true);
        factory.setApplicationContextSchedulerContextKey("applicationContext");
        //註冊觸發器
        factory.setTriggers(
                sendEmailTrigger
        );
        return factory;
    }
}

5. 測試驗證

略(分別執行兩個專案節點,可以發現上述11張表中添加了2個節點的資訊和定時任務執行狀態,此處可自行驗證)

本期已結束,至此專案支援定時任務分散式叢集模式

歡迎加入技術交流QQ群566654343