SEDA (Staged event-driven architecture)
論文在此:
The Staged Event-Driven Architecture for Highly-Concurrent Server Applications
尚未閱讀,從字面理解
- 有event, 則有event queue
- 有event, 則有event handler
- 有Staged,劃分成不同階段,將任務進行細分,每個細分的階段使用不一樣的線程池配置
租借線程
簡而言之,概而括之,推而廣之,系統中不是單個線程池來運行系統中的任務線程,而是多個線程池來handle不同類型的任務,比如數據庫的連接,文件IO的讀寫,網絡IO的讀寫,對象的序列化等等。
不管是單個線程池還是多個線程池,池的配置毫無疑問影響池的性能和效用。在前一篇文章中,我提到單線程吞吐量來估計系統的線程數目(suggestedNumberOfWorkerThread)。本篇文章會介紹另外一個小技巧,線程的租借。
運行任務
如果當前的線程池(m_jobExecutor)拋出RejectedExecutionExecution,則嘗試從其他線程池(Processor)租借(lendAWorkerThread)
private ThreadPoolExecutor m_jobExecutor;
void tryToExecute(Job job)
{
try
{
m_jobExecutor.execute(job);
} catch (RejectedExecutionException e)
{
// Iterate through all processors starting from random point
List allProcessors = getAll();
int startIndex = m_random.nextInt(allProcessors.size());
int i = startIndex;
do
{
Processor processor = allProcessors.get(i);
i = (i + 1) % allProcessors.size();
if (processor == this)
{
continue;
}
if (processor.lendAWorkerThread(job))
{
// We found a Good Samaritan
log.info(getName() + " has lent a worker thread from the "
+ processor.getName());
return;
}
} while (i != startIndex);
// Nobody helped us, re-throw the exception
throw e;
}
}
線程租借條件
如果最大線程池大小和預估使用線程數目(suggestedNumberOfWorkerThread)相差無幾(10%),說明當下線程池也將會很繁忙,不能租借。
如果當前線程池中的已經90%的active (m_jobExecutor.getActiveCount()),說明當下線程池已經很繁忙,不能租借。
boolean lendAWorkerThread(LocateJob job)
{
if (!isStartingOrStarted())
{
return false;
}
int maxPoolSize = m_jobExecutor.getMaximumPoolSize();
if (maxPoolSize - getSuggestedNumberOfWorkerThreads() < (maxPoolSize / 10))
{
// We do not have capacity
return false;
}
if ((maxPoolSize - m_jobExecutor.getActiveCount()) < (maxPoolSize / 10))
{
// We have capacity but we lent (or consumed) almost all of it
return false;
}
try
{
m_jobExecutor.execute(job);
} catch (Throwable e)
{
// We couldn't lent a worker thread
return false;
}
return true;
}
線程池什麼時候會拋出RejectedExecutionException?
當線程池使用了有界隊列和有界池,隊列已經被填滿,池中的線程也已經全部在運行。(參見javadoc
reject task)
本文的ThreadPoolExecutor使用SynchronousQueue,只要線程池被耗盡,新增任務就會拋出RejectedExecutionException
m_jobExecutor = new ThreadPoolExecutor(m_config.getThreadPoolMaxSize(),
m_config.getThreadPoolMaxSize(), getPollingIntervalInMillis(),
TimeUnit.MILLISECONDS, new SynchronousQueue(),
new DaemonThreadFactory(getName()))
{
protected void beforeExecute(Thread t, Runnable r)
{
super.beforeExecute(t, r);
try
{
(Job) r).setExecutionThread(t);
} catch (Throwable e)
{
getLog().error(getName() + ": internal error: " + e);
}
}
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
}
};
m_jobExecutor.prestartAllCoreThreads();
m_jobExecutor.allowCoreThreadTimeOut(true);
線程池的動態調整
在下一篇文章,我將介紹一種超級大殺器: sliding windows statistical,根據過去線程的使用狀況,來配置下一個windows窗口的線程池(run time suggestedNumberOfWorkerThread),從而形成彈性線程池(elastic thread pool),在系統訪問高峯時期,自動增加線程池的數目;在peak回落時,自動減少線程池的數目。