석영 소스 코드 분석 - 쓰레드 스케줄링

면책 조항 :이 문서는, 블로거 원본입니다 다시 인쇄에 오신 것을 환영합니다. https://blog.csdn.net/guo_xl/article/details/85165333

목적

에서 springboot - 석영 통합 및 데모 소스 코드 분석 , 수요가 클러스터 환경을 배포됩니다

  • 다른 기계가 얻을 수없는 동안에만있는 하나의 시스템에서 획득 할 수있는 고정합니다.
  • 그러나 기계를 걸어, 다른 기계는 인수 할 수 있도록

그들은 분산 데이터베이스 잠금을 구현하는 데 사용과 같은 스케줄링 기능을 고려 맨 처음에이 기능이 관련되어, 그것은이었다. 석영은 나중에 정신 연구는 석영에서 수행하는 방법, 이러한 이유를 알고 알고 달성 된 것으로 밝혀졌다.

석영 스레딩 모델

  • ThreadExecutor 일정 스레드
  • 작업자 스레드 ThreadPool이 (SimpleThreadPool) 풀

석영 테이블

테이블 이름 기술
QRTZ_JOB_DETAILS 특정 작업의 표
QRTZ_TRIGGERS TRIGGER_TYPE에 의해 기록 트리거 테이블은 SIMPLE, CRON, DAILY_I, CAL_INT, BLOB를 포함하는 트리거의 종류를 구별합니다. 그것은 SimpleTrigger, CronTirgger, DateIntervalTrigger 및 NthIncludedDayTrigger 대응
QRTZ_SIMPLE_TRIGGERS 트리거 테이블 SIMPLE 유형 트리거 필드의 간격
QRTZ_BLOB_TRIGGERS 트리거 테이블의 블롭 유형
QRTZ_CRON_TRIGGERS 트리거 테이블의 CRON 유형
QRTZ_FIRED_TRIGGERS 트리거 동작에 관련된 상태 정보 저장, 및 실행 정보 관련 업무
QRTZ_CALENDARS 일정 정보는 석영 저장
QRTZ_LOCKS 행 잠금 테이블은 QRTZ_LOCKS는 석영 클러스터 동기화 메커니즘 행 잠금 테이블
QRTZ_SCHEDULER_STATE 기록 스케줄러 인스턴스 객체
StdSchedulerFactory 시작

에서 springboot - 석영 통합 된 소스 코드 분석 및 데모
석영 StdSchedulerFactory을 시작 그려진 최종 분석에 호출됩니다
StdSchedulerFactory#private Scheduler instantiate() throws SchedulerException {}
instantiate(), 초기화의 중요한 특성 중 몇
이 ThreadPool이 및 ThreadExecutor, JobStore에 추가

private Scheduler instantiate() throws SchedulerException {
        JobStore js = null;
        ThreadPool tp = null;
        ThreadExecutor threadExecutor;
}
ThreadPool이

석영 작업자 스레드 ThreadPool이 (SimpleThreadPool) 풀을 시작

private Scheduler instantiate() throws SchedulerException {
        ThreadPool tp = null;
        //默认是SimpleThreadPool
        String tpClass = cfg.getStringProperty(PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());

        if (tpClass == null) {
            initException = new SchedulerException(
                    "ThreadPool class not specified. ");
            throw initException;
        }

        try {
            tp = (ThreadPool) loadHelper.loadClass(tpClass).newInstance();
        } catch (Exception e) {
            ...
        }
        tProps = cfg.getPropertyGroup(PROP_THREAD_POOL_PREFIX, true);
        try {
            setBeanProps(tp, tProps);
        } catch (Exception e) {
            ...
        }
        ...
        tp.initialize();//启动threadpool
        ...
}

의 특성 SimpleThreadPool quartz.jar 일부의 기본 구성
에 Quartz.propertis, 예를 들어,

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

tp.initialize () 即 SimpleThreadPool 번호의 초기화 ()

public class SimpleThreadPool implements ThreadPool {
 private List<WorkerThread> workers;
    private LinkedList<WorkerThread> availWorkers = new LinkedList<WorkerThread>();
    private LinkedList<WorkerThread> busyWorkers = new LinkedList<WorkerThread>();
    
    public void initialize() throws SchedulerConfigException {
        ...
        // 创建worker线程数
        Iterator<WorkerThread> workerThreads = createWorkerThreads(count).iterator();
        // 循环启动
        while(workerThreads.hasNext()) {
            WorkerThread wt = workerThreads.next();
            wt.start();
            //availWorkers是空闲的线程,一开始肯定都是空闲的
            availWorkers.add(wt);
        }
    }

}
의 WorkerThread
class WorkerThread extends Thread {
        //AtomicBoolean来标识是否线程已经停止了
private AtomicBoolean run = new AtomicBoolean(true);
public void run(Runnable newRunnable) {
            synchronized(lock) {
                if(runnable != null) {
                    throw new IllegalStateException("Already running a Runnable!");
                }
                runnable = newRunnable;
                lock.notifyAll();
            }
 }
public void run() {
            boolean ran = false;
            while (run.get()) {
                try {
                    synchronized(lock) {
                        while (runnable == null && run.get()) {
                            lock.wait(500);
                        } 
                        //启动传入的runnable的run(),不是start()
                        if (runnable != null) {
                            ran = true;
                            runnable.run();
                        }
                    }
                } catch (InterruptedException unblock) {
                ...
                } catch (Throwable exceptionInRunnable) {
                ...
                } finally {
                    if (runOnce) {
                           run.set(false);                        clearFromBusyWorkersList(this);
                    } else if(ran) {
                        ran = false;
                        makeAvailable(this);
                    }

                }
            }
        }
    }
    
}
ThreadExecutor

ThreadExecutor 기본이 DefaultThreadExecutor이다에서 인스턴스화

private Scheduler instantiate() throws SchedulerException {
        ThreadExecutor threadExecutor;
        String threadExecutorClass = cfg.getStringProperty(PROP_THREAD_EXECUTOR_CLASS);
        if (threadExecutorClass != null) {
            tProps = cfg.getPropertyGroup(PROP_THREAD_EXECUTOR, true);
            try {
                threadExecutor = (ThreadExecutor) loadHelper.loadClass(threadExecutorClass).newInstance();
                log.info("Using custom implementation for ThreadExecutor: " + threadExecutorClass);

                setBeanProps(threadExecutor, tProps);
            } catch (Exception e) {
                initException = new SchedulerException(
                        "ThreadExecutor class '" + threadExecutorClass + "' could not be instantiated.", e);
                throw initException;
            }
        } else {
            log.info("Using default implementation for ThreadExecutor");
            //使用默认的DefaultThreadExecutor
            threadExecutor = new DefaultThreadExecutor();
        }
        QuartzSchedulerResources rsrcs = new QuartzSchedulerResources();
        rsrcs.setThreadExecutor(threadExecutor);
        qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
}

QuartzScheduler (rsrcs, idleWaitTime, dbFailureRetry) 생성자를 참조

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)
        throws SchedulerException {
        ...
        this.schedThread = new QuartzSchedulerThread(this, resources);
        //schedThreadExecutor可以由
        //org.quartz.threadExecutor.class来指定定义,如果没有定义默认是DefaultThreadExecutor
        ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
        //启动QuartzSchedulerThread
        schedThreadExecutor.execute(this.schedThread);
        ...
    }
public class DefaultThreadExecutor implements ThreadExecutor {

    public void initialize() {
    }

    public void execute(Thread thread) {
        thread.start();
    }

}

QuartzSchedulerThread 주로 볼, 스레드 연장
실행이고, DefaultThreadExecutor 번호 실행 QuartzSchedulerThread #의 run () 메소드를
() 실행 QuartzSchedulerThread

 public void run() {
        ...
        //返回0之前会一直阻塞
        int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
                if(availThreadCount > 0) {//一定是true

                    List<OperableTrigger> triggers;

                    long now = System.currentTimeMillis();

                    clearSignaledSchedulingChange();
                    try {
                    //获取到在一定空闲时间内的任务
                        triggers = qsRsrcs.getJobStore().acquireNextTriggers(
                                now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
        ...
                    List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
                                            if(res != null)
                                                bndles = res;
 }

qsRsrcs.getJobStore (). acquireNextTriggers ()

qsRsrcs.getJobStore () = JobStoreSupport

 public List<OperableTrigger> acquireNextTriggers(final long noLaterThan, final int maxCount, final long timeWindow)
        throws JobPersistenceException {
        
        String lockName;
        if(isAcquireTriggersWithinLock() || maxCount > 1) { 
            lockName = LOCK_TRIGGER_ACCESS;
        } else {
            lockName = null;
        }
     //这个方法要先获取到锁,使用非管理的事务,所谓非管理,就是说事务需要代码手动提交,下面注释是源码里的注释
 /**
     * Execute the given callback having optionally acquired the given lock.
     * This uses the non-managed transaction connection.
     * 
     *
     * @param lockName The name of the lock to acquire, for example
     * "TRIGGER_ACCESS".  If null, then no lock is acquired, but the
     * lockCallback is still executed in a non-managed transaction. 
     */
        return executeInNonManagedTXLock(lockName, 
                new TransactionCallback<List<OperableTrigger>>() {
                    public List<OperableTrigger> execute(Connection conn) throws JobPersistenceException {
                        return acquireNextTrigger(conn, noLaterThan, maxCount, timeWindow);
                    }
                },
                new TransactionValidator<List<OperableTrigger>>() {
                    public Boolean validate(Connection conn, List<OperableTrigger> result) throws JobPersistenceException {
                        try {
                            List<FiredTriggerRecord> acquired = getDelegate().selectInstancesFiredTriggerRecords(conn, getInstanceId());
                            Set<String> fireInstanceIds = new HashSet<String>();
                            for (FiredTriggerRecord ft : acquired) {
                                fireInstanceIds.add(ft.getFireInstanceId());
                            }
                            for (OperableTrigger tr : result) {
                                if (fireInstanceIds.contains(tr.getFireInstanceId())) {
                                    return true;
                                }
                            }
                            return false;
                        } catch (SQLException e) {
                            throw new JobPersistenceException("error validating trigger acquisition", e);
                        }
                    }
                });
    }

executeInNonManagedTXLock ()

 protected <T> T executeInNonManagedTXLock(
            String lockName, 
            TransactionCallback<T> txCallback, final TransactionValidator<T> txValidator) throws JobPersistenceException {
        boolean transOwner = false;
        Connection conn = null;
        try {
            if (lockName != null) {
                // If we aren't using db locks, then delay getting DB connection 
                // until after acquiring the lock since it isn't needed.
                //可以不适用数据库做分布式锁,
                //默认是使用的,getLockHandler()返回的是org.quartz.impl.jdbcjobstore.StdRowLockSemaphore
                if (getLockHandler().requiresConnection()) {
                    conn = getNonManagedTXConnection();
                }
                
                //使用数据库来获取到锁,
                //锁使用的是SELECT * FROM QRTZ_LOCKS WHERE SCHED_NAME = 'quartzScheduler' AND LOCK_NAME = 'TRIGGER_ACCESS' FOR UPDATE来获取到 的行锁
                transOwner = getLockHandler().obtainLock(conn, lockName);
            }
            
            if (conn == null) {
                conn = getNonManagedTXConnection();
            }
            
            //获取到锁后,其它的线程或进程都会阻塞在这里。而获取到锁的线/程会继续执行任务
            final T result = txCallback.execute(conn);
            
            //完后需要手动提交,
            try {
                commitConnection(conn);
            } catch (JobPersistenceException e) {
               ..
            }

            Long sigTime = clearAndGetSignalSchedulingChangeOnTxCompletion();
            if(sigTime != null && sigTime >= 0) {
                signalSchedulingChangeImmediately(sigTime);
            }
            
            return result;
        } catch (JobPersistenceException e) {
            rollbackConnection(conn);
            throw e;
        } catch (RuntimeException e) {
            rollbackConnection(conn);
            throw new JobPersistenceException("Unexpected runtime exception: "
                    + e.getMessage(), e);
        } finally {
            try {
                releaseLock(lockName, transOwner);
            } finally {
                cleanupConnection(conn);
            }
        }
    }

transOwner getLockHandler = () obtainLock (CONN, lockName).;

getLockHandler가 ()이고 org.quartz.impl.jdbcjobstore.StdRowLockSemaphore

StdRowLockSemaphore.obtainLock (CONN, lockName)

 public boolean obtainLock(Connection conn, String lockName)
        throws LockException {

//如果所已经是自己的,不需要再去获取数据库锁
        if (!isLockOwner(lockName)) {

            //!!!关键的获取锁的方法    
            executeSQL(conn, lockName, expandedSQL, expandedInsertSQL);
            
            if(log.isDebugEnabled()) {
                log.debug(
                    "Lock '" + lockName + "' given to: "
                            + Thread.currentThread().getName());
            }
            getThreadLocks().add(lockName);
            
        } else if(log.isDebugEnabled()) {
            log.debug(
                "Lock '" + lockName + "' Is already owned by: "
                        + Thread.currentThread().getName());
        }

        return true;
    }

executeSQL (코네티컷, lockName, expandedSQL, expandedInsertSQL는) 할 수있는 잠금을 획득하여, 다음과 같습니다 행 잠금의 MySQL의 InnoDB의 엔진에서 지식에 대한 자세한 내용은 :

MySQL中InnoDB引擎的行锁是通过加在什么上完成(或称实现)的?为什么是这样子的?
答:InnoDB是基于索引来完成行锁
例: select * from tab_with_index where id = 1 for update;
for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,
如果 id 不是索引键那么InnoDB将完成表锁,并发将无从谈起。

코드 acquireNextTrigger를 얻으려면 (코네티컷, noLaterThan는 MAXCOUNT, 시간 창은) 잠금 후,이은, 이러한 컬렉션을 자연스럽게 WorkerTheard에서 ThreadPool이이 처리 할 수 ​​있도록 모든 실행의 컬렉션을 트리거에 도착하는 것입니다

protected List<OperableTrigger> acquireNextTrigger(Connection conn, long noLaterThan, int maxCount, long timeWindow)
        throws JobPersistenceException {
        if (timeWindow < 0) {
          throw new IllegalArgumentException();
        }
        
        List<OperableTrigger> acquiredTriggers = new ArrayList<OperableTrigger>();
        Set<JobKey> acquiredJobKeysForNoConcurrentExec = new HashSet<JobKey>();
        final int MAX_DO_LOOP_RETRY = 3;
        int currentLoopCount = 0;
        do {
            currentLoopCount ++;
            try {
                //获取到一定时间段内的Trigger,时间段是可以配置的
                List<TriggerKey> keys = getDelegate().selectTriggerToAcquire(conn, noLaterThan + timeWindow, getMisfireTime(), maxCount);
                
                // No trigger is ready to fire yet.
                if (keys == null || keys.size() == 0)
                    return acquiredTriggers;

                long batchEnd = noLaterThan;

                for(TriggerKey triggerKey: keys) {
                    //再查一次封装成OperableTrigger,为什么不在getDelegate().selectTriggerToAcquire(conn, noLaterThan + timeWindow, getMisfireTime(), maxCount)里全部查出来?
                    OperableTrigger nextTrigger = retrieveTrigger(conn, triggerKey);
                    if(nextTrigger == null) {
                        continue; // next trigger
                    }

                    JobKey jobKey = nextTrigger.getJobKey();
                    JobDetail job;
                    try {
                        //根据trigger里对应的jobKey获取到jobDetail表里对应的job
                        job = retrieveJob(conn, jobKey);
                    } catch (JobPersistenceException jpe) {
                       ...
                        continue;
                    }
                    
                      ...
                    //更新QRTZ_TRIGGERS里的状态,从STATE_WAITING改成STATE_ACQUIRED
                    int rowsUpdated = getDelegate().updateTriggerStateFromOtherState(conn, triggerKey, STATE_ACQUIRED, STATE_WAITING);
                  ...
                    nextTrigger.setFireInstanceId(getFiredTriggerRecordId());
                    //插入一条即将触发的Trigger到QRTZ_FIRED_TRIGGERS表里
                    getDelegate().insertFiredTrigger(conn, nextTrigger, STATE_ACQUIRED, null);
                 ...
                    acquiredTriggers.add(nextTrigger);
                }
              ...
                // We are done with the while loop.
                break;
            } catch (Exception e) {
               ...
            }
        } while (true);
        
        // Return the acquired trigger list
        return acquiredTriggers;
    }
얼마나 트리거 부분은 얻을 수있을 것입니다

쓰레드 배정 테이블가 qrtz_triggersNEXT_FIRE_TIME을 얻었다 <모든 현재 시간 + 30 + 트리거 이벤트를 m으로 분류, 트리거 제 N 촬영. m 및 n은 각각
배치 될 수있다

  • org.quartz.scheduler.batchTriggerAcquisitionMaxCount = N
  • org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow = m
SQL 분석

MySQL 데이터베이스를 사용하는 지역은 SQL 기록을 엽니 다 사용

SET GLOBAL log_output = "FILE";
SET GLOBAL general_log_file = "C:/logs/query.log";
SET GLOBAL general_log = 'ON';

다음 범주는 SQL을 기록

-- 获取锁

SELECT * FROM QRTZ_LOCKS WHERE SCHED_NAME = 'quartzScheduler' AND LOCK_NAME = 'TRIGGER_ACCESS'
for update;

-- 获取得到一定时间内的trigger
SELECT TRIGGER_NAME, TRIGGER_GROUP, NEXT_FIRE_TIME, PRIORITY FROM QRTZ_TRIGGERS WHERE SCHED_NAME = 'quartzScheduler' AND TRIGGER_STATE = 'WAITING' AND NEXT_FIRE_TIME <= 1545293594800 AND (MISFIRE_INSTR = -1 OR (MISFIRE_INSTR != -1 AND NEXT_FIRE_TIME >= 1545293504801)) ORDER BY NEXT_FIRE_TIME ASC, PRIORITY DESC

-- 上面如果有获取到TRIGGER_NAME, TRIGGER_GROUP,则使用下面的sql获取到QRTZ_TRIGGERS
SELECT * FROM QRTZ_TRIGGERS WHERE SCHED_NAME = 'quartzScheduler' AND TRIGGER_NAME = 'trigger-job2' AND TRIGGER_GROUP = 'group1'

SELECT * FROM QRTZ_SIMPLE_TRIGGERS WHERE SCHED_NAME = 'quartzScheduler' AND TRIGGER_NAME = 'trigger-job2' AND TRIGGER_GROUP = 'group1'

SELECT * FROM QRTZ_JOB_DETAILS WHERE SCHED_NAME = 'quartzScheduler' AND JOB_NAME = 'job2' AND JOB_GROUP = 'group1'

-- 更新QRTZ_TRIGGERS里的trigger的状态
UPDATE QRTZ_TRIGGERS SET TRIGGER_STATE = 'ACQUIRED' WHERE SCHED_NAME = 'quartzScheduler' AND TRIGGER_NAME = 'trigger-job2' AND TRIGGER_GROUP = 'group1' AND TRIGGER_STATE = 'WAITING'

-- 插入QRTZ_FIRED_TRIGGERS的待触发状态
INSERT INTO QRTZ_FIRED_TRIGGERS (SCHED_NAME, ENTRY_ID, TRIGGER_NAME, TRIGGER_GROUP, INSTANCE_NAME, FIRED_TIME, SCHED_TIME, STATE, JOB_NAME, JOB_GROUP, IS_NONCONCURRENT, REQUESTS_RECOVERY, PRIORITY) VALUES('quartzScheduler', 'DESKTOP-TTESTAQ15452876685211545287681141', 'trigger-job2', 'group1', 'DESKTOP-TTESTAQ1545287668521', 1545293564804, 1545293566392, 'ACQUIRED', null, null, 0, 0, 5)

-- connection.commit()

결론

  1. 클러스터, 각 시스템은 스케줄러있을 것이다,
    스케줄러는 쓰레드 스케줄링이, 스레드를 파견하는 테이블로 이동합니다 qrtz_triggersNEXT_FIRE_TIME <현재 시간 + 30을 얻을 + n 개의 이전에 촬영 트리거링 이벤트에 의해 정렬 된 모든 트리거 m. m 및 n은 각각
    배치 될 수있다
  • org.quartz.scheduler.batchTriggerAcquisitionMaxCount = N
  • org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow = m
  1. 행 잠금을 얻기 위해 업데이트 사용
  2. 업데이트 QRTZ_TRIGGERS 상태에서 WAITINGACQUIRED
  3. 불활성 INTO는 QRTZ_FIRED_TRIGGERS이미 트리거 해고하여 작성
  4. connection.commit () 잠금을 해제합니다
  5. 트리거의 WorkerThread에서 스레드를 처리하기 위해 전달됩니다 삭제

추천

출처blog.csdn.net/guo_xl/article/details/85165333