String
String是简单的key-value类型,value其实不仅可以是String,也可以是数字。
使用场景
String是最常用的一种数据类型,普通的key/value存储都可以归为此类。
常用命令
(1)基本操作
set key value #设置 key-value 类型的值
get key # 根据 key 获得对应的 value
exists key # 判断某个 key 是否存在
strlen key # 返回 key 所储存的字符串值的长度
del key # 删除某个 key 对应的值
(2)批量设置
mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
mget key1 key2 # 批量获取多个 key 对应的 value
(3)计数器
incr number # 将 key 中储存的数字值增一
decr number # 将 key 中储存的数字值减一
(4)过期
expire key 60 # 数据在 60s 后过期
setex key 60 value # 数据在 60s 后过期
ttl key # 查看数据还有多久过期
实现方式
String的内部存储结构一般是SDS(Simple Dynamic String,可以动态扩展内存),但是如果一个String类型的value的值是数字,那么Redis内部会把它转成long类型来存储,从而减少内存的使用。
相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
List
List是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
- 单值多 value 集合,且value有序不唯一。
- 它是一个字符串链表,left,right 都可以插入添加。
- 如果键不存在,创建新的链表,如果值全移除,对应的键也就消失。
- 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。
使用场景
使用Lists结构,我们可以轻松地实现最新消息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出进行执行。
常用命令
(1)通过 rpush/lpop 实现队列
rpush myList value1 # 向 list 的头部(右边)添加元素
rpush myList value2 value3 # 向list的头部(最右边)添加多个元素
lpop myList # 将 list的尾部(最左边)元素取出
(2)通过 rpush/rpop 实现栈
rpush myList2 value1 value2 value3
rpop myList2 # 将 list的头部(最右边)元素取出
(3)通过 lrange 查看对应下标范围的列表元素
lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
(4)通过 llen 查看链表长度
llen myList
实现方式
Redis List由quicklist实现。quicklist是一个双向链表,而且是一个基于ziplist的双向链表,quicklist的每个节点都是一个ziplist,结合了双向链表和ziplist的优点
Set
Redis 的 Set 是 String 类型的无序集合。Set对外提供的功能与list类似是一个列表的功能,特殊之处在于Set的成员是唯一的,这就意味着集合中不能出现重复的数据。一个Set中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
- Set 单值多value,且value无序唯一。
- 可以基于 Set 轻易实现交集、并集、差集的操作。
- Set添加,删除,查找的复杂度都是 O(1)
使用场景
- 你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
- 共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
- 官网举出了一个扑克牌随机发牌的案列,存入52张牌,将此集合复制一份到其他集合中,然后依次弹出元素返回到处理程序也就是发出相应的牌,实现随机发牌。
常用命令
sadd mySet value1 value2 # 添加元素进去
sadd mySet value1 # 不允许有重复元素
smembers mySet # 查看 set 中所有的元素
scard mySet # 查看 set 的长度
sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中
实现方式
当存储的数据同时满足下面这样两个条件的时候,Redis 就采用整数集合intset来实现set这种数据类型:
- 存储的数据都是整数。
- 存储的数据元素个数小于512个。
当不能同时满足这两个条件的时候,Redis 就使用dict来存储集合中的数据
ZSet
ZSet和Set一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。ZSet中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
- 单值多vlaue,且value有序唯一。
- 和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
使用场景
- 一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。
- 可以用sorted set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务,让重要的任务优先执行。
常用命令
zadd myZset 3.0 value1 # 添加元素到 sorted set 中,3.0为权重
zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
zcard myZset # 查看 sorted set 中的元素数量
zscore myZset value1 # 查看某个 value 的权重
zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素
zrevrange myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start 1为 stop
zrem myZset value1 # 移除有序集中的一个或多个成员,不存在的成员将被忽略。
实现方式
有序集合对象ZSet的编码可以是ziplist(压缩列表)或者dict+skiplist(跳表)。同时满足以下条件时使用ziplist编码:
- 所有member的长度都小于64字节。
- 元素数量小于128个。
以上两个条件的上限值可通过zset-max-ziplist-entries和zset-max-ziplist-value来修改。
当不满足上述条件时,sorted set是由一个dict + 一个skiplist来实现的。简单来讲,dict用来查询数据到分数的对应关系,这样就可以用O(1)的复杂度来查找member对应的score值,而skiplist用来根据分数查询数据(可能是范围查找)。虽然同时使用两种结构,但它们会通过指针来共享相同元素的member和score,因此不会浪费额外的内存。
Hash
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
使用场景
系统中对象数据的存储,比如我们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获取该用户的姓名或者年龄或者生日。
常用命令
hset userInfoKey name "guide" description "dev" age "24" #设置字段(filed)中各key(对象)的值(value)。
hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
hget userInfoKey name # 获取存储在哈希表中指定字段的值。
hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
hkeys userInfoKey # 获取 key 列表
hvals userInfoKey # 获取 value 列表
实现方式
当Hash中数据项比较少的情况下,Hash底层才用压缩列表ziplist进行存储数据,随着数据的增加,底层的ziplist就可能会转成dict,具体条件如下:
- 当hash中插入的任意一个value的长度超过了64字节的时候。
- 当hash中的数据项(即filed-value对)的数目超过512时,也就是ziplist数据项超过1024的时候。
当满足上面两个条件其中之一的时候,Redis就使用dict字典来实现hash。
Redis的hash之所以这样设计,是因为当ziplist变得很大的时候,它有如下几个缺点:
- 每次插入或修改引发的realloc操作会有更大的概率造成内存拷贝,从而降低性能。
- 一旦发生内存拷贝,内存拷贝的成本也相应增加,因为要拷贝更大的一块数据。
- 当ziplist数据项过多的时候,在它上面查找指定的数据项就会性能变得很低,因为ziplist上的查找需要进行遍历。