Redis隨記--Lazy Free特性


Lazy Free特性

惰性刪除或延遲釋放(Lazy Free),指在刪除KEY時,采用異步方式延遲釋放EKY所使用的內存,將該操作交給單獨的子線程BIO(backgroup I/O)進行處理,避免在同步方式刪除KEY對Redis主線程的長期占用而影響系統可用性。

在刪除超大KEY如單個EKY占用內存過多或單個KEY包含過多元素時,同步刪除方式會導致Redis服務長期不可用,設置會引發自從主動故障切換。

在Redis 4.0版本開始提供惰性刪除特性。

Lazy Free使用場景

  • 主動使用惰性刪除特性
  • 被動使用惰性刪除特性

主動惰性刪除操作

  • UNLINK命令
使用UNLINK刪除集合鍵時,會按照集合鍵的元素去估算釋放該KEY的成本,
如果釋放成本超過LAZYFREE_THRESHOLD,則會采用Lazy Free方式進行處理。
  • FLUSHALL/FLUSHDB
通過ASYNC選項來設置FLUSHALL操作或FLUSHDB操作是否采用Lazy Free方式處理。

被動使用惰性刪除

被動使用惰性刪除主要有下面四類場景,並通過四個參數進行控制:

## 在內存到達最大內存需要逐出數據時使用
## 建議關閉,避免內存未及時釋放
lazyfree-lazy-eviction no

## 在KEY過期時使用
## 建議開啟
lazyfree-lazy-expire no

## 隱式刪除服務器數據時,如RENAME操作
## 建議開啟
lazyfree-lazy-server-del no

## 在對從庫進行全量數據同步時
## 建議關閉
slave-lazy-flush no

UNLINK命令涉及代碼

void delCommand(client *c) {
    delGenericCommand(c,0);
}

void unlinkCommand(client *c) {
    delGenericCommand(c,1);
}

/* This command implements DEL and LAZYDEL. */
void delGenericCommand(client *c, int lazy) {
    int numdel = 0, j;

    for (j = 1; j < c->argc; j++) {
        expireIfNeeded(c->db,c->argv[j]);
        int deleted  = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
                              dbSyncDelete(c->db,c->argv[j]);
        if (deleted) {
            signalModifiedKey(c->db,c->argv[j]);
            notifyKeyspaceEvent(NOTIFY_GENERIC,
                "del",c->argv[j],c->db->id);
            server.dirty++;
            numdel++;
        }
    }
    addReplyLongLong(c,numdel);
}

/* Delete a key, value, and associated expiration entry if any, from the DB.
 * If there are enough allocations to free the value object may be put into
 * a lazy free list instead of being freed synchronously. The lazy free list
 * will be reclaimed in a different bio.c thread. */
#define LAZYFREE_THRESHOLD 64
int dbAsyncDelete(redisDb *db, robj *key) {
    /* Deleting an entry from the expires dict will not free the sds of
     * the key, because it is shared with the main dictionary. */
    if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);

    /* If the value is composed of a few allocations, to free in a lazy way
     * is actually just slower... So under a certain limit we just free
     * the object synchronously. */
    /* 進行Unlink操作,但不進行FREE操作 */
    dictEntry *de = dictUnlink(db->dict,key->ptr);
    if (de) {
        robj *val = dictGetVal(de);
        /* 計算lazy free當前對象的成本 */
        size_t free_effort = lazyfreeGetFreeEffort(val);

        /* If releasing the object is too much work, do it in the background
         * by adding the object to the lazy free list.
         * Note that if the object is shared, to reclaim it now it is not
         * possible. This rarely happens, however sometimes the implementation
         * of parts of the Redis core may call incrRefCount() to protect
         * objects, and then call dbDelete(). In this case we'll fall
         * through and reach the dictFreeUnlinkedEntry() call, that will be
         * equivalent to just calling decrRefCount(). */
        /* 當lazy free成本超過64時,使用后台線程處理 */
        if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
            /* 對需要lazy free的對象數量+1 */
            atomicIncr(lazyfree_objects,1);
            /* 使用BIO子線程后台處理 */
            bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
            /* 將對象設置為NULL */
            dictSetVal(db->dict,de,NULL);
        }
    }

    /* Release the key-val pair, or just the key if we set the val
     * field to NULL in order to lazy free it later. */
    if (de) {
        dictFreeUnlinkedEntry(db->dict,de);
        if (server.cluster_enabled) slotToKeyDel(key);
        return 1;
    } else {
        return 0;
    }
}
/* Process the job accordingly to its type. */
if (type == BIO_CLOSE_FILE) {
	close((long)job->arg1);
} else if (type == BIO_AOF_FSYNC) {
	redis_fsync((long)job->arg1);
} else if (type == BIO_LAZY_FREE) {
	/* What we free changes depending on what arguments are set:
	 * arg1 -> free the object at pointer.
	 * arg2 & arg3 -> free two dictionaries (a Redis DB).
	 * only arg3 -> free the skiplist. */
	if (job->arg1)
		lazyfreeFreeObjectFromBioThread(job->arg1);
	else if (job->arg2 && job->arg3)
		lazyfreeFreeDatabaseFromBioThread(job->arg2,job->arg3);
	else if (job->arg3)
		lazyfreeFreeSlotsMapFromBioThread(job->arg3);
} else {
	serverPanic("Wrong job type in bioProcessBackgroundJobs().");
}
/* Release objects from the lazyfree thread. It's just decrRefCount()
 * updating the count of objects to release. */
void lazyfreeFreeObjectFromBioThread(robj *o) {
    decrRefCount(o);
    atomicDecr(lazyfree_objects,1);
}

void decrRefCount(robj *o) {
    if (o->refcount == 1) {
        switch(o->type) {
        case OBJ_STRING: freeStringObject(o); break;
        case OBJ_LIST: freeListObject(o); break;
        case OBJ_SET: freeSetObject(o); break;
        case OBJ_ZSET: freeZsetObject(o); break;
        case OBJ_HASH: freeHashObject(o); break;
        case OBJ_MODULE: freeModuleObject(o); break;
        case OBJ_STREAM: freeStreamObject(o); break;
        default: serverPanic("Unknown object type"); break;
        }
        zfree(o);
    } else {
        if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
        if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
    }
}

/* Free list object. */
void freeListObject(robj *o) {
    if (o->encoding == OBJ_ENCODING_QUICKLIST) {
        quicklistRelease(o->ptr);
    } else {
        serverPanic("Unknown list encoding type");
    }
}

/* Free entire quicklist. */
void quicklistRelease(quicklist *quicklist) {
    unsigned long len;
    quicklistNode *current, *next;

    current = quicklist->head;
    len = quicklist->len;
    while (len--) {
        next = current->next;

        zfree(current->zl);
        quicklist->count -= current->count;

        zfree(current);

        quicklist->len--;
        current = next;
    }
    zfree(quicklist);
}

UNLINK命令學習

  • unlink命令和del命令都調用unlinkCommand函數,使用參數lazy來標識是否使用Lazy Free方式。
  • Lazy Free方式會調用dbAsyncDelete函數來處理,處理過程中會計算Lazy Free方式釋放對象的成本,只有超過特定閾值,才會采用Lazy Free方式。
  • Lazy Free方式會調用bioCreateBackgroundJob函數來使用BIO線程后台異步釋放對象。
  • BIO線程后台處理時會采用DEL相同的方式來釋放對象,唯一區別是使用后台線程不會阻塞其他業務操作。
  • 當Redis對象執行UNLINK操作后,對應的KEY會被立即刪除,不會被后續命令訪問到,對應的VALUE采用異步方式來清理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM