Redis不是一个简单的键值对存储,它其实是一个支持各类类型数据结构的存储。在传统的键值存储中,是将字符串键关联到字符串值,可是在Redis中,这些值不只限于简单的字符串,还能够支持更复杂的数据结构。下面就是Redis支持的数据结构:redis
键是二进制安全的,这意味着您可使用任何二进制序列做为键,能够是OneMoreStudy这样的字符串,也可使图片文件的内容,空字符串也是有效的键。不过,还有一些其余规则:算法
字符串类型是和键关联的最简单的类型。它是Memcached中惟一的数据类型,所以对于新手来讲,在Redis中使用它也是很容易的。键是字符串类型,当咱们也使用字符串类型做为值时,咱们会能够从一个字符串映射到另外一个字符串。字符串数据类型有不少应用场景,例如缓存HTML片断或页面。数组
下面简单介绍一下字符串的命令(在redis-cli中使用):缓存
> set one-more-key OneMoreStudy
OK
> get one-more-key
"OneMoreStudy"
复制代码
使用SET和GET命令来设置和查询字符串值的方式。须要注意的是,若是当前键已经和字符串值相关联,SET命令将会替换已存储在键中的现有值。字符串能够是任意的二进制数据,好比jpeg图像。字符串最多不能大于512MB。SET命令还有一些实用的可选参数,好比:安全
> set one-more-key Java nx #若是key存在,则设置失败。
(nil)
> set one-more-key Java xx #若是key存在,才设置成功。
OK
复制代码
虽然字符串是Redis的基本值,但也可使用它们执行一些实用的操做。好比:bash
> set one-more-counter 50
OK
> incr one-more-counter #自增长1
(integer) 51
> incr one-more-counter #自增长1
(integer) 52
> incrby one-more-counter 5 #自增长5
(integer) 57
复制代码
INCR命令将字符串值解析为整数,将其自增长1,最后将得到的值设置为新值。还有其余相似的命令,例如INCRBY,DECR和DECRBY等命令。INCR命令是原子操做,即时有多个客户端同时同一个key的INCR命令,也不会进入竞态条件。好比,上面的例子先设置one-more-counter的值为50,即便两个客户端同时发出INCR命令,那么最后的值也确定是52。服务器
可使用MSET和MGET命令在单个命令中设置或查询多个键的值,对于减小延迟也颇有用。好比:数据结构
> mset a 1 b 2 c 3
OK
> mget a b c
1) "1"
2) "2"
3) "3"
复制代码
使用MGET命令时,Redis返回一个值的数组。app
使用DEL命令能够删除键和相关联的值,存在指定的键则返回1,不存在指定的键则返回0。使用EXISTS命令判断Redis中是否存在指定的键,存在指定的键则返回1,不存在指定的键则返回0。好比:性能
> set one-more-key OneMoreStudy
OK
> exists one-more-key
(integer) 1
> del one-more-key
(integer) 1
> exists one-more-key
(integer) 0
复制代码
使用TYPE命令,能够返回存储在指定key的值的数据类型,好比:
> set one-more-key OneMoreStudy
OK
> type one-more-key
string
> del one-more-key
(integer) 1
> type one-more-key
none
复制代码
在讨论更复杂的数据结构以前,咱们须要讨论另外一个功能,该功能不管值类型是什么都适用,它就是EXPIRE命令。它能够为键设置到期时间,当超过这个到期时间后,该键将自动销毁,就像对这个键调用了DEL命令同样。好比:
> set one-more-key OneMoreStudy
OK
> expire one-more-key 5
(integer) 1
> get one-more-key #马上调用
"OneMoreStudy"
> get one-more-key #5秒钟后调用
(nil)
复制代码
上面的例子,适用了EXPIRE命令设置了过时时间,也可使用PERSIST命令移除键的过时时间,这个键将持久保持。除了EXPIRE命令,还可使用SET命令设置过时时间,好比:
> set one-more-key OneMoreStudy ex 10 #设置过时时间为10秒
OK
> ttl one-more-key
(integer) 9
复制代码
上面的例子,设置了一个字符串值OneMoreStudy的one-more-key,该键的到期时间为10秒。以后,调用TTL命令以检查该键的剩余生存时间。
到期时间可使用秒或毫秒精度进行设置,但到期时间的分辨率始终为1毫秒。实际上,Redis服务器上存储的不是到期时间长度,而是该键到期的时间。
Redis列表是使用链表实现的,这就意味着在头部或尾部增长或删除一个的元素的时间复杂度是O(1),很是快的。不过,按索引查询对应元素的时间复杂度就是O(n),慢不少。若是想快速查询大量数据,可使用有序集合,后面会有介绍。
LPUSH命令将一个新元素添加到列表的左侧(顶部),而RPUSH命令将一个新元素添加到列表的右侧(底部)。最后,LRANGE命令能够从列表中按范围提取元素。好比:
> rpush one-more-list A
(integer) 1
> rpush one-more-list B
(integer) 2
> lpush one-more-list first
(integer) 3
> lrange one-more-list 0 -1
1) "first"
2) "A"
3) "B"
复制代码
LRANGE命令须要另外两个参数,要返回的第一个元素的索引和最后一个元素的索引。若是索引为负值,Redis将从末尾开始计数,-1是列表的最后一个元素,-2是列表的倒数第二个元素,依此类推。
LPUSH和RPUSH命令支持多个参数,可使用一次命令添加多个元素,好比:
> rpush one-more-list 1 2 3 4 5 "last"
(integer) 9
> lrange one-more-list 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "last"
复制代码
在Redis列表上,也能够移除并返回元素。与LPUSH和RPUSH命令,对应的就是LPOP和RPOP命令,LPOP命令是将列表的左侧(顶部)的元素移除并返回,RPOP命令是将列表的右侧(底部)的元素移除并返回。好比:
> rpush one-more-list a b c
(integer) 3
> rpop one-more-list
"c"
> rpop one-more-list
"b"
> rpop one-more-list
"a"
复制代码
咱们添加了三个元素,并移除并返回了三个元素,此时列表为空,没有任何元素。若是再使用RPOP命令,会返回一个NULL值:
> rpop one-more-list
(nil)
复制代码
使用RPUSH和RPOP命令,或者LPUSH和LPOP命令能够实现栈的功能,使用LPUSH和RPOP命令,或者RPUSH和LPOP命令能够实现队列的功能。也能够实现生产者和消费者模式,好比多个生产者使用LPUSH命令将任务添加到列表中,多个消费者使用RPOP命令将任务从列表中取出。可是,有时列表可能为空,没有任何要处理的任务,所以RPOP命令仅返回NULL。在这种状况下,消费者被迫等待一段时间,而后使用RPOP命令重试。这就暴露了有几个缺点:
有什么办法能够解决呢?使用BRPOP和BLPOP的命令,它们和RPOP和LPOP命令相似,惟一的区别是:若是列表为空时,命令会被阻塞,直到有新元素添加到列表中,或指定的超时时间到了时,它们才会返回到调用方。好比:
> brpop tasks 5
复制代码
它含义是,列表为空时,等待列表中的元素,但若是5秒钟后没有新的元素被添加,则返回。您能够将超时时间传入0,表示永远等待元素添加。也能够传入多个列表,这时会按参数前后顺序依次检查各个列表,返回第一个非空列表的尾部元素。另外还有如下3点须要注意的:
列表的建立和删除都是由Redis自动完成的,当尝试向不存在的键添加元素时,Redis会自动建立一个空的列表;当最后一个元素被移除时,Redis会自动删除这个列表。这不是特定于列表的,它适用于由多个元素组成的全部Redis数据类型,好比集合、有序集合、哈希,它们都有3条规则:
> del one-more-list
(integer) 1
> lpush one-more-list 1 2 3
(integer) 3
复制代码
可是,在键存在时,就不能操做错误的数据类型了,好比:
> set one-more-key OneMoreStudy
OK
> lpush one-more-key 1 2 3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> type one-more-key
string
复制代码
> lpush one-more-list 1 2 3
(integer) 3
> exists one-more-list
(integer) 1
> lpop one-more-list
"3"
> lpop one-more-list
"2"
> lpop one-more-list
"1"
> exists one-more-list
(integer) 0
复制代码
> del one-more-list
(integer) 0
> llen one-more-list
(integer) 0
> lpop one-more-list
(nil)
复制代码
Redis为了追求高性能,列表的内部实现不是一个简单的链表,这里先卖个关子,后续的文章会详细介绍。
集合是一个字符串的无序集合,SADD命令能够将新元素添加到集合中。还能够对集合进行许多其余操做,例如:判断给定元素是否已存在、执行多个集合之间的交集、并集或差等等。好比:
> sadd one-more-set 1 2 3
(integer) 3
> smembers one-more-set
1) "1"
2) "3"
3) "2"
复制代码
在上面的例子中,在集合中添加了三个元素,并让Redis返回全部元素。正如你所见,返回的元素是没有排序的。在每次调用时,元素的顺序都有可能不同。
还可使用SISMEMBER命令判断给定元素是否已存在,好比:
> sismember one-more-set 3
(integer) 1
> sismember one-more-set 30
(integer) 0
复制代码
在上面的例子中,3在集合中,因此返回1;而30不在集合中,因此返回0。
可使用SINTER命令,计算出多个集合的交集;使用SUNION命令,计算多个集合的并集;使用SPOP命令,移除并返回集合中的一个随机元素;使用SCARD命令,计算集合中的元素的数量。好比:
> sadd one-more-set1 1 2 3
(integer) 3
> sadd one-more-set2 2 3 4
(integer) 3
> sinter one-more-set1 one-more-set2 #交集
1) "3"
2) "2"
> sunion one-more-set1 one-more-set2 #并集
1) "1"
2) "3"
3) "2"
4) "4"
> spop one-more-set1 #随机移除一个元素
"3"
> scard one-more-set1 #元素数量
(integer) 2
复制代码
有序集合是一种相似于集合和哈希之间混合的数据类型。像集合同样,有序集合中由惟一的、非重复的字符串元素组成,所以从某种意义上说,有序集合也是一个集合。可是集合中的元素是没有排序的,而有序集合中的每一个元素都与一个称为分数(score)的浮点值相关联,这就是为何有序集合也相似于哈希的缘由,由于每一个元素都映射到一个值。有序集合的排序规则以下:
咱们来举个例子,把王者荣耀战队的名字和积分添加到有序集合中,其中把战队的名字做为值,把战队的积分做为分数。
> zadd kpl 12 "eStarPro"
(integer) 1
> zadd kpl 12 "QGhappy"
(integer) 1
> zadd kpl 10 "XQ"
(integer) 1
> zadd kpl 8 "EDG.M"
(integer) 1
> zadd kpl 8 "RNG.M"
(integer) 1
> zadd kpl 4 "TES"
(integer) 1
> zadd kpl 2 "VG"
(integer) 1
复制代码
如上所示,ZADD命令和SADD命令类似,可是多了一个额外的参数(在要添加的元素的前面)做为分数。ZADD命令也支持多个参数,虽然在上面的例子中未使用它,但你也能够指定多个分数和值对。使用有序集合,快速地返回按其积分排序的战队列表,由于实际上它们已经被排序了。
须要注意的是,为了快速获取有序集合中的元素,每次添加元素的时间复杂度都为O(log(N)),这是由于有序集合是同时使用跳跃表和字典来实现的,具体原理这里先卖个关子,后续的文章会详细介绍。
可使用ZRANGE命令按照升序获取对应的值,好比:
> zrange kpl 0 -1
1) "VG"
2) "TES"
3) "EDG.M"
4) "RNG.M"
5) "XQ"
6) "QGhappy"
7) "eStarPro"
复制代码
0和-1表明查询从第一个到最后一个的元素。还可使用ZREVRANGE命令按照降序获取对应的值,好比:
> zrevrange kpl 0 -1
1) "eStarPro"
2) "QGhappy"
3) "XQ"
4) "RNG.M"
5) "EDG.M"
6) "TES"
7) "VG"
复制代码
加上WITHSCORES参数,就能够连同分数一块儿返回,好比:
> zrange kpl 0 -1 withscores
1) "VG"
2) "2"
3) "TES"
4) "4"
5) "EDG.M"
6) "8"
7) "RNG.M"
8) "8"
9) "XQ"
10) "10"
11) "QGhappy"
12) "12"
13) "eStarPro"
14) "12"
复制代码
有序集合还有更强大的功能,好比在分数范围内操做,让咱们获取小于10(含)的战队,使用ZRANGEBYSCORE命令:
> zrangebyscore kpl -inf 10
1) "VG"
2) "TES"
3) "EDG.M"
4) "RNG.M"
5) "XQ"
复制代码
这就是获取分数从负无穷到10所对应的值,一样的咱们也能够获取分数从4到10所对应的值:
> zrangebyscore kpl 4 10
1) "TES"
2) "EDG.M"
3) "RNG.M"
4) "XQ"
复制代码
另外有用的命令:ZRANK命令,它能够返回指定值的升序排名(从0开始);ZREVRANK命令,它能够返回指定值的降序排名(从0开始),好比:
> zrank kpl "EDG.M"
(integer) 2
> zrevrank kpl "EDG.M"
(integer) 4
复制代码
有序集合的分数是随时更新的,只要对已有的有序集合调用ZADD命令,就会以O(log(N))时间复杂度更新其分数和排序。这样,当有大量更新时,有序集合是合适的。因为这种特性,常见的场景是排行榜,能够方便地显示排名前N位的用户和用户在排行榜中的排名。
Redis的哈希和人们指望的“哈希”结构是同样的,它是一个无序哈希,内部存储了不少键值对,好比:
> hmset one-more-fans:100 name Lily age 25
OK
> hget one-more-fans:100 name
"Lily"
> hget one-more-fans:100 age
"25"
> hgetall one-more-fans:100
1) "name"
2) "Lily"
3) "age"
4) "25"
复制代码
尽管哈希很容易用来表示对象,可是实际上能够放入哈希中的字段数是没有实际限制的,所以您能够以更多种的不一样方式使用哈希。除了HGET命令获取单个字段对应的值,也可使用HMSET命令获取多个字段及对应的值,它返回的是一个数组,好比:
> hmget one-more-fans:100 name age non-existent-field
1) "Lily"
2) "25"
3) (nil)
复制代码
还可使用HINCRBY命令,为指定字段的值作增量,好比:
> hget one-more-fans:100 age
"25"
> hincrby one-more-fans:100 age 3
(integer) 28
> hget one-more-fans:100 age
"28"
复制代码
Redis哈希的实现结构,和Java中的HashMap是同样的,也是“数组+链表”的结构,当发生数组位置碰撞是,就会将碰撞的元素用链表串起来。不过Redis为了追求高性能,rehash的方式不太同样,这里先卖个关子,后续的文章会详细介绍。
位图不是实际的数据类型,而是在String类型上定义的一组面向位的操做。因为字符串是二进制安全的,而且最大长度为512MB,所以能够设置多达2^32个不一样的位。位图操做分为两类:固定单个位操做,好比将一个位设置为1或0或获取其值;对位组的操做,好比计算给定位范围内设置位的数量。
位图的最大优势之一是,它们在存储信息时一般能够节省大量空间。例如,在以增量用户ID位标识表示用户是否要接收新闻通信,仅使用512 MB内存就能够记住40亿用户的一位信息。
使用SETBIT和GETBIT命令来设置和获取指定位,好比:
> setbit one-more-key 10 1
(integer) 0
> getbit one-more-key 10
(integer) 1
> getbit one-more-key 11
(integer) 0
复制代码
SETBIT命令将位号做为其第一个参数,将其设置为1或0的值做为其第二个参数。若是位号超出当前字符串长度,该命令将会自动扩大字符串。GETBIT命令只是返回指定位号的位的值,若是位号超出存储的字符串长度则会返回0。
对位组的操做有如下3个命令:
> set one-more-key "\x13\x7f" #二进制为0001 0011 0111 1111
OK
> bitcount one-more-key #整个字符串中1的位数
(integer) 10
> bitcount one-more-key 0 0 #第一个字符(0001 0011)中1的位数
(integer) 3
> bitcount one-more-key 1 1 #第二个字符(0111 1111)中1的位数
(integer) 7
> bitpos one-more-key 0 #整个字符串中第一个0位
(integer) 0
> bitpos one-more-key 1 #整个字符串中第一个1位
(integer) 3
> bitpos one-more-key 1 0 0 #第一个字符(0001 0011)中第一个1位
(integer) 3
> bitpos one-more-key 1 1 1 #第二个字符(0111 1111)中第一个1位
(integer) 9
复制代码
位图能够应用于各种实时分析,也能够节省空间高效地存储位信息。好比,记录用户天天的签到数据,每个位表示用户是否签到过,这样就能够计算出某个时间段用户签到了几回,某个时间段用户第一次签到是哪一天。
HyperLogLog是一种几率数据结构,用于统计惟一元素的数量,也能够理解为估计集合中元素的个数。
一般状况下,对惟一元素进行统计数量时,须要使用与要统计的元素数量成比例的内存量,由于须要记住过去已经看到的元素,以免屡次对其进行统计。可是,有一组算法能够之内存换取精度,最终会获得带有标准偏差的估计数量,在Redis的HyperLogLogs中,该偏差小于1%。
这个算法的神奇之处在于,再也不须要使用与所统计元素数量成比例的内存量,而可使用恒定数量的内存。在最坏的状况下占据12KB的内存空间,Redis对HyperLogLog的存储进行了优化,在计数比较少时,占据的内存空间会更小,这里先卖个关子,后续的文章会详细介绍其中原理。
在集合中,能够将每一个元素添加到集合中,并使用SCARD命令获取集合中的元素数量,由于SADD命令不会从新添加现有元素,因此元素都是惟一的。HyperLogLog的操做和集合比较相似,使用PFADD命令将元素添加到HyperLogLog中,相似于集合的SADD命令;使用PFCOUNT命令获取HyperLogLog中的惟一元素的当前近似值数量,相似于集合的SCARD命令。好比:
> pfadd one-more-hll a b c d e
(integer) 1
> pfcount one-more-hll
(integer) 5
复制代码
Redis中的HyperLogLog尽管在技术上是不一样的数据结构,但被编码为字符串,所以能够调用GET命令来序列化HyperLogLog,而后调用SET命令来将其反序列化回服务器。
Redis提供更加丰富的数据结构,键(Key)和字符串(String),都是二进制安全的字符串;列表(List),根据插入顺序排序的字符串元素列表,基于链表实现;集合(Set),惟一的乱序的字符串元素的集合;有序集合(Sorted Set),与集合相似,可是每一个字符串元素都与一个称为score的数字相关联;哈希(Hash),由字段与值相关联组成的映射,字段和值都是字符串;位图(Bitmap),像操做位数组同样操做字符串值,能够设置和清除某个位,对全部为1的位进行计数,找到第一个设置1的位,找到第一个设置0的位等等;HyperLogLogs,一种几率数据结构,使用较小的内存空间来统计惟一元素的数量,偏差小于1%。