《Redis入门指南(第二版)》读书思考总结之Redis五大数据类型

热身:系统级命令

1. 获得符合规则的键名列表

KEYS pattern

     模式匹配

    产品的缓存:product+"."+....;  => keys product*

    订单的缓存:order+"."+...;    => keys order*

使用 KEYS *能获得Redis中的所有键

提示:KEYS命令需要遍历Redis中的所有键,当键的数量较多时会影响性能,不建议在生产环境中使用。Redis不区分命令大小写。

 

2. 判断一个键是否存在

EXISTS key 

如果键存在则返回整数类型1,否则返回0。

3. 删除键

DEL key [key …]

可以删除一个或多个键,返回值是删除的键的个数。

4. 获得键值的数据类型

 TYPE key

5.清空数据库

Flushdb

6.过期时间

Expire(秒为单位)

  如果要做缓存,那么一定要有一个过期时间。

7.查看剩余存活时间。

ttl 查看key还有多长存活时间。(秒为单位)

 

  为了让你更全面地了解 Redis的每种数据类型,接下来我会先讲解如何将Redis作为数据库使用,但是实际上 Redis可不只是数据库这么简单,更多的公司和团队将 Redis用作缓存和队列系统,而这部分内容等你掌握了Redis的基础后我会再进行介绍,下面将介绍redis的五大数据类型,并通过实战一个博客管理系统的原理来讲解他们的应用。

一、字符串类型

1. 介绍

  字符串类型是 Redis 中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据。你可以用其存储用户的邮箱、JSON 化的对象甚至是一张图片。一个字符串类型键允许存储的数据的最大容量是512 MB。

  字符串类型是其他4种数据类型的基础,其他数据类型和字符串类型的差别从某种角度来说只是组织字符串的形式不同。例如,列表类型是以列表的形式组织字符串,而集合类型是以集合的形式组织字符串。

2. 命令

(1)赋值与取值

SET key value

GET key

  key等于”hello”在Redis中是这样表示的:

redis> SET key hello

OK

  想要读取键值则更简单:

redis> GET key

"hello"

  当键不存在时会返回空结果。

(2) 递增数字

INCR key

  前面说过字符串类型可以存储任何形式的字符串,当存储的字符串是整数形式时, Redis 提供了一个实用的命令 INCR,其作用是让当前键值递增,并返回递增后的值,用法为:

redis> INCR num

(integer) 1

redis> INCR num

(integer) 2

  当要操作的键不存在时会默认键值为0,所以第一次递增后的结果是1。当键值不是整数时Redis会提示错误。

  包括 INCR在内的所有Redis命令都是原子操作(atomic operation)的。

  原子操作取“原子”的“不可拆分”的意思,原子操作是最小的执行单位,不会在执行的过程中被其他命令插入打断。

3. 实战应用

(1) 博客文章访问量统计

我们可以为每篇文章使用一个名为post:文章ID:page.view的键来记录文章的访问量,每次访问文章的时候使用INCR命令使相应的键值递增。

Redis 对于键的命名并没有强制的要求,但比较好的实践是用“对象类型:对象ID:对象属性”来命名一个键,如使用键user:1:friends来存储ID为1的用户的好友列表。对于多个单词则推荐使用“.”分隔。

(2)生成自增ID

Redis中的实现方法:对于每一类对象使用名为对象类型(复数形式):count的键(如users:count)来存储当前类型对象的数量,每增加一个新对象时都使用INCR命令递增该键的值。由于使用INCR命令建立的键的初始键值是1,所以可以很容易得知, INCR命令的返回值既是加入该对象后的当前类型的对象总数,又是该新增对象的ID。

(3)存储文章数据

由于每个字符串类型键只能存储一个字符串,而一篇博客文章是由标题、正文、作者与发布时间等多个元素构成的。为了存储这些元素,我们需要使用序列化函数(如PHP中的 serialize和JavaScript中的 JSON.stringify)将它们转换成一个字符串。除此之外因为字符串类型键可以存储二进制数据,所以也可以使用MessagePack进行序列化,速度更快,占用空间也更小。

4. 其他命令

(1)增加指定的整数

INCRBY key increment

INCRBY命令与INCR命令基本一样,只不过前者可以通过increment参数指定一次增加的数值,如:

redis> INCRBY bar 2

(integer) 2

redis> INCRBY bar 3

(integer) 5

(2)减少指定的整数

DECR key DECRBY key decrement

  DECR命令与INCR命令用法相同,只不过是让键值递减,例如:

redis> DECR bar

(integer) 4

(3)增加指定浮点数

INCRBYFLOAT key increment

  INCRBYFLOAT命令类似INCRBY命令,差别是前者可以递增一个双精度浮点数,如:

redis> INCRBYFLOAT bar 2.7

"6.7"

redis> INCRBYFLOAT bar 5E+4

"50006.69999999999999929"

(4)向尾部追加值

APPEND key value

APPEND作用是向键值的末尾追加value。如果键不存在则将该键的值设置为value,即相当于 SET key value。返回值是追加后字符串的总长度。

如:

redis> SET key hello

OK

redis> APPEND key " world!"

(integer) 12

此时 key 的值是"hello world!"。

APPEND 命令的第二个参数加了双引号,原因是该参数包含空格,在redis-cli中输入需要双引号以示区分。

(5)获取字符串长度

STRLEN key

STRLEN命令返回键值的长度,如果键不存在则返回0。

例如:

redis> STRLEN key

(integer) 12

redis> SET key 你好

OK

redis> STRLEN key

(integer) 6

  字符串类型可以存储二进制数据,所以它可以存储任何编码的字符串。例子中Redis接收到的是使用UTF-8编码的中文,由于“你”和“好”两个字的UTF-8编码的长度都是3,所以此例中会返回6。

(6)同时获得/设置多个键值

MGET key [key …]

MSET key value [key value …]

MGET/MSET 与GET/SET 相似,不过MGET/MSET 可以同时获得/设置多个键的键值。

例如:

redis> MSET key1 v1 key2 v2 key3 v3

OK

redis> GET key2

"v2"

redis> MGET key1 key3

1) "v1"

2) "v3"

(7)位操作

GETBIT key offset

SETBIT key offset value

BITCOUNT key [start] [end]

BITOP operation destkey key [key …]

一个字节由8个二进制位组成,Redis提供了4个命令可以直接对二进制位进行操作。

1)GETBIT命令可以获得一个字符串类型键指定位置的二进制位的值(0或1),索引从0开始,如果需要获取的二进制位的索引超出了键值的二进制位的实际长度则默认位值是0。

2)SETBIT 命令可以设置字符串类型键指定位置的二进制位的值,返回值是该位置的旧值。

3)BITCOUNT命令可以获得字符串类型键中值是1的二进制位个数。

4)BITOP命令可以对多个字符串类型键进行位运算,并将结果存储在destkey参数指定的键中。BITOP命令支持的运算操作有AND、OR、XOR和NOT。

5)Redis 2.8.7引入了 BITPOS命令,可以获得指定键的第一个位值是0或者1的位置。

如果不设置结束字节且键值的所有二进制位都是1,则当要查询值为0的二进制位偏移量时,返回结果会是键值长度的下一个字位的偏移量。这是因为 Redis 会认为键值长度之后的二进制位都是0。

(7)位操作应用举例

利用位操作命令可以非常紧凑地存储布尔值。比如如果网站的每个用户都有一个递增的整数ID,如果使用一个字符串类型键配合位操作来记录每个用户的性别(用户ID作为索引,二进制位值1和0表示男性和女性),那么记录100万个用户的性别只需占用100 KB多的空间,而且由于GETBIT和SETBIT的时间复杂度都是O(1),所以读取二进制位值性能很高。

使用 SETBIT 命令时,如果当前键的键值长度小于要设置的二进制位的偏移量时,Redis会自动分配内存并将键值的当前长度到指定的偏移量之间的二进制位都设置为0。如果要分配的内存过大,则很可能会造成服务器的暂时阻塞而无法接收同一时间的其他请求。

还是举刚才存储网站用户性别的例子,如果这个网站的用户ID是从100000001开始的,那么会造成10多MB的浪费,正确的做法是给每个用户的ID减去100000000再进行存储。

二、散列类型

1. 介绍

 

 

  我们现在已经知道 Redis 是采用字典结构以键值对的形式存储数据的,而散列类型(hash)的键值也是一种字典结构,其存储了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他数据类型,换句话说,散列类型不能嵌套其他的数据类型。一个散列类型键可以包含至多2^32−1个字段。

  除了散列类型,Redis 的其他数据类型同样不支持数据类型嵌套。比如集合类型的每个元素都只能是字符串,不能是另一个集合或散列表等。

  散列类型适合存储对象:使用对象类别和 ID 构成键名,使用字段表示对象的属性,而字段值则存储属性值。例如要存储ID为2的汽车对象,可以分别使用名为color、name和price的3个字段来存储该辆汽车的颜色、名称和价格。

 

 

  回想关系数据库中存储汽车对象:

 

 

  增加一个属性后对于ID为2和3的两条字段而言data字段是冗余的。

  Redis 的散列类型则不存在这个问题。虽然我们在图 3-5 中描述了汽车对象的存储结构,但是这个结构只是人为的约定,Redis并不要求每个键都依据此结构存储,我们完全可以自由地为任何键增减字段而不影响其他键。

2. 命令

(1)HSET命令用来给字段赋值,而HGET命令用来获得字段的值。

  HSET 命令的方便之处在于不区分插入和更新操作,这意味着修改数据时不用事先判断字段是否存在来决定要执行的是插入操作(update)还是更新操作(insert)。当执行的是插入操作时(即之前字段不存在)HSET命令会返回1,当执行的是更新操作时(即之前字段已经存在)HSET命令会返回0。更进一步,当键本身不存在时,HSET命令还会自动建立它。

  需要同时设置多个字段的值时,可以使用HMSET命令。

  Redis中每个键都属于一个明确的数据类型,如通过 HSET命令建立的键是散列类型,通过SET命令建立的键是字符串类型等等。使用一种数据类型的命令操作另一种数据类型的键会提示错误:"ERR Operation against a key holding the wrong kind of value"。

  如果想获取键中所有字段和字段值却不知道键中有哪些字段时应该使用HGETALL命令。如:

redis> HGETALL car

1) "price"

2) "500"

3) "name"

4) "BMW"

(2)判断字段是否存在

 HEXISTS key field

  HEXISTS命令用来判断一个字段是否存在。如果存在则返回1,否则返回0(如果键不存在也会返回0)。

(3)当字段不存在时赋值

HSETNX key field value

HSETNX命令与HSET命令类似,区别在于如果字段已经存在,HSETNX命令将不执行任何操作。

(4)增加指定数字

HINCRBY key field increment

(5)删除字段

HDEL key field [field …]

HDEL命令可以删除一个或多个字段,返回值是被删除的字段个数:

redis> HDEL car price

(integer) 1

redis> HDEL car price

(integer) 0

3. 实战应用

(1)存储文章数据

 

  可以使用 HGETALL 命令获得一个对象的所有字段,删除一个对象时只需要删除一个键,另外存储同样的数据散列类型往往比字符串类型更加节约空间。

(2)存储文章缩略名

  每个文章的缩略名必须是唯一的,所以在发布文章时程序需要验证用户输入的缩略名是否存在,同时也需要通过缩略名获得文章的ID。

  我们可以使用一个散列类型的键slug.to.id来存储文章缩略名和ID之间的映射关系。其中字段用来记录缩略名,字段值用来记录缩略名对应的ID。这样就可以使用HEXISTS命令来判断缩略名是否存在,使用HGET命令来获得缩略名对应的文章ID了。

4. 其他命令

(1)只获取字段名或字段值

HKEYS key

HVALS key

(2)获得字段数量

HLEN key

例如:

redis> HLEN car

(integer) 2

三、列表类型

1. 介绍

  列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。

  列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的(和从只有20个元素的列表中获取头部或尾部的10条记录的速度是一样的),不过使用链表的代价是通过索引访问元素比较慢。

  这种特性使列表类型能非常快速地完成关系数据库难以应付的场景:

  如社交网站的新鲜事,我们关心的只是最新的内容,使用列表类型存储,即使新鲜事的总数达到几千万个,获取其中最新的100条数据也是极快的。同样因为在两端插入记录的时间复杂度是O(1),列表类型也适合用来记录日志,可以保证加入新日志的速度不会受到已有日志数量的影响。

  与散列类型键最多能容纳的字段数量相同,一个列表类型键最多能容纳2^32−1个元素。

2.命令

(1)向列表两端增加元素

 LPUSH key value [value …]

 RPUSH key value [value …]

LPUSH命令用来向列表左边增加元素,返回值表示增加元素后列表的长度。

redis> LPUSH numbers 1

(integer) 1

LPUSH命令还支持同时增加多个元素,例如:

redis> LPUSH numbers 2 3

(integer) 3

LPUSH会先向列表左边加入"2",然后再加入"3",所以此时numbers键中的数据如图3-9所示。

向列表右边增加元素的话则使用RPUSH命令。

(2)从列表两端弹出元素

LPOP key

RPOP key

有进有出,LPOP命令可以从列表左边弹出一个元素。LPOP命令执行两步操作:第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。

例如,从 numbers列表左边弹出一个元素(也就是"3"):

redis> LPOP numbers

"3"

RPOP命令可以从列表右边弹出一个元素。

  结合上面提到的 4 个命令可以使用列表类型来模拟栈和队列的操作:如果想把列表当做栈,则搭配使用LPUSH和LPOP或RPUSH和RPOP,如果想当成队列,则搭配使用LPUSH和RPOP或RPUSH和LPOP。

(3)获取列表中元素的个数

 LLEN key

当键不存在时LLEN会返回0:

redis> LLEN numbers

(integer) 3

  LLEN 命令的功能类似SQL语句 SELECT COUNT(*) FROM table_name,但是 LLEN的时间复杂度为O(1),使用时Redis会直接读取现成的值,而不需要像部分关系数据库(如使用InnoDB存储引擎的MySQL表)那样需要遍历一遍数据表来统计条目数量。

(4)获得列表片段

LRANGE key start stop

 

  LRANGE命令是列表类型最常用的命令之一,它能够获得列表中的某一片段。LRANGE命令将返回索引从 start到 stop之间的所有元素(包含两端的元素)。与大多数人的直觉相同,Redis的列表起始索引为0。

  LRANGE命令也支持负索引,表示从右边开始计算序数,如"−1"表示最右边第一个元素,"-2"表示最右边第二个元素,依次类推。

显然,LRANGE numbers 0 -1 可以获取列表中的所有元素。

另外一些特殊情况如下:

1)如果start的索引位置比stop的索引位置靠后,则会返回空列表。

2)如果stop大于实际的索引范围,则会返回到列表最右边的元素。

(5)删除列表中指定的值

LREM key count value

LREM命令会删除列表中前count个值为value的元素,返回值是实际删除的元素个数。根据count值的不同,LREM命令的执行方式会略有差异。

1)当 count > 0时 LREM 命令会从列表左边开始删除前 count 个值为 value的元素。

2)当 count < 0时 LREM 命令会从列表右边开始删除前|count|个值为 value 的元素。  

3)当 count = 0是 LREM命令会删除所有值为 value的元素。

3. 实战应用

(1)存储文章ID列表

我们使用列表类型键posts:list记录文章ID列表。当发布新文章时使用LPUSH命令把新文章的ID加入这个列表中,另外删除文章时也要记得把列表中的文章ID 删除,就像这样:

LREM posts:list 1 要删除的文章的ID

有了文章 ID列表,就可以使用 LRANGE命令来实现文章的分页显示了。

(2)存储评论列表

我们可以将一条评论的各个元素序列化成字符串后作为列表类型键中的元素来存储。使用列表类型键post:文章的ID:comments来存储某个文章的所有评论。

发布评论的伪代码如下(以ID为42的文章为例):

#将评论序列化成字符串

$serializedComment = serialize($author, $email, $time, $content)

LPUSH post:42:comments, $serializedComment

读取评论时同样使用LRANGE命令即可。

4.其他命令

(1)获得/设置指定索引的元素值

LINDEX key index

LSET key index value

  如果要将列表类型当作数组来用,LINDEX命令是必不可少的。LINDEX命令用来返回指定索引的元素,索引从0开始。

  LSET是另一个通过索引操作列表的命令,它会将索引为index的元素赋值为value。

例如:

redis> LSET numbers 1 7

OK

redis> LINDEX numbers 1

"7"

(2)只保留列表指定片段

  LTRIM key start end LTRIM 命令可以删除指定索引范围之外的所有元素,其指定列表范围的方法和LRANGE命令相同。

  LTRIM命令常和LPUSH命令一起使用来限制列表中元素的数量,比如记录日志时我们希望只保留最近的100条日志,则每次加入新元素时调用一次LTRIM命令即可:

LPUSH logs $newLog

LTRIM logs 0 99

(3)向列表中插入元素

LINSERT key BEFORE|AFTER pivot value

 

  LINSERT 命令首先会在列表中从左到右查找值为 pivot 的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面。

  LINSERT命令的返回值是插入后列表的元素个数。

(4)将元素从一个列表转到另一个列表

RPOPLPUSH source destination

  RPOPLPUSH是个很有意思的命令,从名字就可以看出它的功能:先执行RPOP命令再执行LPUSH命令。RPOPLPUSH命令会先从source列表类型键的右边弹出一个元素,然后将其加入到destination列表类型键的左边,并返回这个元素的值,整个过程是原子的。

  当把列表类型作为队列使用时,RPOPLPUSH 命令可以很直观地在多个队列中传递数据。当source和destination相同时,RPOPLPUSH命令会不断地将队尾的元素移到队首,借助这个特性我们可以实现一个网站监控系统:

  使用一个队列存储需要监控的网址,然后监控程序不断地使用 RPOPLPUSH 命令循环取出一个网址来测试可用性。这里使用RPOPLPUSH命令的好处在于在程序执行过程中仍然可以不断地向网址列表中加入新网址,而且整个系统容易扩展,允许多个客户端同时处理队列。

四、集合类型

1.介绍

  集合的概念高中的数学课就学习过。在集合中的每个元素都是不同的,且没有顺序。一个集合类型(set)键可以存储至多2^32 −1个(相信这个数字对大家来说已经很熟悉了)字符串。

  集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型在Redis内部是使用值为空的散列表(hash table)实现的,所以这些操作的时间复杂度都是O(1)。最方便的是多个集合类型键之间还可以进行并集、交集和差集运算,稍后就会看到灵活运用这一特性带来的便利。

2.命令

(1)增加/删除元素

SADD key member [member …]

SREM key member [member …]

SADD 命令用来向集合中增加一个或多个元素,如果键不存在则会自动创建。因为在一个集合中不能有相同的元素,所以如果要加入的元素已经存在于集合中就会忽略这个元素。本命令的返回值是成功加入的元素数量(忽略的元素不计算在内)。

SREM命令用来从集合中删除一个或多个元素,并返回删除成功的个数。

(2)获得集合中的所有元素

SMEMBERS key

SMEMBERS命令会返回集合中的所有元素。

(3)判断元素是否在集合中

 SISMEMBER key member

判断一个元素是否在集合中是一个时间复杂度为O(1)的操作,无论集合中有多少个元素,SISMEMBER命令始终可以极快地返回结果。当值存在时 SISMEMBER命令返回1,当值不存在或键不存在时返回0。

(4)集合间运算

SDIFF key [key „]

SINTER key [key „]

SUNION key [key „]

1)SDIFF命令用来对多个集合执行差集运算。集合A与集合B的差集表示为A−B,代表所有属于A且不属于B的元素构成的集合。

2)SINTER命令用来对多个集合执行交集运算。集合A与集合B的交集表示为A ∩ B,代表所有属于A 且属于B的元素构成的集合。

3)SUNION命令用来对多个集合执行并集运算。

3. 实战应用

(1)存储文章标签

考虑到一个文章的所有标签都是互不相同的,而且展示时对这些标签的排列顺序并没有要求,我们可以使用集合类型键存储文章标签。

具体操作伪代码如下:

# 给 ID 为 42 的文章增加标签:
SADD post:42:tags, 闲言碎语, 技术文章, Java
# 删除标签:
SREM post:42:tags, 闲言碎语
# 显示所有的标签:
$tags = SMEMBERS post:42:tags
print $tags

 

(2)通过标签搜索文章

  有时我们还需要列出某个标签下的所有文章,甚至需要获得同时属于某几个标签的文章列表,这种需求在传统关系数据库中实现起来比较复杂,下面举一个例子。

  现有3张表,即posts、tags和posts_tags,分别存储文章数据、标签、文章与标签的对应关系。结构分别如表3-5、表3-6、表3-7所示。

 

 

为了找到同时属于“Java”、“MySQL”和“Redis”这3个标签的文章,需要使用如下的SQL语句:

SELECT p.post_title

FROM posts_tags pt,

posts p,

tags t

WHERE pt.tag_id = t.tag_id

AND (t.tag_name IN ('Java', 'MySQL', 'Redis'))

AND p.post_id = pt.post_id

GROUP BY p.post_id HAVING COUNT(p.post_id)=3;

  可以很明显看到这样的 SQL 语句不仅效率相对较低,而且不易阅读和维护。而使用Redis可以很简单直接地实现这一需求。

  具体做法是为每个标签使用一个名为tag:标签名称:posts的集合类型键存储标有该标签的文章ID列表。假设现在有3篇文章,ID分别为1、2、3,其中ID为1的文章标签是“Java”,ID 为 2 的文章标签是“Java”、“MySQL”,ID 为 3 的文章标签是“Java”、“MySQL”和“Redis”,则有关标签部分的存储结构如图3-18所示:

 

  最简单的,当需要获取标记“MySQL”标签的文章时只需要使用命令 SMEMBERS tag:MySQL:posts即可。如果要实现找到同时属于Java、MySQL和Redis 3 个标签的文章,只需要将tag:Java:posts、tag:MySQL:posts和tag:Redis:posts这3个键取交集,借助SINTER命令即可轻松完成。

4. 其他命令

(1)获得集合中元素个数

SCARD key

SCARD命令用来获得集合中的元素个数。

(2)进行集合运算并将结果存储

 SDIFFSTORE destination key [key …]

 SINTERSTORE destination key [key …]

 SUNIONSTORE destination key [key …]

 SDIFFSTORE命令和SDIFF命令功能一样,唯一的区别就是前者不会直接返回运算结果,而是将结果存储在destination键中。

SDIFFSTORE命令常用于需要进行多步集合运算的场景中,如需要先计算差集再将结果和其他键计算交集。SINTERSTORE和SUNIONSTORE命令与之类似,不再赘述。

(3)随机获得集合中的元素

SRANDMEMBER key [count]

SRANDMEMBER命令用来随机从集合中获取一个元素,还可以传递count参数来一次随机获得多个元素。

根据count的正负不同,具体表现也不同。

1)当count为正数时,SRANDMEMBER会随机从集合里获得count个不重复的元素。如果count的值大于集合中的元素个数,则SRANDMEMBER会返回集合中的全部元素。

2)当count为负数时,SRANDMEMBER会随机从集合里获得|count|个的元素,这些元素有可能相同。

(4)从集合中弹出一个元素

SPOP key

由于集合类型的元素是无序的,所以 SPOP命令会从集合中随机选择一个元素弹出。

五、有序集合类型

1. 介绍

在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。

有序集合类型在某些方面和列表类型有些相似。

(1)二者都是有序的。

(2)二者都可以获得某一范围的元素。

但是二者有着很大的区别,这使得它们的应用场景也是不同的。

(1)列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用。

(2)有序集合类型是使用散列表和跳跃表(Skip list)实现的,所以即使读取位于中间部分的数据速度也很快(时间复杂度是O(log(N)))。

(3)列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)。

(4)有序集合要比列表类型更耗费内存。

有序集合类型算得上是Redis的5种数据类型中最高级的类型了,在学习时可以与列表类型和集合类型对照理解。

2. 命令

(1)增加元素

ZADDkey score member [score member …]

  ZADD 命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。ZADD命令的返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)。

  +inf和-inf分别表示正无穷和负无穷。

(2)获得元素的分数

 ZSCORE key member

(3)获得排名在某个范围的元素列表

ZRANGE key start stop [WITHSCORES]

ZREVRANGE key start stop [WITHSCORES]

ZRANGE命令会按照元素分数从小到大的顺序返回索引从 start到stop之间的所有元素(包含两端的元素)。负数代表从后向前查找(−1表示最后一个元素)。

如果需要同时获得元素的分数的话可以在 ZRANGE 命令的尾部加上 WITHSCORES 参数。

ZRANGE命令的时间复杂度为O(log n+m)(其中n为有序集合的基数,m为返回的元素个数)。

(4)获得指定分数范围的元素

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

ZRANGEBYSCORE 命令参数虽然多,但是都很好理解。该命令按照元素分数从小到大的顺序返回分数在min和max之间(包含min和max)的元素:

redis> ZRANGEBYSCORE scoreboard 80 100

1) "Tom"

2) "David"

如果希望分数范围不包含端点值,可以在分数前加上“(”符号。例如,希望返回80分到100分的数据,可以含80分,但不包含100分,则稍微修改一下上面的命令即可:

redis> ZRANGEBYSCORE scoreboard 80 (100

1)  "Tom"

(5)增加某个元素的分数

ZINCRBY key increment member

ZINCRBY 命令可以增加一个元素的分数,返回值是更改后的分数。例如,想给 Jerry加4分:

redis> ZINCRBY scoreboard 4 Jerry

"60"

increment也可以是个负数表示减分,例如,给Jerry减4分:

redis> ZINCRBY scoreboard -4 Jerry

"56"

如果指定的元素不存在,Redis 在执行命令前会先建立它并将它的分数赋为 0 再执行操作。

3. 实战应用

(1)实现按点击量排序

要按照文章的点击量排序,就必须再额外使用一个有序集合类型的键来实现。在这个键中以文章的 ID 作为元素,以该文章的点击量作为该元素的分数。将该键命名为posts:page.view,每次用户访问一篇文章时,博客程序就通过 ZINCRBY posts:page. view 1 文章 ID更新访问量。

需要按照点击量的顺序显示文章列表时,有序集合的用法与列表的用法大同小异:

$postsPerPage = 10

$start = ($currentPage - 1) * $postsPerPage

$end = $currentPage * $postsPerPage - 1

$postsID = ZREVRANGE posts:page.view, $start$end

for each $id in $postsID

$postData = HGETALL post:$id

print 文章标题:$postData.title

另外介绍字符串类型时用键post:文章ID:page.view来记录单个文章的访问量,现在这个键已经不需要了,想要获得某篇文章的访问量可以通过 ZSCORE posts:page. view文章ID 来实现。

(2)更改文章发布时间和获得指定时间范围内的文章列表

为了能够自由地更改文章发布时间,可以采用有序集合类型代替列表类型。自然地,元素仍然是文章的ID,而此时元素的分数则是文章发布的Unix时间。通过修改元素对应的分数就可以达到更改时间的目的。另外借助 ZREVRANGEBYSCORE 命令还可以轻松获得指定时间范围的文章列表,借助这个功能可以实现类似WordPress的按月份查看文章的功能。

4. 其他命令

(1)获得集合中元素的数量

ZCARD key

例如:

redis> ZCARD scoreboard

(integer) 6

(2)获得指定分数范围内的元素个数

ZCOUNT key min max

例如:

redis> ZCOUNT scoreboard 90 100

(integer) 2

(3)删除一个或多个元素

ZREM key member [member …]

ZREM命令的返回值是成功删除的元素数量(不包含本来就不存在的元素)。

(4)按照排名范围删除元素

ZREMRANGEBYRANK key start stop

ZREMRANGEBYRANK 命令按照元素分数从小到大的顺序(即索引0表示最小的值)删除处在指定排名范围内的所有元素,并返回删除的元素数量。

(5)按照分数范围删除元素

ZREMRANGEBYSCORE key min max

ZREMRANGEBYSCORE命令会删除指定分数范围内的所有元素,参数min和max的特性和ZRANGEBYSCORE命令中的一样。返回值是删除的元素数量。

(6)获得元素的排名

ZRANK key member

ZREVRANK key member

ZRANK命令会按照元素分数从小到大的顺序获得指定的元素的排名

(从0开始,即分数最小的元素排名为0)。如:

redis> ZRANK scoreboard Peter

(integer) 0

ZREVRANK命令则相反(分数最大的元素排名为0):

redis> ZREVRANK scoreboard Peter

(integer) 4

(7)计算有序集合的交集

ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight…]] [AGGREGATESUM|MIN|MAX]

ZINTERSTORE命令用来计算多个有序集合的交集并将结果存储在destination键中(同样以有序集合类型存储),返回值为destination键中的元素个数。

destination键中元素的分数是由AGGREGATE参数决定的。

(1)当AGGREGATE是SUM时(也就是默认值),destination键中元素

的分数是每个参与计算的集合中该元素分数的和。

(2)当AGGREGATE是MIN时,destination键中元素的分数是每个参与

计算的集合中该元素分数的最小值。

(3)当AGGREGATE是MAX时,destination键中元素的分数是每个参与

计算的集合中该元素分数的最大值。

ZINTERSTORE命令还能够通过WEIGHTS参数设置每个集合的权重,每

个集合在参与计算时元素的分数会被乘上该集合的权重。

总结

 

数据类型

 

结构存储的值

 

结构的读写能力

 

博客系统中的应用

 

字符串类型

可以是字符串、整数或者浮点数

对整个字符串或字符串的其中一部分执行操作;对整数和浮点数执行自增或者自减操作

(1) 博客文章访问量统计

(2)生成自增ID

 

散列类型

包含键值对的无序散列表

添加、获取、移除单个键值对;获取所有键值对

(1)存储文章数据

(2)存储文章缩略名

 

列表类型

一个链表,链表上的每个节点都包含了一个字符串

从链表的两端推入或者弹出元素;根据偏移量对链表进行修剪(trim);读取单个或多个元素;根据值查找或者移除元素

(1)存储文章ID列表

(2)存储评论列表

 

集合类型

包含字符串的无序收集器,并且被包含的每个字符串都独一无二、各不相同

添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集;从集合里面随机获取元素

(1)存储文章标签

(2)通过标签搜索文章

 

有序集合类型

字符串成员与浮点数分值之间的有序映射,元素的排列顺序由分值的大小决定

添加、获取、删除单个元素;根据分值范围或者成员来获取元素

(1)实现按点击量排序

(2)更改文章发布时间和获得指定时间范围内的文章列表

 

posted @ 2016-11-23 20:56  千年风雅丶  阅读(1912)  评论(1编辑  收藏  举报