Redis是一个开源的Key-Value存储引擎,它支持string、hash、list、set和sorted set等多种值类型。由于其卓越的性能表现、丰富的数据类型及稳定性,广泛用于各种需要k/v存储的场景。甚至在一些分布式缓存系统中,也用它作为底层存储引擎。本文对redis最常用的数据类型进行剖析,从而让使用者在各自场景下选择合适的数据类型,从而发挥其最好的优势。
1、String
String是最常用的一种数据类型,普通的k/v存储都可以归为此类,内部由sds.h定义。在类型的设计上,除了包含字符数组buf,还会额外存储实际字符串的长度以及buf的剩余空间,从而提供更灵活的管理方式。特别需要提到的是,redis自己管理内存,从而自身可以掌握更多关于内存的信息,提高内存分配的性能以及作为其它功能的依据。此外,该结构还提供了一些字符串相关的操作函数,功能丰富,实现透明,使用方便。
常用命令:
命令 |
时间复杂度 |
描述 |
返回值 |
APPEND |
O(1) |
如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。如果该Key不存在,APPEND命令将会创建一个新的Key/Value。 |
追加后Value的长度 |
INCR |
O(1) |
将指定Key的Value原子性的递增1。如果该Key不存在,其初始值为0,在incr之后其值为1。如果Value的值不能转换为整型值,该操作将执行失败并返回相应的错误信息。 |
递增后的Value值 |
INCRBY |
O(1) |
将指定Key的Value原子性的增加increment。如果该Key不存在,其初始值为0,在incrby之后其值为increment。如果Value的值不能转换为整型值,该操作将执行失败并返回相应的错误信息。 |
增加后的Value值 |
DECR |
O(1) |
将指定Key的Value原子性的递减1。如果该Key不存在,其初始值为0,在decr之后其值为-1。如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。 |
递减后的Value值。 |
DECRBY |
O(1) |
将指定Key的Value原子性的减少decrement。如果该Key不存在,其初始值为0,在decrby之后其值为-decrement。如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。 |
减少后的Value值。 |
GET |
O(1) |
获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将返回错误信息。 |
与该Key相关的Value,如果该Key不存在,返回nil。 |
SET |
O(1) |
设定该Key持有指定的字符串Value,如果该Key已经存在,则覆盖其原有值。 |
返回"OK" |
2、Hash
Hash是一个string类型的field和value的映射表,由dict.h定义。一个key可对应多个field,一个field对应一个value。将一个对象存储为hash类型,较于每个字段都存储成string类型更能节省内存,并且可以更方便的存取整个对象。hash内部存储的value为一个hashMap,并提供了直接存取这个Map成员的接口。
举个例子:我们要存储一个用户详细信息,就对每个用户设置一个userID作为key,而存储的value包含了包括姓名name,年龄age,生日birth,住址addr等信息。如果用普通的k/v结构存储,一般就是userID与value中的每一项组合作为key,而对应的内容作为value。如通过命令
mset user1_name ”张三” user1_age 18 user1_addr “杭州”
这种模式,在数据量大的时候,存在明显的内存浪费。而redis提供的hash结构就很好的解决了这个问题。对于上面的例子,通过执行命令hmset user1 name ”张三” age 18 addr “杭州”
user1作为key,而name、age、birth等作为属性。如此,便通过key+field的方式,避免了数据的重复存储。
提到Hash结构,不得不说的是key的冲突解决办法和哈希表的扩容方案。在Redis中,在冲突发生时,采取的是链式冲突解决办法。另外,redis采用了双哈希表结构(ht[2])。简单来说,就是初始k/v保存在ht[0]中,当冲突严重时,将ht[1]中桶的大小设置为ht[0]的两倍,并逐步将ht[0]中的元素迁移到ht[1]。等到所有元素都迁移完成后,再将ht[0]与ht[1]交换地址。
常用命令:
命令 |
时间复杂度 |
描述 |
返回值 |
HSET |
O(1) |
为指定的Key设定Field/Value对,如果Key不存在,该命令将创建新Key以参数中的Field/Value对,如果参数中的Field在该Key中已经存在,则用新值覆盖其原有值。 |
1表示新的Field被设置了新值,0表示Field已经存在,用新值覆盖原有值。 |
HGET |
O(1) |
返回指定Key中指定Field的关联值。 |
返回参数中Field的关联值,如果参数中的Key或Field不存,返回nil。 |
HEXISTS |
O(1) |
判断指定Key中的指定Field是否存在。 |
1表示存在,0表示参数中的Field或Key不存在。 |
3、List
List实现为一个双向链表,支持了反向的插入、查找和遍历,由adlist.h定义。我们可以通过push,pop操作从链表的头部或者尾部添加删除元素。在需要队列这样的数据结构时,List能提供非常丰富的接口,广泛用于缓冲队列,消息队列等应用场景。学过数据结构的朋友,对此肯定不会陌生。
常用命令:
命令 |
时间复杂度 |
描述 |
返回值 |
LPUSH |
O(1) |
在指定Key所关联的List Value的头部插入参数中给出的所有Values。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的头部插入。如果该键的Value不是链表类型,该命令将返回相关的错误信息。 |
插入后链表中元素的数量。 |
RPUSH |
O(1) |
在指定Key所关联的List Value的尾部插入参数中给出的所有Values。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的尾部插入。如果该键的Value不是链表类型,该命令将返回相关的错误信息。 |
插入后链表中元素的数量。 |
LPOP |
O(1) |
返回并弹出指定Key关联的链表中的第一个元素,即头部元素。如果该Key不存,返回nil。 |
链表头部的元素。 |
RPOP |
O(1) |
返回并弹出指定Key关联的链表中的最后一个元素,即尾部元素。如果该Key不存,返回nil。 |
链表尾部的元素。 |
LLEN |
O(1) |
返回指定Key关联的链表中元素的数量,如果该Key不存在,则返回0。如果与该Key关联的Value的类型不是链表,则返回相关的错误信息。 |
链表中元素的数量。 |
4、Set
Set集合类型提供的是一个列表的功能,而且是可以自动去重的。它的内部实现其实是一个value值为null的HashMap,也正是因此来使得数据去重以及判断某个成员是否在集合内等其它一些重要接口变的简单。当某些应用场景需要存储一个列表,且不想要重复的数据时,就可以选择set这个结构来处理。
常用命令:
命令 |
时间复杂度 |
描述 |
返回值 |
SADD |
O(N) |
时间复杂度中的N表示操作的成员数量。如果在插入的过程用,参数中有的成员在Set中已经存在,该成员将被忽略,而其它成员仍将会被正常插入。如果执行该命令之前,该Key并不存在,该命令将会创建一个新的Set,此后再将参数中的成员陆续插入。如果该Key的Value不是Set类型,该命令将返回相关的错误信息。 |
本次操作实际插入的成员数量。 |
SREM |
O(N) |
时间复杂度中的N表示被删除的成员数量。从与Key关联的Set中删除参数中指定的成员,不存在的参数成员将被忽略,如果该Key并不存在,将视为空Set处理。 |
从Set中实际移除的成员数量,如果没有则返回0。 |
SCARD |
O(1) |
获取Set中成员的数量。 |
返回Set中成员的数量,如果该Key并不存在,返回0。 |
SISMEMBER |
O(1) |
判断参数中指定成员是否已经存在于与Key相关联的Set集合中。 |
1表示已经存在,0表示不存在,或该Key本身并不存在。 |
5、Sort Set
Sort Set的功能与Set非常相似,只不过它是可以通过用户提供一个优先级参数来实现自动排序的,而Set结构不会做自动排序。Sort set内部使用HashMap和SkipList来实现数据的有序存储,保证查询的效率以及元素有序性。在某些应用场景,比如需要为某个班级的学生根据成绩来排序,就可以将优先级参数设置为成绩分数,这样在插入到这个结构时,就可以实现自动的排序。
命令 |
时间复杂度 |
描述 |
返回值 |
APPEND |
O(1) |
如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。如果该Key不存在,APPEND命令将会创建一个新的Key/Value。 |
追加后Value的长度 |
INCR |
O(1) |
将指定Key的Value原子性的递增1。如果该Key不存在,其初始值为0,在incr之后其值为1。如果Value的值不能转换为整型值,该操作将执行失败并返回相应的错误信息。 |
递增后的Value值 |
INCRBY |
O(1) |
将指定Key的Value原子性的增加increment。如果该Key不存在,其初始值为0,在incrby之后其值为increment。如果Value的值不能转换为整型值,该操作将执行失败并返回相应的错误信息。 |
增加后的Value值 |
DECR |
O(1) |
将指定Key的Value原子性的递减1。如果该Key不存在,其初始值为0,在decr之后其值为-1。如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。 |
递减后的Value值。 |
DECRBY |
O(1) |
将指定Key的Value原子性的减少decrement。如果该Key不存在,其初始值为0,在decrby之后其值为-decrement。如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。 |
减少后的Value值。 |
GET |
O(1) |
获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将返回错误信息。 |
与该Key相关的Value,如果该Key不存在,返回nil。 |
SET |
O(1) |
设定该Key持有指定的字符串Value,如果该Key已经存在,则覆盖其原有值。 |
返回"OK" |