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