在Redis中,对于大KEY的删除一直是个比较头疼的问题,为了不影响服务,我们通常需要自己实现工具来删除大KEY,或者在业务低峰期进行删除操作。
为了解决以上问题, Redis 4.0 新添加了 UNLINK 命令用于执行大KEY异步删除。那么这个异步删除的背后的逻辑是什么?
通过源码我们可以的得知以下信息:
当我们调用异步删除UNLINK命令时:
- 释放掉Expire Dicti 对 K-V 的引用
- 释放Main Dict 对 K-V 的引用,同时记录下这个K-V 的 Entry地址
- 计算释放掉这个V 所需要的代价,计算方法如下:
3.1 如果这个V 是一个 String 类型,则代价为 1
3.2 如果这个V 是一个复合类型,则代价为 该复合类型的长度,比如,list 则为 llen 的结果,hash 则为 hlen 的结果 … - 根据得到的代价值,和代价阈值比对,如果小于 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 存储,则直接释放空间。 - 设置 V 值等于NULL
- 释放掉 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);
http://www.cnblogs.com/svan/p/7054129.html
相关推荐
文中提到的Redis4.0新特性包含大key删除、热点key问题、info信息的完善、LFU & hotkeys的改进、memory管理优化以及PSYNC的更新。 在Redis4.0中,增加了Lazy Free机制,主要是为了解决在执行大量删除操作时,例如...
**Redis扩展的高级特性** 除了基础操作,PHP7的Redis扩展还支持许多高级功能,如事务(`multi`和`exec`)、管道(`pipeline`)、脚本执行(`eval`和`evalsha`)、Redis事务的lua脚本支持、以及Redis Sentinel的高...
在Linux系统中,Redis 3.2.0版本提供了丰富的特性和优化,使得它成为开发和运维人员的首选之一。本文将深入探讨Redis 3.2.0的核心特点、安装与配置、常用命令以及使用场景。 首先,让我们来看看Redis 3.2.0版本的...
以上是Redis从4.0至7.0版本的发展历程及各个版本中引入的关键特性和改进之处的详细介绍。这些特性不仅增强了Redis的功能性,还极大地提高了其在生产环境中的稳定性和性能表现。对于开发者来说,了解这些变化有助于更...
- **使用Stream结构**:自Redis 4.0版本起,引入了Stream数据类型,可以更好地实现异步队列功能。 #### 40、使用过Redis分布式锁么,它是什么回事? Redis分布式锁通常使用`SETNX`命令实现。当多个客户端尝试获取...
LazyFree是Redis 4.0中引入的一项新特性,用于改善Redis在处理大型键时的性能。Redis 5.0.4保留了这项特性,并提供以下配置项: ```ini lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-...
1、4.0之后,后台线程处理缓慢任务(清理脏数据、释放无用连接、删除大key) 2、提高网络IO性能,IO线程并发读取主线程获取的socket,主线程执行命令 3、配置 io-threads-do-reads yes io-threads 4 (略小于CPU核心...
11. Bigkey处理:Redis允许用户识别和处理大键,避免网络拥塞和内存不均匀分配。例如,通过`DEBUG OBJECT key`查看`serializedlength`属性判断键是否过大,通常认为超过10KB的键就可能成为大键。 12. 内存管理:...
在.NET环境中,`Redis.Driver.Net`是针对Redis的一个C#异步客户端库,专为.NET 4.0及更高版本以及Mono平台设计。这个库提供了一种高效且易于使用的接口,使得开发人员能够方便地与Redis服务器进行通信。 ### 异步...
Java生成密钥的实例 1个目标文件 摘要:Java源码,算法相关,密钥 Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、...