头条高级面试题:请谈谈Redis9种数据结构以及它们的内部编码实现
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
5种普通数据结构
这个没什么好说的,对Redis稍微有点了解的都知道5种最基本的数据结构:String,List,Hash,Set,Sorted Set。不过,需要注意的是,这里依然有几个高频面试题。
•Set和Hash的关系
答案就是Set是一个特殊的value为空的Hash。Set类型操作的源码在t_set.c中。以新增一个元素为例(
int setTypeAdd(robj *subject, sds value)
),如果编码类型是OBJ_ENCODING_HT,那么新增源码的源码如下,事实上就是对dict即Hash数据结构进行操作,并且dictSetVal时value是NULL:
dictEntry *de = dictAddRaw(ht,value,NULL);
if (de) {
dictSetKey(ht,de,sdsdup(value));
dictSetVal(ht,de,NULL);
return 1;
}
同样的,我们在t_hash.c中看到Hash类型新增元素时,当判断编码类型是OBJ_ENCODING_HT时,也是调用dict的方法:dictAdd(o-
>ptr,f,v),dictAdd最终也是调用dictSetVal()方法,只不过v即value 不为NULL:
/* Add an element to the target hash table */
int dictAdd(dict *d, void *key, void *val)
{
dictEntry *entry = dictAddRaw(d,key,NULL);
if (!entry) return DICT_ERR;
dictSetVal(d, entry, val);
return DICT_OK;
}
所以,Redis中Set和Hash的关系就很清楚了,当编码是
OBJ_ENCODING_HT时,两者都是dict数据类型,只不过Set是value 为NULL的特殊的dict。
•谈谈你对Sorted Set的理解
Sorted Set的数据结构是一种跳表,即SkipList,如下图所示,红线是查找10的过程:
SkipList
•如何借助Sorted set实现多维排序
Sorted Set默认情况下只能根据一个因子score进行排序。如此一来,局限性就很大,举个栗子:热门排行榜需要按照下载量&最近更新时间排序,即类似数据库中的ORDER BY download_count, update_time DESC。那这样的需求如果用Redis的Sorted Set实现呢?
事实上很简单,思路就是将涉及排序的多个维度的列通过一定的方式转换成一个特殊的列,即result = function(x, y, z),即x,y,z是三个排序因子,例如下载量、时间等,通过自定义函数function()计算得到result,将result作为Sorted Set中的score的值,就能实现任意维度的排序需求了。可以参考笔者之前的文章:《》。
Redis内部编码
我们常说的String,List,Hash,Set,Sorted Set只是对外的编码,实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis可以在合适的场景选择更合适的内部编码。
如下图所示(图片纠正:intset编码,而不是inset编码),可以看到每种数据结构都有2种以上的内部编码实现,例如String数据结构就包含了raw、int和embstr三种内部编码。同时,有些内部编码可以作为多种外部数据结构的内部实现,例如ziplist就是hash、list和zset共有的内部编码,而set的内部编码可能是hashtable或者intset:
Redis内部编码
Redis这样设计有两个好处:
1.可以偷偷的改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动对外数据结构和命令。
2.多种内部编码实现可以在不同场景下发挥各自的优势。例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有所下降。这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist。
String的3种内部编码
由上图可知,String的3种内部编码分别是:int、embstr、raw。int类型很好理解,当一个key的value是整型时,Redis就将其编码为int类型(另外还有一个条件:把这个value当作字符串来看,它的长度不能超过20)。如下所示。这种编码类型为了节省内存。Redis默认会缓存
10000个整型值(#define OBJ_SHARED_INTEGERS 10000),这就意味着,如果有10个不同的KEY,其value都是10000以内的值,事实上全部都是共享同一个对象:
127.0.0.1:6379> set number "7890"
OK
127.0.0.1:6379> object encoding number
"int"
接下来就是ebmstr和raw两种内部编码的长度界限,请看下面的源码:
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}
也就是说,embstr和raw编码的长度界限是44,我们可以做如下验证。长度超过44以后,就是raw编码类型,不会有任何优化,是多长,就要消耗多少内存:
127.0.0.1:6379> set name "a1234567890123456789012345678901234567890 123"
OK
127.0.0.1:6379> object encoding name
"embstr"