• Redis4.0新特性之-大KEY删除


    接上一篇,我们得知了redis中存在大KEY,那么这个大KEY如何删除呢?本文将从源码角度分析Redis4.0带来的新特性。

    在Redis中,对于大KEY的删除一直是个比较头疼的问题,为了不影响服务,我们通常需要自己实现工具来删除大KEY,或者在业务低峰期进行删除操作。 
    为了解决以上问题, Redis 4.0 新添加了 UNLINK 命令用于执行大KEY异步删除。那么这个异步删除的背后的逻辑是什么?

    通过源码我们可以的得知以下信息: 
    当我们调用异步删除UNLINK命令时:

    1. 释放掉Expire Dicti 对 K-V 的引用
    2. 释放Main Dict 对 K-V 的引用,同时记录下这个K-V 的 Entry地址
    3. 计算释放掉这个V 所需要的代价,计算方法如下: 
      3.1 如果这个V 是一个 String 类型,则代价为 1 
      3.2 如果这个V 是一个复合类型,则代价为 该复合类型的长度,比如,list 则为 llen 的结果,hash 则为 hlen 的结果 …
    4. 根据得到的代价值,和代价阈值比对,如果小于 64 则,可以直接释放掉K-V 内存空间;如果大于 64 则,把该V 放入lazyfree 队列中,同时启动一个BIO后台JOB进行删除 
      4.1 在后台线程对 V 进行删除时,也是根据不同类型的 V 做不同的操作 
      4.2 如果是 LIST 类型,则根据LIST 长度,则直接释放空间。 
      4.3 如果是 SET 类型,并且数据结构采用 HASH 表存储,那么遍历整个hash表,逐个释放 k,v空间;如果数据结构采用 intset,则直接释放空间即可 
      4.4 如果是 ZSET 类型,并且数据结构采用 SKIPLIST 存储,由于 SKIPLIST 底层采用 HASH + skiplist 存储,那么会先释放掉 SKIPLIST 中 hash 存储空间,再释放掉 SKIPLIST 中 skiplist 部分; 如果数据结构采用 ZIPLIST 存储,则直接释放空间。 
      4.5 如果是 HASH 类型,并且数据结构采用 HASH表存储,则遍历整个hash表,逐个释放 k,v空间;如果数据结构采用 ZIPLIST 存储,则直接释放空间。
    5. 设置 V 值等于NULL
    6. 释放掉 K-V 空间

    异步删除代码如下 :

    int dbAsyncDelete(redisDb *db, robj *key) {
    /* */
    if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
    /* 在Main Dict 链表去掉引用,得到K-V entryDict */
    dictEntry *de = dictUnlink(db->dict,key->ptr);
    if (de) {
    robj *val = dictGetVal(de);
    size_t free_effort = lazyfreeGetFreeEffort(val);
    /* 计算DEL key 的代价,根据代价决定是否采用异步删除方式 */
    if (free_effort > LAZYFREE_THRESHOLD) {
    atomicIncr(lazyfree_objects,1,lazyfree_objects_mutex);
    bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
    dictSetVal(db->dict,de,NULL);
    }
    }
    /* 释放K-V空间,或者采用了异步删除方式,只需要释放Key空间 */
    if (de) {
    dictFreeUnlinkedEntry(db->dict,de);
    if (server.cluster_enabled) slotToKeyDel(key);
    return 1;
    } else {
    return 0;
    }
    }

    /* 释放LIST 空间 */
    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);
    }

    /* 释放HASH表空间 */
    static int _dictClear(dict *ht) {
    unsigned long i;
    for (i = 0; i < ht->size && ht->used > 0; i++) {
    dictEntry *he, *nextHe;
    if ((he = ht->table[i]) == NULL) continue;
    while(he) {
    nextHe = he->next;
    dictFreeEntryKey(ht, he);
    dictFreeEntryVal(ht, he);
    free(he);
    ht->used--;
    he = nextHe;
    }
    }
    free(ht->table);
    _dictReset(ht);
    return DICT_OK; /* never fails */
    }

    由于异步删除实际上是先在MAIN DICT 里边把 这个K,V 的引用关系去掉了,所以当我们再次查询这个Key 的时候是查不到的,然后在慢慢释放Value 所占用的内存空间。

    我们发现在异步进行删除的时候,不管是删除 HASH也好,还是QUICKLIST 也罢,这部分其实并没有进行一个速度的控制,只是起了一个线程让他去删除,能跑多快就跑多快,这样可能会导致我们在进行删除的时候CPU飙高。

    这个删除大KEY是在Master 上进行的,如果这个节点有Slave呢?slave 会进行怎样的操作?同样根据代码可以发现,我们在执行UNLINK操作时,实际上在 AOF 和 通知Slave的时候只是发送了一条DEL xxkey 命令,当slave 收到del命令时,会采取以上同样的判断对这个key进行删除。

    notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[j],c->db->id);
  • 相关阅读:
    ubuntu虚拟机下提示Network service discovery disabled
    jq简单实现选项卡--tab
    sublime text 出现错误error trying to parse file:Unexpected trailing characters in PackagesUserDefault(Windows).sublime-keymap:1:54
    sublime text 3设置快捷键让html文件在浏览器打开
    自动刷新浏览器
    项目---开饭了
    关于面试
    软件测试学习-关于三次握手与四次挥手的理解
    软件测试学习-笔记
    软件测试学习-数据库基础
  • 原文地址:https://www.cnblogs.com/svan/p/7054129.html
Copyright © 2020-2023  润新知