目录

一、Redis五种类型的常用命令

1.1 String

常用命令:

1.2 List

常用命令:

1.3 Set 

常用命令:

1.4  hash

常用命令:

1.5  zset (有序集合)

常用命令:

二、五种数据类型的原理(包括其编码方式)

 

2.1  redisObject如何表示string (各个encoding方式解释)

2.1.1   int

2.1.2    raw

2.1.3 embstr

2.2  redisObject如何表示 list

2.2.1  ziplist 压缩列表

ziplist能够节省内存的原理

2.2.2  linkedlist

2.3  redisObject如何表示 hash

2.3.1  ziplist

2.3.2  hashtable

2.4  redisObject如何表示 set

2.4.1  intset

2.4.2  hashtable

2.5  zset 

2.5.1  ziplist

2.5.2  skiplist

三、五种数据类型的应用场景

3.1  string的应用场景

3.2  list 的应用场景

3.3  hash 的应用场景

3.4  set 的应用场景

3.5 zset的应用场景


Redis是 key-value结构的数据库

Redis常用的五种数据类型分别是:String、List、Set、Hash、Zset。

一、Redis五种类型的常用命令

1.1 String

String是 redis 最基本的数据类型。一个 key 对应一个value。redis的String可以表示任何数据,比如 jpg图像或者序列化的对象,String的最大值能存储512MB

常用命令:

get、set、incr、decr、mget

set:往redis里输入key-value,如:输入 key 为name,value为zhujunwen

get:输入 key 值,返回对应的 value 值。如:

incr:自增1,如:

decr:自减。

mget:一次性获取多个key的value。如:

 

1.2 List

常用命令:

lpush、lpop、rpush、rpop、llen

lpush:从列表List的左边插入一个元素。

lpop:从列表List的左边移出一个元素。

rpush:从列表List的右边插入一个元素。

rpop:从列表List的右边移出一个元素。

llen:打印当前列表List中的元素个数。

 

1.3 Set 

常用命令:

sadd、srem、scard、sismember

sadd:往set中添加数据。

srem:从set中删除数据。

scard:查看set中存在的元素个数。

sismember:查看set中是否存在某个数据。

 

1.4  hash

常用命令:

hget、hset、hmget

hget:通过key值,从hash里取对应的value

hset:往hash里,添加key-value

hmget:一次性获取多个key的value

 

1.5  zset (有序集合)

在redis中,set和zset都是元素的集合,都不允许有重复的元素。不同的是,zset的每个元素都会关联一个分数(分数可以重复),redis通过这个分数为集合中的成员进行排序。

常用命令:

zadd、zcard、zrange、zrem、zrevrange

zadd:添加数据

zrem:删除元素

zrem 还可以一次性删除多个元素:

zcard:查询数据

zrange:数据排序,根据分数从小到大

withscores表示用分数进行排序。下面命令的 0、2 表示排序的区间是第0个到第二个元素。

zrevrange:数据排序, 根据分数从大到小

例如返回分值最高的前3个元素:

 

 

二、五种数据类型的原理(包括其编码方式)

Redis内部使用一个 redisObject对象来表示所有的key和value,redisObject的信息如下图所示:

可以看到redisObject的信息有 type、encoding、ptr、vm。

type:用来表示 这个redisObject是属于 五种类型(string、hash、list、set、zset)的哪一种,比如 type=string代表value 存储的是一个普通字符串。

encoding:用来表示 type 的底层数据结构是用什么实现的。打个比方,就如Java中的 list ,可以由ArrayList 来实现,也可以由LinkedList 来实现。

ptr:指向底层数据结构的指针。

vm:只有打开了Redis的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。

 

2.1  redisObject如何表示string (各个encoding方式解释)

字符串的encoding有三种方式:

  • int
  • raw
  • embstr

2.1.1   int

如果一个字符串string保存的是整数值,如 set number 10086 ,那么这个整数值可以用 long 类型标识。那么该字符串的 redisObject会把10086这个数值保存在ptr属性中,并将encoding设置成int。假设有如下命令:set number 10086。那么 number 键对象的示意图如下:

(即当string对象的值全部是数字,就会使用int编码。)

2.1.2    raw

如果字符串string 保存的是一个字符串值。并且这个字符串大于39个字节,那么字符串对象将使用一个 简单动态字符串(SDS) 来保存这个字符串值,并将 redisObject的encoding设置为raw。

使用 raw 存储字符串的示意图如下:

由于字符串大于39个字节的话,即不能确定值的字节大小,所以redisObject 和 字符串的内存是分开分配的。

注意:raw编码方式,是redisObject 的内存地址和 sds 的内存地址是不连续的。

 

2.1.3 embstr

如果字符串string 保存的是一个字符串值,并且这个字符串小于39个字节,那么字符串将使用 embstr 编码的方式来保存这个字符串。

用embstr存储的话,一般字符串的存储内存很小,因此redis一次性分配 redisObject和 sds 的内存,且内存连续。

embstr对比raw的优点:

  1. embstr创建 字符串对象(redisObject) 的次数只需1次,而raw 是两次(redisObject 和sds 分开分配)。
  2. 同理,embstr调用释放内存的函数也是1次,比raw编码的字符串对象少一次。
  3. 由于 embstr编码的是内存连续的,而raw是不连续的,因此存取速度embstr比较快。

 

注意:embstr 编码方式,是 redisObject 的内存地址和 sds 的内存地址是连续的。

 

2.2  redisObject如何表示 list

列表对象list 的编码方式encoding有两种,分别是:ziplist、linkedlist。

2.2.1  ziplist 压缩列表

压缩列表是 节省内存而设计的内存结构(是redis创造的)。优点是 节省内存,缺点是 比其他结构要消耗更多的时间,所以redis在数据量少的时候使用压缩列表存储

列表长度少于 512,并且所有元素的长度都少于64个字节时,使用压缩列表存储,否则使用 linkedlist 存储

用压缩列表ziplist 编码的redisObject如下:

由上图所示:在ziplist中,结点之前有三个变量,分别是: zlbytes、zltail、zllen,这三个的详细说明如下:

ziplist能够节省内存的原理

我们知道,普通数据能够支持随机访问的原因就是存储的内存是连续的。但是有一个问题,就是数组中每个元素的大小都必须是一样的,如果有大小不一样的话,那么该元素的内存就必须按照数组中最大的元素(假设是5个字节)的内存存放,那么存储少于5字节的元素就会存在内存浪费问题。如下图:

我们要想既保留数组的优势,又不浪费内存,就只能把数组设计成    每个元素的大小都不定的,如下:

那么ziplist是设计去如何解决这个问题呢?对于一个 每个元素不定长的数组 我们在遍历它的时候由于不知道每个元素的大小是多少,因此也就无法计算出下一个节点的具体位置。这个时候我们可以给每个节点增加一个lenght的属性

data-length 记录着 data-value的大小。因此我们在遍历节点的之后就知道每个节点的长度(占用内存的大小),就可以很容易计算出下一个节点再内存中的位置。这种结构就像一个简单的压缩列表了。

 

2.2.2  linkedlist

当列表长度 少于 512且 每个元素都少于64个字节,那么就用ziplist存储。否则就用linkedlist存储

linkedlist的编码方式如下:

 

2.3  redisObject如何表示 hash

hash 的encoding 编码方式共有两种:ziplist、hashtable

2.3.1  ziplist

当哈希对象保存的键值对数量少于512,且所有键值对的长度都少于64字节时,使用压缩列表保存。

在压缩列表中,每当有新的键值对要加入到哈希对象时, 程序会先将保存了  键的压缩列表节点  推入到压缩列表表尾, 然后再将  保存了值的压缩列表节点  推入到压缩列表表尾, 因此:保存了同一键值对的两个节点总是紧挨在一起。如下图:

 

2.3.2  hashtable

若哈希对象保存的键值对个数大于512,并且其中有键值对大于64个字节,就使用hashtable 保存。如下:

如:

 

2.4  redisObject如何表示 set

set 的encoding编码方式有:intset、hashtable

2.4.1  intset

当 集合的长度少于 512 时,并且所有元素都是整数,使用 intset存储。否则使用 hashtable。

 

2.4.2  hashtable

hashtable编码的底层实现是字典,字典的每个键是字符串对象,只不过值都是空(NULL)。

 

2.5  zset 

zset的encoding 编码有两种,分别是:ziplist、skiplist。

2.5.1  ziplist

当zset的长度少于128,并且所有元素的长度都少于64字节时,用ziplist存储。如下图:

我们可以看到,每个节点,前面是字符串,后面是分数值。分值小的靠近表头,大的靠近表尾。

 

2.5.2  skiplist

redis 的 skiplist 是由 字典dict 和 跳表构成的。

zset的结构体定义如下:

typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

什么是跳表?

如下图:

原始链表中,如果要查询结点15,那么一共就需要从结点1开始,到结点15,一共查询15个结点才能找到结点15。(跳表必须是排好序的)

那么如果我们额外加一层索引层呢?如下图,我们从下图的 第一级索引层 开始查找,那么要找到结点15,只需要找 结点 1-4-7-10-14-14-15,只需要查询七个结点即可。这就是跳表,可以大大地方便查询。

当然,那还能不能加第二层、第三层索引层呢?当然可以。如下图,我们从第二级索引层开始,查询结点15。那么查询顺序为 1-7-14-14-14-15,一共需要查询6个结点,查询效率又提高了。

这种通过对链表加多级索引的机构,就是跳表了。跳表 每层索引层的结点数目都是其前一层的一半。 因为这样的话,查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到O(log n)。 但是这样的话,会存在一个问题,就是 当新插入一个结点的时候,会打乱上下相邻两层链表上节点个数严格的2:1的对应关系。 如果硬要维持这种2:1的关系话,那么插入和删除结点后,都必须要对整个跳表重新调整,这会降低效率。

因此 skiplist 为了解决这个问题,并不要求 相邻上下链表结点个数必须按照2:1的关系。而是为每个节点随机出一个层数(level)。比如,一个节点随机出的层数是3,那么就把它链入到第1层到第3层这三层链表中。为了表达清楚,下图展示了如何通过一步步的插入操作从而形成一个skiplist的过程:

从上面skiplist的创建和插入过程可以看出,每一个节点的层数(level)是随机出来的,而且新插入一个节点不会影响其它节点的层数。因此,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。这就降低了插入操作的复杂度。实际上,这是skiplist的一个很重要的特性,这让它在插入性能上明显优于平衡树的方案。

 

skiplist的字典dict 和 跳表。

刚刚说了 skiplist 是由 dict 和跳表组成的。

  • dict 用于记录 字符串对象和分数,即查询 字符串对象对应 分数。
  • 跳表则用来,根据 分数查询对应字符串。

为什么 skiplist 编码要同时用字典和跳表来实现?

  • 字典查询分值的时间复杂度是O(1)。但是无序。
  • 跳表的优点是有序,但是查询的时间复杂度为O(logn)。
  • 虽然采用两个结构,但是集合的元素成员和分值是共享的,两种结构都通过指针指向同一地址,所以不会存在内存浪费。

redis里的一个跳表的结构如下:

  1.  包含一个 头节点header 和 尾结点 tail。
  2. length 表示节点数。
  3. level 表示表示skiplist的总层数,即所有节点层数的最大值。

skiplist 结构如下:

三、五种数据类型的应用场景

3.1  string的应用场景

普通的 key-value键值对都可以用 string来保存,例如:

  1. 访问量统计,每次访问博客和文章,都用 incr 命令加一。
  2. 做缓存。

3.2  list 的应用场景

作为队列,因为 list 的两端操作比较方便,所以可以用于一些需要获取最新数据的场景,如新闻类应用的最新新闻。

3.3  hash 的应用场景

用于存储、修改对象属性。比如:用户(姓名、性别、爱好),文章(标题、发布时间、作者、内容)。其中 用户相当于key,(姓名、性别、爱好)相当于存储的value。

3.4  set 的应用场景

  1. 好友推荐,根据set的内容求交集,大于某个阈值就可以推荐。
  2. 利用set的唯一性,统计网站内所有独立ip。

3.5 zset的应用场景

  1. 排行榜,因为zset 本来就是有序的,并且有排序功能。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐