Redis定時任務原理的實現
本文主要是基于 redis 6.2 源碼進行分析定時事件的數據結構和常見操作。
數據結構
在 redis 中通過 aeTimeEvent 結構來創(chuàng)建定時任務事件,代碼如下:
/* Time event structure */
typedef struct aeTimeEvent {
? ? // 標識符
? ? long long id; /* time event identifier. */
? ? // 定時納秒數
? ? monotime when;
? ? // 定時回調函數
? ? aeTimeProc *timeProc;
? ? // 注銷定時器時候的回調函數
? ? aeEventFinalizerProc *finalizerProc;
? ? void *clientData;
? ? struct aeTimeEvent *prev;
? ? struct aeTimeEvent *next;
? ? int refcount; /* refcount to prevent timer events from being
? ?? ??? ? ? * freed in recursive time event calls. */
} aeTimeEvent;常見操作
1. 創(chuàng)建定時事件
redis 中最重要的定時函數且是周期執(zhí)行的函數,使用的是 serverCron 函數。在 redis 中由于定時任務比較少,因此并沒有嚴格的按照過期時間來排序的,而是按照 id自增 + 頭插法 來保證基本有序。
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
? serverPanic("Can't create event loop timers.");
? exit(1);
}
//創(chuàng)建定時器對象
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
? ? ? ? aeTimeProc *proc, void *clientData,
? ? ? ? aeEventFinalizerProc *finalizerProc)
{
? ? long long id = eventLoop->timeEventNextId++;
? ? aeTimeEvent *te;
? ? te = zmalloc(sizeof(*te));
? ? if (te == NULL) return AE_ERR;
? ? te->id = id;
? ? te->when = getMonotonicUs() + milliseconds * 1000;
? ? te->timeProc = proc;
? ? te->finalizerProc = finalizerProc;
? ? te->clientData = clientData;
? ? te->prev = NULL;
? ? // 頭插法?
? ? te->next = eventLoop->timeEventHead;
? ? te->refcount = 0;
? ? if (te->next)
? ? ? ? te->next->prev = te;
? ? eventLoop->timeEventHead = te;
? ? return id;
}
2. 觸發(fā)定時事件
redis 中是采用 IO 復用來進行定時任務的。
查找距離現在最近的定時事件,見 usUntilEarliestTimer
?
/* How many microseconds until the first timer should fire.
?* If there are no timers, -1 is returned.
?*
?* Note that's O(N) since time events are unsorted.
?* Possible optimizations (not needed by Redis so far, but...):
?* 1) Insert the event in order, so that the nearest is just the head.
?* ? ?Much better but still insertion or deletion of timers is O(N).
?* 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
?*/
static int64_t usUntilEarliestTimer(aeEventLoop *eventLoop) {
? ? aeTimeEvent *te = eventLoop->timeEventHead;
? ? if (te == NULL) return -1;
? ? aeTimeEvent *earliest = NULL;
? ? while (te) {
? ? ? ? if (!earliest || te->when < earliest->when)
? ? ? ? ? ? earliest = te;
? ? ? ? te = te->next;
? ? }
? ? monotime now = getMonotonicUs();
? ? return (now >= earliest->when) ? 0 : earliest->when - now;
}
?這里時間復雜度可能比較高,實際中需要結合具體場景使用。
更新剩余過期時間,想想為啥呢?因為我們前面提到過,IO 復用有可能因為 IO 事件返回,所以需要更新。
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
? usUntilTimer = usUntilEarliestTimer(eventLoop);
if (usUntilTimer >= 0) {
? tv.tv_sec = usUntilTimer / 1000000;
? tv.tv_usec = usUntilTimer % 1000000;
? tvp = &tv;
} else {
? if (flags & AE_DONT_WAIT) {
? ? // 不等待
? ? tv.tv_sec = tv.tv_usec = 0;
? ? tvp = &tv;
? } else {
? ? /* Otherwise we can block */
? ? tvp = NULL; /* wait forever */
? }
}3. 執(zhí)行定時事件
一次性的執(zhí)行完直接刪除,周期性的執(zhí)行完在重新添加到鏈表。
/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
? int processed = 0;
? aeTimeEvent *te;
? long long maxId;
? te = eventLoop->timeEventHead;
? maxId = eventLoop->timeEventNextId-1;
? monotime now = getMonotonicUs();
??
? // 刪除定時器
? while(te) {
? ? long long id;
?? ??? ?
? ? // 下一輪中對事件進行刪除
? ? /* Remove events scheduled for deletion. */
? ? if (te->id == AE_DELETED_EVENT_ID) {
? ? ? aeTimeEvent *next = te->next;
? ? ? /* If a reference exists for this timer event,
? ? ? ? ? ? ?* don't free it. This is currently incremented
? ? ? ? ? ? ?* for recursive timerProc calls */
? ? ? if (te->refcount) {
? ? ? ? te = next;
? ? ? ? continue;
? ? ? }
? ? ? if (te->prev)
? ? ? ? te->prev->next = te->next;
? ? ? else
? ? ? ? eventLoop->timeEventHead = te->next;
? ? ? if (te->next)
? ? ? ? te->next->prev = te->prev;
? ? ? if (te->finalizerProc) {
? ? ? ? te->finalizerProc(eventLoop, te->clientData);
? ? ? ? now = getMonotonicUs();
? ? ? }
? ? ? zfree(te);
? ? ? te = next;
? ? ? continue;
? ? }
? ??
? ? if (te->id > maxId) {
? ? ? te = te->next;
? ? ? continue;
? ? }
? ? if (te->when <= now) {
? ? ? int retval;
? ? ? id = te->id;
? ? ? te->refcount++;
? ? ? // timeProc 函數返回值 retVal 為時間事件執(zhí)行的間隔
? ? ? retval = te->timeProc(eventLoop, id, te->clientData);
? ? ? te->refcount--;
? ? ? processed++;
? ? ? now = getMonotonicUs();
? ? ? if (retval != AE_NOMORE) {
? ? ? ? te->when = now + retval * 1000;
? ? ? } else {
? ? ? ? // 如果超時了,那么標記為刪除
? ? ? ? te->id = AE_DELETED_EVENT_ID;
? ? ? }
? ? }
? ? // 執(zhí)行下一個
? ? te = te->next;
? }
? return processed;
}總結
優(yōu)點:實現簡單
缺點:如果定時任務很多,效率比較低。
到此這篇關于Redis定時任務原理的實現的文章就介紹到這了,更多相關Redis定時任務內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
一文詳解Redis在Ubuntu系統(tǒng)上的安裝步驟
安裝redis在Ubuntu上有多種方法,下面這篇文章主要給大家介紹了關于Redis在Ubuntu系統(tǒng)上安裝的相關資料,文中通過圖文以及代碼介紹的非常詳細,需要的朋友可以參考下2024-07-07

