前言
了解完同步删除和异步删除你将对Redis的认识会更上一层楼。加油
名次解释 :
- Redis存储数据的k-v结构是用字典实现的。k对应的过期时间的存储也是用字典实现。
- 键空间 : 用于存储数据库的k-v数据。
- 过期键空间 : 用于存储数据库过期的k-v数据。
例如 : setex name 60 zhangsan 这个命令的name-zhangsan的k-v是存储在键空间的字典里。
60秒的过期时间name-60是存储在过期空间的字典里。
一、同步和异步指的是什么?
同步删除 : 删除key时释放value空间是在主线程中执行。
异步删除 : 删除key时释放value空间是在异步线程中执行。
二、代码实现
// 同步删除就很简单了,直接在字典里进行切断引用并释放空间
int dbSyncDelete(redisDb *db, robj *key) {
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;
}
}
// 异步删除
int dbAsyncDelete(redisDb *db, robj *key) {
// 同步删除过期键空间key数据,并释放空间
if (dictSize(db->expires) > 0) dictDelete(db->expires, key->ptr);
// 因为数据库使用字典实现的,所以把存储k-v数据的dictEntry节点取出来,
// 并且在取出来的同时,切断这个节点和键空间的引用。
// 所以称把键空间存储的key-value删除,但是未释放key-value空间
dictEntry *de = dictUnlink(db->dict, key->ptr);
if (de) {
robj *val = dictGetVal(de);
// 获取value的大小(这个大小不是占用的字节数,而是redis内部定义的算法,根据不同类型规定的大小)
size_t free_effort = lazyfreeGetFreeEffort(val);
// 只有大小大于64才进行异步释放,小于64进行同步释放空间。
// 这里就说明了,异步删除只是对value的空间进行异步释放。
if (free_effort > LAZYFREE_THRESHOLD) {
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
// 这里把数据节点de的value设置成null,因为value的空间释放交给异步线程去做了。
dictSetVal(db->dict,de,NULL);
}
}
if (de) {
// 释放节点的key-value空间,如果上面已经进行异步删除了,这里
// 释放节点的key空间
dictFreeKey(d, he);
// 释放节点的value空间,如果上面已经进行异步删除释放了,这里就什么都不做。
dictFreeVal(d, he);
// 释放节点空间
zfree(he);
// 如果是集群,删除对应的槽分配数据
if (server.cluster_enabled) slotToKeyDel(key);
return 1;
} else {
return 0;
}
}
// 计算对象的大小,用于判断是否可以异步释放空间。这个大小是从需要申请多少次空间来定义的。
// 对于为什么每种类型还要判断实现的数据结构?
// 因为Redis的每中类型的底层存储的数据结构都不止一种。
size_t lazyfreeGetFreeEffort(robj *obj) {
// list 取链表长度
if (obj->type == OBJ_LIST) {
return obj->ptr->len;
// set并且数据结构是字典,取字典长度
} else if (obj->type == OBJ_SET && obj->encoding == OBJ_ENCODING_HT) {
return dictSize(obj->ptr);
// zset并且数据结构是跳跃链表,取跳表长度
} else if (obj->type == OBJ_ZSET && obj->encoding == OBJ_ENCODING_SKIPLIST){
return obj->ptr->zsl->length;
// hash并且数据结构是字典,取字典长度
} else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {
return dictSize(obj->ptr);
} else {
return 1; /* Everything else is a single allocation. */
}
}
总结
同步和异步的区别就是释放value空间是主线程去执行还是异步线程去执行,理解这句话很关键。其他的操作都是由主线程执行的。