懒是一个很好的托词,说的就像勤快了你就能干成什么大事一样
文章目录
开发人员在直接使用redis时,可以使用五种数据类型,然而这五种数据类型在底层又分别有着不同的实现,这也是面试中的高频考点之一。
redisObject
redis中,常用的底层数据结构有:简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合、跳跃表等。
而redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,而每种对象又通过不同的编码映射到不同的底层数据结构。
该对象即为redisObject。根据我在网上搜到的文章,redisObject定义在redis.h文件中。但我下载的redis-6.2.6版本中,redisObject定义在server.h中。
redis使用该对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的健(键对象),另一个对象用作键值对的值(值对象)。
其中Redis的键对象都是字符串对象,而Redis的值对象主要有字符串、哈希、列表、集合、有序集合几种。其分别对应的内部编码和底层数据结构如下图所示:
redisObject – type
Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、 encoding属性和ptr属性。
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
}
4位的type表示具体的(面向用户的)数据类型,即:字符串、列表、集合、有序集合、字典。2^4 = 8足以表示这些类型。
/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
在redis-6.2.6中,则变成了:
/* The actual Redis Object */
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
redisObject – encoding
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
}
4位的encoding表示该类型的物理编码方式,同一种数据类型可能有不同的编码方式。在我百度到的博客中,写有目前Redis中主要有8种编码方式:
#define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
但在我下载的redis-6.2.6中,有如下10种编码方式:
/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0 /* Raw representation */
#define OBJ_ENCODING_INT 1 /* Encoded as integer */
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
五种数据类型
字符串-string
字符串类型是Redis最基础的数据结构,其他几种数据结构都是在字符串类型基础上构建的。字符串类型的值是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频)等。
数字在redis中也是字符串。
内部结构
字符串底层的数据结构实现主要有两种:int和简单动态字符串 ---- SDS。
SDS,和C语言的字符串不一样。C字符串仅仅作为字符串字面量,用在一些无需对字符串进行修改的地方,如打印日志。
如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码设置为int:
typedef struct redisObject{
//类型
unsigned type:4; // #define OBJ_STRING 0
//编码
unsigned encoding:4; // #define OBJ_ENCODING_INT 1
//指向底层数据结构的指针
void *ptr; // long
}
我在网上查到的资料,有说32字节的,有说44字节的,我也懒得没去看哪个版本开始改变的。在我下载的redis-6.2.6中,是44字节,object.c的源码如下:
/* Create a string object with EMBSTR encoding if it is smaller than
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
* used.
*
* The current limit of 44 is chosen so that the biggest string object
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
#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);
}
如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于44字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw;如果小于44字节,就将编码方式设置为embstr。
typedef struct redisObject{
//类型
unsigned type:4; // #define OBJ_STRING 0
//编码
unsigned encoding:4; // embstr or raw
//指向底层数据结构的指针
void *ptr; // sds
}
哈希-hash
当存储的内容是对象的时候,Redis 字符串对象的很多功能使用Redis 哈希对象也可以实现。如缓存用户信息的时候,使用Redis哈希对象存储,简单直观,如果使用合理可以减少内存空间的使用。但也有其缺点,就是要要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
列表-list
存储一组有序的、可重复的数据。因为其有序性,它可以获取指定范围的元素列表、可以在O(1)的时间复杂度获取指定索引下标的元素等。
通过使用lpush、rpush、lpop、rpop,可以实现栈和队列,也可以实现一个简易的消息队列。
集合-set
无序并唯一的键值集合。它的存储顺序不会按照插入的先后顺序进行存储。
集合无序,唯一,且支持交并差,可以用来做标签。
通过 SPOP(随机移除并返回集合中一个或多个元素) 和 SRANDMEMBER(随机返回集合中一个或多个元素) 命令,可以实现一个抽奖系统。
有序集合-zset
有序集合类型 (Sorted Set或ZSet) 相比于集合类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。
有序集合键不可重复,分值可以重复;且有序集合中的元素可以排序。
有序集合比较典型的使用场景就是排行榜系统例如学生成绩的排名、某视频(博客等)网站的用户点赞、播放排名、电商系统中商品的销量排名等。