爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      Redis(四):del/unlink 命令源码解析

      首页 知识中心 软件开发 文章详情页

      Redis(四):del/unlink 命令源码解析

      2024-12-02 09:45:53 阅读次数:23

      内存,异步

        上一篇文章从根本上理解了set/get的处理过程,相当于理解了 增、改、查的过程,现在就差一个删了。本篇我们来看一下删除过程。

        对于客户端来说,删除操作无需区分何种数据类型,只管进行 del 操作即可。

      零、删除命令 del 的定义

        主要有两个: del/unlink, 差别是 unlink 速度会更快, 因为其使用了异步删除优化模式, 其定义如下:

      // 标识只有一个 w, 说明就是一个普通的写操作,没啥好说的
          {"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0}
          // 标识为 wF, 说明它是一个快速写的操作,其实就是有一个异步优化的过程,稍后详解
          {"unlink",unlinkCommand,-2,"wF",0,NULL,1,-1,1,0,0}

      一、delCommand

        delCommand 的作用就是直接删除某个 key 的数据,释放内存即可。

      // db.c, del 命令处理    
      void delCommand(client *c) {
          // 同步删除
          delGenericCommand(c,0);
      }
      
      /* 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]);
              // 此处分同步删除和异步删除, 主要差别在于对于复杂数据类型的删除方面,如hash,list,set...
              // 针对 string 的删除是完全一样的
              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++;
              }
          }
          // 响应删除数据量, 粒度到 key 级别
          addReplyLongLong(c,numdel);
      }

        框架代码一看即明,只是相比于我们普通的删除是多了不少事情。否则也不存在设计了。

      二、unlinkCommand

        如下,其实和del是一毛一样的,仅是变化了一个 lazy 标识而已。

      // db.c, unlink 删除处理
      void unlinkCommand(client *c) {
          // 与 del 一致,只是 lazy 标识不一样
          delGenericCommand(c,1);
      }

      三、删除数据过程详解

        删除数据分同步和异步两种实现方式,道理都差不多,只是一个是后台删一个是前台删。我们分别来看看。

      1. 同步删除 dbSyncDelete

        同步删除很简单,只要把对应的key删除,val删除就行了,如果有内层引用,则进行递归删除即可。

      // db.c, 同步删除数据
      /* Delete a key, value, and associated expiration entry if any, from the DB */
      int dbSyncDelete(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. */
          // 首先从 expires 队列删除,然后再从 db->dict 中删除
          if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
          if (dictDelete(db->dict,key->ptr) == DICT_OK) {
              if (server.cluster_enabled) slotToKeyDel(key);
              return 1;
          } else {
              return 0;
          }
      }
      // dict.c, 如上, 仅仅是 dictDelete() 就可以了,所以真正的删除动作是在 dict 中实现的。
      int dictDelete(dict *ht, const void *key) {
          // nofree: 0, 即要求释放内存
          return dictGenericDelete(ht,key,0);
      }
      // dict.c, nofree: 0:要释放相应的val内存, 1:不释放相应val内存只删除key
      /* Search and remove an element */
      static int dictGenericDelete(dict *d, const void *key, int nofree)
      {
          unsigned int h, idx;
          dictEntry *he, *prevHe;
          int table;
      
          if (d->ht[0].size == 0) return DICT_ERR; /* d->ht[0].table is NULL */
          if (dictIsRehashing(d)) _dictRehashStep(d);
          h = dictHashKey(d, key);
          // ht[0] 和 ht[1] 如有可能都进行扫描
          for (table = 0; table <= 1; table++) {
              idx = h & d->ht[table].sizemask;
              he = d->ht[table].table[idx];
              prevHe = NULL;
              while(he) {
                  if (dictCompareKeys(d, key, he->key)) {
                      /* Unlink the element from the list */
                      if (prevHe)
                          prevHe->next = he->next;
                      else
                          d->ht[table].table[idx] = he->next;
                      // no nofree, 就是要 free 内存咯
                      if (!nofree) {
                          // 看起来 key/value 需要单独释放内存哦
                          dictFreeKey(d, he);
                          dictFreeVal(d, he);
                      }
                      zfree(he);
                      d->ht[table].used--;
                      return DICT_OK;
                  }
                  prevHe = he;
                  he = he->next;
              }
              // 如果没有进行 rehashing, 只需扫描0就行了
              if (!dictIsRehashing(d)) break;
          }
          return DICT_ERR; /* not found */
      }

        其实对于有GC收集器的语言来说,根本不用关注内存的释放问题,自有后台工具处理,然而对于 c 语言这种级别语言,则是需要自行关注内存的。这也是本文存在的意义,不然对于一个 hash 表的元素删除操作,如上很难吗?并没有。

        下面,我们就来看看 redis 是如何具体释放内存的吧。

      // dict.h, 释放key, value 的逻辑也是非常简单,用一个宏就定义好了
      // 释放依赖于 keyDestructor, valDestructor
      #define dictFreeKey(d, entry) \
          if ((d)->type->keyDestructor) \
              (d)->type->keyDestructor((d)->privdata, (entry)->key)
      #define dictFreeVal(d, entry) \
          if ((d)->type->valDestructor) \
              (d)->type->valDestructor((d)->privdata, (entry)->v.val)
      // 所以,我们有必要回去看看 key,value 的析构方法
      // 而这,又依赖于具体的数据类型,也就是你在 setXXX 的时候用到的数据类型
      // 我们看一下这个 keyDestructor,valDestructor 初始化的样子
      // server.c  kv的析构函数定义
      /* Db->dict, keys are sds strings, vals are Redis objects. */
      dictType dbDictType = {
          dictSdsHash,                /* hash function */
          NULL,                       /* key dup */
          NULL,                       /* val dup */
          dictSdsKeyCompare,          /* key compare */
          dictSdsDestructor,          /* key destructor */
          dictObjectDestructor   /* val destructor */
      };
      
      // 1. 先看看 key destructor, key 的释放
      // server.c, 直接调用 sds 提供的服务即可
      void dictSdsDestructor(void *privdata, void *val)
      {
          DICT_NOTUSED(privdata);
          // sds 直接释放key就行了
          sdsfree(val);
      }
      // sds.c, 真正释放 value 内存
      /* Free an sds string. No operation is performed if 's' is NULL. */
      void sdsfree(sds s) {
          if (s == NULL) return;
          // zfree, 确实很简单嘛, 因为 sds 是连续的内存空间,直接使用系统提供的方法即可删除
          s_free((char*)s-sdsHdrSize(s[-1]));
      }
      
      // 2. value destructor 对value的释放, 如果说 key 一定是string格式的话,value可主不一定了,因为 redis提供丰富的数据类型呢
      // server.c
      void dictObjectDestructor(void *privdata, void *val)
      {
          DICT_NOTUSED(privdata);
      
          if (val == NULL) return; /* Lazy freeing will set value to NULL. */
          decrRefCount(val);
      }
      // 减少 value 的引用计数
      void decrRefCount(robj *o) {
          if (o->refcount == 1) {
              switch(o->type) {
                  // string 类型
                  case OBJ_STRING: freeStringObject(o); break;
                  // list 类型
                  case OBJ_LIST: freeListObject(o); break;
                  // set 类型
                  case OBJ_SET: freeSetObject(o); break;
                  // zset 类型
                  case OBJ_ZSET: freeZsetObject(o); break;
                  // hash 类型
                  case OBJ_HASH: freeHashObject(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--;
          }
      }

        额,可以看出,对key的释放自然是简单之极。而对 value 则谨慎许多,首先它表面上只对引用做减操作。只有发只剩下1个引用即只有当前引用的情况下,本次释放就是最后一次释放,所以才会回收内存。

      // 在介绍不同数据类型的内存释放前,我们可以先来看下每个元素的数据结构
      // dict.h
      typedef struct dictEntry {
          // 存储 key 字段内容
          void *key;
          // 用一个联合体存储value
          union {
              // 存储数据时使用 *val 存储
              void *val;
              uint64_t u64;
              // 存储过期时间时使用该字段
              int64_t s64;
              // 存储 score 时使用
              double d;
          } v;
          // 存在hash冲突时,作链表使用
          struct dictEntry *next;
      } dictEntry;
      
      // 1. string 类型的释放
      // object.c
      void freeStringObject(robj *o) {
          // 直接调用 sds服务释放
          if (o->encoding == OBJ_ENCODING_RAW) {
              sdsfree(o->ptr);
          }
      }
      
      // 2. list 类型的释放
      // object.c
      void freeListObject(robj *o) {
          switch (o->encoding) {
          case OBJ_ENCODING_QUICKLIST:
              quicklistRelease(o->ptr);
              break;
          default:
              serverPanic("Unknown list encoding type");
          }
      }
      // quicklist.c
      /* Free entire quicklist. */
      void quicklistRelease(quicklist *quicklist) {
          unsigned long len;
          quicklistNode *current, *next;
      
          current = quicklist->head;
          len = quicklist->len;
          // 链表依次迭代就可以释放完成了
          while (len--) {
              next = current->next;
              // 释放list具体值
              zfree(current->zl);
              quicklist->count -= current->count;
              // 释放list对象
              zfree(current);
      
              quicklist->len--;
              current = next;
          }
          zfree(quicklist);
      }
      
      // 3. set 类型的释放
      // object.c, set 分两种类型, ht, intset
      void freeSetObject(robj *o) {
          switch (o->encoding) {
          case OBJ_ENCODING_HT:
              // hash 类型则需要删除每个 hash 的 kv
              dictRelease((dict*) o->ptr);
              break;
          case OBJ_ENCODING_INTSET:
              // intset 直接释放
              zfree(o->ptr);
              break;
          default:
              serverPanic("Unknown set encoding type");
          }
      }
      // dict.c, 
      /* Clear & Release the hash table */
      void dictRelease(dict *d)
      {
          // ht[0],ht[1] 依次清理
          _dictClear(d,&d->ht[0],NULL);
          _dictClear(d,&d->ht[1],NULL);
          zfree(d);
      }
      // dict.c, 
      /* Destroy an entire dictionary */
      int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {
          unsigned long i;
      
          /* Free all the elements */
          for (i = 0; i < ht->size && ht->used > 0; i++) {
              dictEntry *he, *nextHe;
      
              if (callback && (i & 65535) == 0) callback(d->privdata);
              // 元素为空,hash未命中,但只要 used > 0, 代表就还有需要删除的元素存在
              // 其实对于只有少数几个元素的情况下,这个效率就呵呵了
              if ((he = ht->table[i]) == NULL) continue;
              while(he) {
                  nextHe = he->next;
                  // 这里的释放 kv 逻辑和前面是一致的
                  // 看起来像是递归,其实不然,因为redis不存在数据类型嵌套问题,比如 hash下存储hash, 所以不会存在递归
                  // 具体结构会在后续解读到
                  dictFreeKey(d, he);
                  dictFreeVal(d, he);
                  zfree(he);
                  ht->used--;
                  he = nextHe;
              }
          }
          /* Free the table and the allocated cache structure */
          zfree(ht->table);
          /* Re-initialize the table */
          _dictReset(ht);
          return DICT_OK; /* never fails */
      }
      
      // 4. hash 类型的释放
      // object.c, hash和set其实是很相似的,代码也做了大量的复用
      void freeHashObject(robj *o) {
          switch (o->encoding) {
          case OBJ_ENCODING_HT:
              // ht 形式与set一致
              dictRelease((dict*) o->ptr);
              break;
          case OBJ_ENCODING_ZIPLIST:
              // ziplist 直接释放
              zfree(o->ptr);
              break;
          default:
              serverPanic("Unknown hash encoding type");
              break;
          }
      }
      
      // 5. zset 类型的释放
      // object.c, zset 的存储形式与其他几个
      void freeZsetObject(robj *o) {
          zset *zs;
          switch (o->encoding) {
          case OBJ_ENCODING_SKIPLIST:
              zs = o->ptr;
              // 释放dict 数据, ht 0,1 的释放
              dictRelease(zs->dict);
              // 释放skiplist 数据, 主要看下这个
              zslFree(zs->zsl);
              zfree(zs);
              break;
          case OBJ_ENCODING_ZIPLIST:
              zfree(o->ptr);
              break;
          default:
              serverPanic("Unknown sorted set encoding");
          }
      }
      // t_zset.c, 释放跳表数据
      /* Free a whole skiplist. */
      void zslFree(zskiplist *zsl) {
          zskiplistNode *node = zsl->header->level[0].forward, *next;
      
          zfree(zsl->header);
          while(node) {
              // 基于第0层数据释放,也基于第0层做迭代,直到删除完成
              // 因为其他层数据都是引用的第0层的数据,所以释放时无需关注
              next = node->level[0].forward;
              zslFreeNode(node);
              node = next;
          }
          zfree(zsl);
      }
      // t_zset 也很简单,只是把 node.ele 释放掉,再把自身释放到即可
      // 这样的删除方式依赖于其存储结构,咱们后续再聊
      /* Free the specified skiplist node. The referenced SDS string representation
       * of the element is freed too, unless node->ele is set to NULL before calling
       * this function. */
      void zslFreeNode(zskiplistNode *node) {
          sdsfree(node->ele);
          zfree(node);
      }

      2. 异步删除过程

        异步删除按理说会更复杂,更有意思些。只不过我们前面已经把核心的东西撸了个遍,这剩下的也不多了。

      // lazyfree.c, 
      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. */
          dictEntry *de = dictFind(db->dict,key->ptr);
          if (de) {
              robj *val = dictGetVal(de);
              size_t free_effort = lazyfreeGetFreeEffort(val);
      
              /* If releasing the object is too much work, let's put it into the
               * lazy free list. */
              // 其实异步方法与同步方法的差别在这,即要求 删除的元素影响须大于某阀值(64)
              // 否则按照同步方式直接删除,因为那样代价更小
              if (free_effort > LAZYFREE_THRESHOLD) {
                  // 异步释放+1,原子操作
                  atomicIncr(lazyfree_objects,1,&lazyfree_objects_mutex);
                  // 将 value 的释放添加到异步线程队列中去,后台处理, 任务类型为 异步释放内存
                  bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
                  // 设置val为NULL, 以便在外部进行删除时忽略释放value相关内存
                  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 (dictDelete(db->dict,key->ptr) == DICT_OK) {
              if (server.cluster_enabled) slotToKeyDel(key);
              return 1;
          } else {
              return 0;
          }
      }
      // bio.c, 添加异步任务到线程中, 类型由type决定,线程安全地添加
      // 然后嘛,后台线程就不会停地运行了任务了
      void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
          struct bio_job *job = zmalloc(sizeof(*job));
      
          job->time = time(NULL);
          job->arg1 = arg1;
          job->arg2 = arg2;
          job->arg3 = arg3;
          // 上锁操作
          pthread_mutex_lock(&bio_mutex[type]);
          listAddNodeTail(bio_jobs[type],job);
          bio_pending[type]++;
          // 唤醒任务线程
          pthread_cond_signal(&bio_newjob_cond[type]);
          pthread_mutex_unlock(&bio_mutex[type]);
      }
      // bio.c, 后台线程任务框架,总之还是有事情可做了。
      void *bioProcessBackgroundJobs(void *arg) {
          struct bio_job *job;
          unsigned long type = (unsigned long) arg;
          sigset_t sigset;
      
          /* Check that the type is within the right interval. */
          if (type >= BIO_NUM_OPS) {
              serverLog(LL_WARNING,
                  "Warning: bio thread started with wrong type %lu",type);
              return NULL;
          }
      
          /* Make the thread killable at any time, so that bioKillThreads()
           * can work reliably. */
          pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
          pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
      
          pthread_mutex_lock(&bio_mutex[type]);
          /* Block SIGALRM so we are sure that only the main thread will
           * receive the watchdog signal. */
          sigemptyset(&sigset);
          sigaddset(&sigset, SIGALRM);
          if (pthread_sigmask(SIG_BLOCK, &sigset, NULL))
              serverLog(LL_WARNING,
                  "Warning: can't mask SIGALRM in bio.c thread: %s", strerror(errno));
          // 任务一直运行
          while(1) {
              listNode *ln;
      
              /* The loop always starts with the lock hold. */
              if (listLength(bio_jobs[type]) == 0) {
                  // 注意此处将会释放锁哟,以便外部可以添加任务进来
                  pthread_cond_wait(&bio_newjob_cond[type],&bio_mutex[type]);
                  continue;
              }
              /* Pop the job from the queue. */
              ln = listFirst(bio_jobs[type]);
              job = ln->value;
              /* It is now possible to unlock the background system as we know have
               * a stand alone job structure to process.*/
              pthread_mutex_unlock(&bio_mutex[type]);
      
              /* Process the job accordingly to its type. */
              if (type == BIO_CLOSE_FILE) {
                  close((long)job->arg1);
              } else if (type == BIO_AOF_FSYNC) {
                  aof_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. */
                  // 本文介绍的删除value形式,用第一种情况
                  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().");
              }
              zfree(job);
      
              /* Unblock threads blocked on bioWaitStepOfType() if any. */
              // 唤醒所有相关等待线程
              pthread_cond_broadcast(&bio_step_cond[type]);
      
              /* Lock again before reiterating the loop, if there are no longer
               * jobs to process we'll block again in pthread_cond_wait(). */
              pthread_mutex_lock(&bio_mutex[type]);
              listDelNode(bio_jobs[type],ln);
              bio_pending[type]--;
          }
      }
      
      // lazyfree.c, 和同步删除一致了
      /* 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,&lazyfree_objects_mutex);
      }

        从此处redis异步处理过程,我们可以看到,redis并不是每次进入异步时都进行异步操作,而是在必要的时候才会进行。这也提示我们,不要为了异步而异步,而是应该计算利弊。

        如此,整个 del/unlink 的过程就完成了。总体来说,删除都是基于hash的简单remove而已,唯一有点难度是对内存的回收问题,这其实就是一个简单的使用引用计数器算法实现的垃圾回收器应该做的事而已。勿须多言。具体过程需依赖于数据的存储结构,主要目的自然是递归释放空间,避免内存泄漏了。

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.51cto.com/szk123456/11395949,作者:省赚客开发者,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:Java常量总结

      下一篇:Spring Boot中的依赖注入详解

      相关文章

      2025-05-14 10:33:31

      计算机小白的成长历程——数组(1)

      计算机小白的成长历程——数组(1)

      2025-05-14 10:33:31
      strlen , 个数 , 元素 , 内存 , 十六进制 , 地址 , 数组
      2025-05-14 10:33:25

      30天拿下Rust之网络编程

      在现代软件开发中,网络编程无处不在。无论是构建高性能的服务器、实时通信应用,还是实现复杂的分布式系统,对网络编程技术的掌握都至关重要。Rust语言以其卓越的安全性、高性能和优秀的并发模型,为网络编程提供了坚实的基础。

      2025-05-14 10:33:25
      Rust , TCP , 使用 , 客户端 , 异步 , 编程
      2025-05-14 10:33:25

      超级好用的C++实用库之环形内存池

      环形内存池是一种高效的内存管理技术,特别适合于高并发、实时性要求高的系统中,比如:网络服务器、游戏引擎、实时音视频等领域。

      2025-05-14 10:33:25
      buffer , CHP , 内存 , 分配 , 加锁
      2025-05-14 10:07:38

      30天拿下Rust之所有权

      在编程语言的世界中,Rust凭借其独特的所有权机制脱颖而出,为开发者提供了一种新颖而强大的工具来防止内存错误。这一特性不仅确保了代码的安全性,还极大地提升了程序的性能。

      2025-05-14 10:07:38
      data , Rust , 内存 , 函数 , 变量 , 数据
      2025-05-14 10:03:13

      AJAX-事件循环(超详细过程)

      JS有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。

      2025-05-14 10:03:13
      代码 , 任务 , 出栈 , 异步 , 执行 , 调用 , 队列
      2025-05-14 10:02:58

      Linux top 命令使用教程

      Linux top 是一个在Linux和其他类Unix 系统上常用的实时系统监控工具。它提供了一个动态的、交互式的实时视图,显示系统的整体性能信息以及正在运行的进程的相关信息。

      2025-05-14 10:02:58
      CPU , 信息 , 内存 , 占用 , 备注 , 进程
      2025-05-14 10:02:48

      使用JavaScript打印网页占用内存:详细指南

      在前端开发中,了解网页的内存占用情况对于优化性能和提高用户体验至关重要。

      2025-05-14 10:02:48
      JavaScript , 内存 , 占用 , 泄漏 , 浏览器 , 监听器 , 示例
      2025-05-14 09:51:15

      java循环创建对象内存溢出怎么解决

      在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError)。

      2025-05-14 09:51:15
      内存 , 占用 , 对象 , 引用 , 循环 , 次数 , 溢出
      2025-05-13 09:53:13

      计算机萌新的成长历程18——指针

      计算机要存储数据的话有以下几种途径,按访问速度由快到慢来排列分别是:寄存器>高速缓存>内存>硬盘。它们的存储空间大小是依次增大的,寄存器的存储空间大小最小,硬盘存储空间大小最大。

      2025-05-13 09:53:13
      内存 , 变量 , 地址 , 寄存器 , 指针
      2025-05-08 09:03:57

      m3db调优踩坑问题总结

      m3db调优踩坑问题总结

      2025-05-08 09:03:57
      内存 , 查询 , 聚合
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5265698

      查看更多

      最新文章

      超级好用的C++实用库之环形内存池

      2025-05-14 10:33:25

      AJAX-事件循环(超详细过程)

      2025-05-14 10:03:13

      使用JavaScript打印网页占用内存:详细指南

      2025-05-14 10:02:48

      C语言:内存函数

      2025-05-07 09:12:52

      Java之IO流

      2025-05-07 09:12:52

      C语言:动态内存管理

      2025-05-07 09:10:01

      查看更多

      热门文章

      游戏编程之十一 图像页CPICPAGE介绍

      2022-11-28 01:25:04

      C/C++ 动态解密释放ShellCode

      2023-06-19 06:57:29

      Python中查看变量的类型,内存地址,所占字节的大小

      2023-04-25 10:22:01

      游戏编程之十二 资源管理

      2023-02-15 08:38:56

      Linux Command apt 软件包管理

      2023-05-05 09:57:52

      Java学习之方法调用过程图解(理解)

      2023-04-06 06:35:24

      查看更多

      热门标签

      java Java python 编程开发 代码 开发语言 算法 线程 Python html 数组 C++ 元素 javascript c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      linux上的大文件如何通过服务器上的Java服务通过前端页面的按钮下载到本地

      C语言内存函数

      C++游戏开发指南(新改)

      【Java】引用传递的实例分析

      Java中的内存模型与并发编程

      了解Java中的内存管理机制

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号