• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

Redis使用教程(2):入门

Redis winrains 来源:田守芝 1年前 (2019-10-20) 54次浏览

3 入门

学会了如何安装和运行Redis,并了解了Redis的基础知识后,本章将详细介绍Redis的五种数据类型及相应的命令,带领读者真正进入Redis的世界。在学习的时候,手边打开一个redis-cli程序来跟着一起输入命令将会极大地提高学习效率。
在之后的章节中你会遇到两个学习伙伴:小白和宋老师。小白是一个标准的极客,最近刚开始他的Redis学习之旅,而他大学时的计算机老师宋老师恰好对 Redis颇有研究,于是就顺理成章地成为了小白的私人Redis教师。这不,小白想基于Redis开发一个博客,于是找到宋老师,向他请教。在本章中宋老师会向小白介绍Redis最核心的内容——数据类型,从他们的对话中你一定能学到不少知识!
3.2节到3.6节这5节将分别介绍Redis的5种数据类型,其中每节都是由4个部分组成,依次是“介绍”、“命令”、“实践”和“命令拾遗”。“介绍”部分是对数据类型的概述,“命令”部分会对“实践”部分将用到的命令进行介绍,“实践”部分会讲解该数据类型在开发中的应用方法,“命令拾遗”部分会对该数据类型其他比较有用的命令进行补充介绍。

3.1 热身

在介绍 Redis的数据类型之前,我们先来了解几个比较基础的命令作为热身,赶快打开redis-cli,跟着样例亲自输入命令来体验一下吧!

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

KEYS pattern
pattern支持glob风格通配符格式,具体规则如表3-1所示。
QQ截图20160317150048.png
现在Redis中空空如也(如果你从第2章开始就一直跟着本书的进度输入命令,此时数据库中可能还会有个foo键),为了演示KEYS命令,首先我们得给Redis加点料。使用SET命令(会在3.2节介绍)建立一个名为bar的键:

redis>SET bar 1
OK

然后使用KEYS *就能获得Redis中所有的键了(当然由于数据库中只有一个bar键,所以KEYS ba*或者KEYS bar 等命令都能获得同样的结果):

redis> KEYS *
1) "bar"

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

2.判断一个键是否存在

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

redis>EXISTS bar
(integer) 1
redis>EXISTS noexists
(integer)0

3.删除键

DEL key [key …]
可以删除一个或多个键,返回值是删除的键的个数。如:

redis>DEL bar
(integer) 1
redis>DEL bar
(integer) 0

第二次执行DEL命令时因为bar键已经被删除了,实际上并没有删除任何键,所以返回0。
技巧 DEL 命令的参数不支持通配符,但我们可以结合Linux 的管道和xargs 命令自己实现删除所有符合规则的键。比如要删除所有以“user:”开头的键,就可以执行redis-cli KEYS “user:*” | xargs redis-cli DEL。另外由于DEL 命令支持多个键作为参数, 所以还可以执行redis-cli DEL ‘redis-cli KEYS “user:*”‘来达到同样的效果,但是性能更好。

4.获得键值的数据类型

TYPE key
TYPET命令用来获得键值的数据类型,返回值可能是string(字符串类型)、hash(散列类型)、list(列表类型)、set(集合类型)、zset(有序集合类型)。例如:

redis>SET foo 1
OK
redis>TYPE foo
string
redis>LPUSH bar 1
(integer) 1
redis>TYPE bar
list

LPUSHL命令的作用是向指定的列表类型键中增加一个元素,如果键不存在则创建它,3.4节会详细介绍。

3.2 字符串类型

作为一个爱造轮子的资深极客,小白每次看到自己博客最下面的“Powered byWordPress”都觉得有些不舒服,终于有一天他下定决心要开发一个属于自己的博客。但是用腻了MySQL数据库的小白总想尝试一下新技术,恰好上次参加Node Party时听人介绍过Redis数据库,便想着趁机试一试。可小白只知道Redis是一个键值对数据库,其他的一概不知。抱着试一试的态度,小白找到了自己大学时教计算机的宋老师,一问之下欣喜地发现宋老师竟然对Redis颇有研究。宋老师有感于小白的好学,决定给小白开个小灶。

小白:
宋老师您好,我最近听别人介绍过Redis,当时就对它很感兴趣。恰好最近想开发一个博客,准备尝试一下它。有什么能快速学会Redis的方法吗?
宋老师笑着说:
心急吃不了热豆腐,要学会Redis就要先掌握Redis的键值数据类型和相关的命令。Redis不仅支持多种数据类型,而且还为每种数据类型提供了丰富实用的命令。作为开始,我先来讲讲Redis中最基本的数据类型——字符串类型。

3.2.1 介绍

字符串类型是Redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据。你可以用其存储用户的邮箱、JSON化的对象甚至是一张图片。一个字符串类型键允许存储的数据的最大容量是512MB。
字符串类型是其他4种数据类型的基础,其他数据类型和字符串类型的差别从某种角度来说只是组织字符串的形式不同。例如,列表类型是以列表的形式组织字符串,而集合类型是以集合的形式组织字符串。学习过本章后面几节后相信读者对此会有更深的理解。

3.2.2 命令

1.赋值与取值

SET key value
GET key

SETS和GET是Redis中最简单的两个命令,它们实现的功能和编程语言中的读写变量相似,如key=”hello”在Redis中是这样表示的:

redis>SET key hello
OK

想要读取键值则更简单:

redis>GET key
"hello"

当键不存在时会返回空结果。
为了节约篇幅,同时避免读者过早地被编程语言的细节困扰,本书大部分章节将只使用redis-cli进行命令演示(必要的时候会配合伪代码),第5章会专门介绍在编程语言Java中使用Redis的方法。

2.递增数字

INCR key

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

redis>INCR num
(integer) 1
redis>INCR num
(integer) 2

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

redis>SET foo lorem
OK
redis>INCR foo
(error) ERR value is not an integer or out of range

3.2.3 实践

1.文章访问量统计

博客的一个常见的功能是统计文章的访问量,我们可以为每篇文章使用一个名为post:文章ID:page.view的键来记录文章的访问量,每次访问文章的时候使用INCR命令使相应的键值递增。
提示 Redis对于键的命名并没有强制的要求,但比较好的实践是用“对象类型:对象ID:对象属性”来命名一个键,如使用键user:1:friends来存储ID为1的用户的好友列表。对于多个单词则推荐使用“.”分隔,一方面是沿用以前的习惯(Redis以前版本的键名不能包含空格等特殊字符),另一方面是在redis-cli中容易输入,无需使用双引号包裹。另外为了日后维护方便,键的命名一定要有意义,如u:1:f的可读性显然不如user:1:friends好(虽然采用较短的名称可以节省存储空间,但由于键值的长度往往远远大于键名的长度,所以这部分的节省大部分情况下并不如可读性来得重要)。

2.生成自增ID

那么怎么为每篇文章生成一个唯一ID呢?在关系数据库中我们通过设置字段属性为AUTO_INCREMENT来实现每增加一条记录自动为其生成一个唯一的递增ID的目的,而在Redis中可以通过另一种模式来实现:对于每一类对象使用名为对象类型(复数形式):count①的键(如users:count)来存储当前类型对象的数量,每增加一个新对象时都使用INCR命令递增该键的值。由于使用INCR命令建立的键的初始键值是1,所以可以很容易得知,INCR命令的返回值既是加入该对象后的当前类型的对象总数,又是该新增对象的ID。
注释:这个键名只是参考命名,实际使用中可以使用任何容易理解的名称。

3.存储文章数据

由于每个字符串类型键只能存储一个字符串,而一篇博客文章是由标题、正文、作者与发布时间等多个元素构成的。为了存储这些元素,我们需要使用序列化函数(如PHP中的serialize和JavaScript中的JSON.stringify)将它们转换成一个字符串。

3.2.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

DECRBY命令的作用不用介绍想必读者就可以猜到,DECRBY key 5 相当于INCRBY key -5。

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

STRLENST命令返回键值的长度,如果键不存在则返回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 …]

MGETM/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个命令可以直接对二进制位进行操作。为了演示,我们首先将foo键赋值为bar:

redis>SET foo bar
OK

bar的3个字母对应的ASCII码分别为98、97和114,转换成二进制后分别为1100010、1100001和1110010,所以foo键中的二进制位结构如图3-3所示。
QQ截图20160317153810.png
GETBIT命令可以获得一个字符串类型键指定位置的二进制位的值(0或1),索引从0开始:

redis>GETBIT foo 0
(integer) 0
redis>GETBIT foo 6
(integer) 1

如果需要获取的二进制位的索引超出了键值的二进制位的实际长度则默认位值是0:

redis>GETBIT foo 100000
(integer) 0

SETBIT 命令可以设置字符串类型键指定位置的二进制位的值,返回值是该位置的旧值。如我们要将foo键值设置为aar,可以通过位操作将foo键的二进制位的索引第6位设为0,第7位设为1:

redis>SETBIT foo 6 0
(integer) 1
redis>SETBIT foo 7 1
(integer) 0
redis>GET foo
"aar"

如果要设置的位置超过了键值的二进制位的长度,SETBIT命令会自动将中间的二进制位设置为0,同理设置一个不存在的键的指定二进制位的值会自动将其前面的位赋值为0:

redis>SETBIT nofoo 10 1
(integer) 0
redis>GETBIT nofoo 5
(integer) 0

BITCOUNTB命令可以获得字符串类型键中值是1的二进制位个数,例如:

redis>BITCOUNT foo
(integer)10

可以通过参数来限制统计的字节范围,如我们只希望统计前两个字节(即”aa”):

redis>BITCOUNT foo 0 1
(integer)6

BITOPB命令可以对多个字符串类型键进行位运算,并将结果存储在destkey参数指定的键中。BITOP命令支持的运算操作有AND、OR、XORNOT。如我们可以对bar和aar进行OR运算:

redis>SET foo1 bar
OK
redis>SET foo2 aar
OK
redis>BITOP OR res foo1 foo2
(integer) 3
redis>GET res
"car"

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

3.3 散列类型

小白只用了半个多小时就把访问统计和发表文章两个部分做好了。同时借助Bootstrap框架,老师花了一小会儿时间教会了之前只涉猎过HTML的小白如何做出一个像样的网页界面。

接着小白发问:
接下来我想要做的功能是博客的文章列表页,我设想在列表页中每个文章只显示标题部分,可是使用您刚才介绍的方法,若想取得文章的标题,必须把整个文章数据字符串取出来反序列化,而其中占用空间最大的文章內容部分却是不需要的,这样难道不会在传输和处理时造成资源浪费吗?
老师有些惊喜地看着小白答道:“很对!”同时以一个夸张的幅度点了下头,接着说:
这正是我接下来准备讲的。不仅取数据时会有资源浪费,在修改数据时也会有这个问题,比如当你只想更改文章的标题时也不得不把整个文章数据字符串更新一遍。
没等小白再问,老师就又继续说道:
前面我说过Redis的强大特性之一就是提供了多种实用的数据类型,其中的散列类型可以非常好地解决这个问题。

3.3.1 介绍

我们现在已经知道Redis是采用字典结构以键值对的形式存储数据的,而散列类型(hash)的键值也是一种字典结构,其存储了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他数据类型,换句话说,散列类型不能嵌套其他的数据类型。一个散列类型键可以包含至多232-1个字段。
提示 除了散列类型,Redis的其他数据类型同样不支持数据类型嵌套。比如集合类型的每个元素都只能是字符串,不能是另一个集合或散列表等。
散列类型适合存储对象:使用对象类别和ID构成键名,使用字段表示对象的属性,而字段值则存储属性值。例如要存储ID为2的汽车对象,可以分别使用名为color、name和price的3个字段来存储该辆汽车的颜色、名称和价格。存储结构如图3-5所示。
QQ截图20160317154650.png
回想在关系数据库中如果要存储汽车对象,存储结构如表3-2所示。
QQ截图20160317154731.png
数据是以二维表的形式存储的,这就要求所有的记录都拥有同样的属性,无法单独为某条记录增减属性。如果想为ID为1的汽车增加生产日期属性,就需要把数据表更改为如表3-3所示的结构。
QQ截图20160317154815.png
对于ID为2和3的两条记录而言date字段是冗余的。可想而知当不同的记录需要不同的属性时,表的字段数量会越来越多以至于难以维护。
而Redis的散列类型则不存在这个问题。虽然我们在图3-5中描述了汽车对象的存储结构,但是这个结构只是人为的约定,Redis并不要求每个键都依据此结构存储,我们完全可以自由地为任何键增减字段而不影响其他键。

3.3.2 命令

1.赋值与取值

HSET key field value
HGET key field
HMSET key field value [field value …]
HMGET key field [field …]
HGETALL key

HSET命令用来给字段赋值,而HGET命令用来获得字段的值。用法如下:

redis>HSET car price 500
(integer) 1
redis>HSET car name BMW
(integer) 1
redis>HGET car name
"BMW"

HSET命令的方便之处在于不区分插入和更新操作,这意味着修改数据时不用事先判断字段是否存在来决定要执行的是插入操作(update)还是更新操作(insert)。当执行的是插入操作时(即之前字段不存在)HSET命令会返回1,当执行的是更新操作时(即之前字段已经存在)HSET命令会返回0。更进一步,当键本身不存在时,HSET命令还会自动建立它。
提示 在Redis中每个键都属于一个明确的数据类型,如通过HSET命令建立的键是散列类型,通过SET命令建立的键是字符串类型等。使用一种数据类型的命令操作另一种数据类型的键会提示错误:“ERR Operation against a key holding the wrong kind of value”。
注释:并不是所有命令都是如此,比如SET命令可以覆盖已经存在的键而不论原来键是什么类型。
当需要同时设置多个字段的值时,可以使用HMSET命令。例如,下面两条语句

HSET key field1 value1
HSET key field2 value2

可以用HMSET命令改写成

HMSET key field1 value1 field2 value2

相应地,HMGET命令可以同时获得多个字段的值:

redis>HMGET car price name
1) "500"
2) "BMW"

如果想获取键中所有字段和字段值却不知道键中有哪些字段时(如3.3.1节介绍的存储汽车对象的例子,每个对象拥有的属性都未必相同)应该使用HGETALL命令。如:

redis>HGETALL car
1) "price"
2) "500"
3) "name"
4) "BMW"

返回的结果是字段和字段值组成的列表,不是很直观,好在很多语言的Redis客户端会将HGETALL的返回结果封装成编程语言中的对象,处理起来就非常方便了。

2.判断字段是否存在

HEXISTS key field

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

redis>HEXISTS car model
(integer) 0
redis>HSET car model C200
(integer) 1
redis>HEXISTS car model
(integer) 1

3.当字段不存在时赋值

HSETNX key field value

HSETNXH 命令与HSET命令类似,区别在于如果字段已经存在,HSETNX命令将不执行任何操作。其实现可以表示为如下伪代码:
注释:HSETNX 中的“NX”表示“if Not eXists”(如果不存在)。

def hsetnx( key, field, value)
isExists=HEXISTS key, field
if isExists is 0
HSET key, field, value
return 1
else
return 0

只不过HSETNX命令是原子操作,不用担心竞态条件。

4.增加数字

HINCRBY key field increment

上一节的命令拾遗部分介绍了字符串类型的命令INCRBY,HINCRBY命令与之类似,可以使字段值增加指定的整数。散列类型没有HINCR命令,但是可以通过HINCRBY key field 1来实现。
HINCRBY命令的示例如下:

redis>HINCRBY person score 60
(integer) 60

之前person键不存在,HINCRBY命令会自动建立该键并默认score字段在执行命令前的值为“0”。命令的返回值是增值后的字段值。

5.删除字段

HDEL key field [field …]

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

redis>HDEL car price
(integer) 1
redis>HDEL car price
(integer) 0

3.3.3 实践

1.存储文章数据

3.2.3节介绍了可以将文章对象序列化后使用一个字符串类型键存储,可是这种方法无法提供对单个字段的原子读写操作支持,从而产生竞态条件,如两个客户端同时获得并反序列化某个文章的数据,然后分别修改不同的属性后存入,显然后存入的数据会覆盖之前的数据,最后只会有一个属性被修改。另外如小白所说,即使只需要文章标题,程序也不得不将包括文章内容在内的所有文章数据取出并反序列化,比较消耗资源。除此之外,还有一种方法是组合使用多个字符串类型键来存储一篇文章的数据,如图3-6所示。
QQ截图20160317161445.png
使用这种方法的好处在于无论获取还是修改文章数据,都可以只对某一属性进行操作,十分方便。而本章介绍的散列类型则更适合此场景,使用散列类型的存储结构如图3-7所示。
QQ截图20160317161823.png
从图3-7可以看出使用散列类型存储文章数据比图3-6所示的方法看起来更加直观也更容易维护(比如可以使用HGETALL命令获得一个对象的所有字段,删除一个对象时只需要删除一个键),另外存储同样的数据散列类型往往比字符串类型更加节约空间,具体的细节会在4.6节中介绍。

2.存储文章缩略名

使用过WordPress的读者可能会知道发布文章时一般需要指定一个缩略名(slug)来构成该篇文章的网址的一部分,缩略名必须符合网址规范且最好可以与文章标题含义相似,如“This Is A Great Post!”的缩略名可以为“this-is-a-great-post”。每个文章的缩略名必须是唯一的,所以在发布文章时程序需要验证用户输入的缩略名是否存在,同时也需要通过缩略名获得文章的ID。
我们可以使用一个散列类型的键slug.to.id来存储文章缩略名和ID之间的映射关系。其中字段用来记录缩略名,字段值用来记录缩略名对应的ID。这样就可以使用HEXISTS命令来判断缩略名是否存在,使用HGET命令来获得缩略名对应的文章ID了。

3.3.4 命令拾遗

1.只获取字段名或字段值

HKEYS key
HVALS key

有时仅仅需要获取键中所有字段的名字而不需要字段值,那么可以使用HKEYS命令,就像这样:

redis>HKEYS car
1) "name"
2) "model"

HVALS命令与HKEYS命令相对应,HVALS命令用来获得键中所有字段值,例如:

redis>HVALS car
1) "BMW"
2) "C200"

2.获得字段数量

HLEN key

例如:

redis>HLEN car
(integer) 2

3.4 列表类型

3.4.1 介绍

列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。
列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的(和从只有20个元素的列表中获取头部或尾部的10条记录的速度是一样的)。
不过使用链表的代价是通过索引访问元素比较慢,设想在iPad mini发售当天有1000个人在三里屯的苹果店排队等候购买,这时苹果公司宣布为了感谢大家的排队支持,决定奖励排在第486位的顾客一部免费的iPad mini。为了找到这第486位顾客,工作人员不得不从队首一个一个地数到第486个人。但同时,无论队伍多长,新来的人想加入队伍的话直接排到队尾就好了,和队伍里有多少人没有任何关系。这种情景与列表类型的特性很相似。
这种特性使列表类型能非常快速地完成关系数据库难以应付的场景:如社交网站的新鲜事,我们关心的只是最新的内容,使用列表类型存储,即使新鲜事的总数达到几千万个,获取其中最新的100条数据也是极快的。同样因为在两端插入记录的时间复杂度是0(1),列表类型也适合用来记录日志,可以保证加入新日志的速度不会受到已有日志数量的影响。
借助列表类型,Redis还可以作为队列使用,4.4节会详细介绍。
与散列类型键最多能容纳的字段数量相同,一个列表类型键最多能容纳232-1个元素。

3.4.2 命令

1.向列表两端增加元素

LPUSH key value [value …]
RPUSH key value [value …]

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

redis>LPUSH numbers 1
(integer) 1

这时numbers键中的数据如图3-8所示。LPUSH命令还支持同时增加多个元素,例如:
QQ截图20160317165509.png

redis>LPUSH numbers 2 3
(integer) 3

LPUSH会先向列表左边加入”2″,然后再加入”3″,所以此时numbers键中的数据如图3-9所示。
QQ截图20160317165549.png
向列表右边增加元素的话则使用RPUSH命令,其用法和LPUSH命令一样:

redis>RPUSH numbers 0 -1
(integer) 5

此时numbers 键中的数据如图3-10所示。
QQ截图20160317165625.png

2.从列表两端弹出元素

LPOP key
RPOP key

有进有出,LPOP命令可以从列表左边弹出一个元素。LPOP命令执行两步操作:第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。例如,从numbers列表左边弹出一个元素(也就是”3″):

redis>LPOP numbers
"3"

此时numbers键中的数据如图3-11所示。
QQ截图20160317165709.png
同样,RPOP命令可以从列表右边弹出一个元素:

redis>RPOP numbers
"-1"

此时numbers键中的数据如图3-12所示。
QQ截图20160317165743.png
结合上面提到的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的时间复杂度为0(1),使用时Redis会直接读取现成的值,而不需要像部分关系数据库(如使用InnoDB存储引擎的MySQL 表)那样需要遍历一遍数据表来统计条目数量。

4.获得列表片段

LRANGE key start stop

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

redis>LRANGE numbers 0 2
1) "2"
2) "1"
3) "0"

LRANGE命令在取得列表片段的同时不会像LPOP一样删除该片段,另外LRANGE命令与很多语言中用来截取数组片段的方法slice有一点区别是LRANGE返回的值包含最右边的元素,如在JavaScript中:

var numbers=[2, 1, 0];
console.log(numbers.slice(0, 2)); //返回数组:[2, 1]

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

redis>LRANGE numbers -2 -1
1) "1"
2) "0"

显然,LRANGE numbers 0 -1可以获取列表中的所有元素。另外一些特殊情况如下。
(1)如果start的索引位置比stop的索引位置靠后,则会返回空列表。
(2)如果stop大于实际的索引范围,则会返回到列表最右边的元素:

redis>LRANGE numbers 1 999
1) "1"
2) "0"

5.删除列表中指定的值

LREM key count value

LREM命令会删除列表中前count个值为value的元素,返回值是实际删除的元素个数。根据count值的不同,LREM命令的执行方式会略有差异:
●当count>0时LREM命令会从列表左边开始删除前count个值为value的元素;
●当count<0时LREM 命令会从列表右边开始删除前|count|个值为value的元素;
●当count=0是LREM命令会删除所有值为value的元素。例如:

redis>RPUSH numbers 2
(integer) 4
redis>LRANGE numbers 0 -1
1) "2"
2) "1"
3) "0"
4) "2"

#从右边开始删除第一个值为”2″的元素

redis>LREM numbers -1 2
(integer) 1
redis>LRANGE numbers 0 -1
1) "2"
2) "1"
3) "0"

3.4.3 命令拾遗

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

LINDEX key index
LSET key index value

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

redis>LINDEX numbers 0
"2"

如果index是负数则表示从右边开始计算的索引,最右边元素的索引是-1。例如:

redis>LINDEX numbers -1
"0"

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

redis>LSET numbers 1 7
OK
redis>LINDEX numbers 1
"7"

2.只保留列表指定片段

LTRIM key start end

LTRIM命令可以删除指定索引范围之外的所有元素,其指定列表范围的方法和LRANGE命令相同。就像这样:

redis>LRANGE numbers 0 1
1) "1"
2) "2"
3) "7"
4) "3"
"0"
redis>LTRIM numbers 1 2
OK
redis>LRANGE numbers 0 1
1) "2"
2) "7"

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

LPUSH logs newLog
LTRIM logs 0 99

3.向列表中插入元素

LINSERT key BEFORE|AFTER pivot value

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

redis>LRANGE numbers 0 -1
1) "2"
2) "7"
3) "0"
redis>LINSERT numbers AFTER 7 3
(integer) 4
redis>LRANGE numbers 0 -1
1) "2"
2) "7"
3) "3"
4) "0"
redis>LINSERT numbers BEFORE 2 1
(integer) 5
redis>LRANGE numbers 0 -1
1) "1"
2) "2"
3) "7"
4) "3"
5) "0"

4.将元素从一个列表转到另一个列表R

POPLPUSH source destination

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

def rpoplpush( source, destination)
value=RPOP source
LPUSH destination, value
return value

当把列表类型作为队列使用时,RPOPLPUSH命令可以很直观地在多个队列中传递数据。当source和destination相同时,RPOPLPUSH命令会不断地将队尾的元素移到队首,借助这个特性我们可以实现一个网站监控系统:使用一个队列存储需要监控的网址,然后监控程序不断地使用RPOPLPUSH命令循环取出一个网址来测试可用性。这里使用RPOPLPUSH命令的好处在于在程序执行过程中仍然可以不断地向网址列表中加入新网址,而且整个系统容易扩展,允许多个客户端同时处理队列。

3.5 集合类型

3.5.1 介绍

集合的概念高中的数学课就学习过。在集合中的每个元素都是不同的,且没有顺序。一个集合类型(set)键可以存储至多232-1个(相信这个数字对大家来说已经很熟悉了)字符串。集合类型和列表类型有相似之处,但很容易将它们区分开来,如表3-4所示。
QQ截图20160317171515.png
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型在Redis内部是使用值为空的散列表(hash table)实现的,所以这些操作的时间复杂度都是0(1)。最方便的是多个集合类型键之间还可以进行并集、交集和差集运算,稍后就会看到灵活
运用这一特性带来的便利。

3.5.2 命令

1.增加/删除元素

SADD key member [member …]
SREM key member [member …]

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

redis>SADD letters a
(integer) 1
redis> SADD letters a b c
(integer) 2

第二条SADD命令的返回值为2是因为元素“a”已经存在,所以实际上只加入了两个元素。
SREM命令用来从集合中删除一个或多个元素,并返回删除成功的个数,例如:

redis>SREM letters c d
(integer) 1

由于元素“d”在集合中不存在,所以只删除了一个元素,返回值为1。

2.获得集合中的所有元素

SMEMBERS key

SMEMBERS命令会返回集合中的所有元素,例如:

redis>SMEMBERS letters
1) "b"
2) "a"

3.判断元素是否在集合中

SISMEMBER key member

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

redis>SISMEMBER letters a
(integer) 1
redis>SISMEMBER letters d
(integer) 0

4.集合间运算

SDIFF key [key …]
SINTER key [key …]
SUNION key [key …]

接下来要介绍的3个命令都是用来进行多个集合间运算的。
(1)SDIFF命令用来对多个集合执行差集运算。集合A与集合B的差集表示为A-B,代表所有属于A且不属于B的元素构成的集合(如图3-13所示),即A-B={x|x∈A且x∈/B}。例如:
QQ截图20160317171730.png
{1, 2, 3}-{2, 3, 4}={1}
{2, 3, 4}-{1, 2, 3}={4}
SDIFF命令的使用方法如下:

redis>SADD setA 1 2 3
(integer) 3
redis>SADD setB 2 3 4
(integer) 3
redis>SDIFF setA setB
1) "1"
redis>SDIFF setB setA
1 ) "4"

SDIFF 命令支持同时传入多个键,例如:

redis>SADD setC 2 3
(integer) 2
redis>SDIFF setA setB setC
1 ) "1"

计算顺序是先计算setA-setB,再计算结果与setC的差集。
(2)SINTER命令用来对多个集合执行交集运算。集合A与集合B的交集表示为A∩B,代表所有属于A且属于B的元素构成的集合(如图3-14所示),即A∩B={x|x∈A且x∈B}。例如:
QQ截图20160317171813.png
{1, 2, 3}∩{2, 3, 4}={2, 3}
SINTER命令的使用方法如下:

redis>SINTER setA setB
1) "2"
2) "3"

SINTER命令同样支持同时传入多个键,如:

redis>SINTER setA setB setC
1) "2"
2) "3"

(3)SUNION命令用来对多个集合执行并集运算。集合A与集合B的并集表示为AUB,代表所有属于A或属于B的元素构成的集合(如图3-15所示),即AUB={x|x∈A 或x∈B}。例如:
QQ截图20160317171857.png
{1, 2, 3}∪{2, 3, 4}={1, 2, 3, 4}
SUNION命令的使用方法如下:

redis>SUNION setA setB
1) "1"
2) "2"
3) "3"
4) "4"

SUNION命令同样支持同时传入多个键,例如:

redis>SUNION setA setB setC
1) "1"
2) "2"
3) "3"
4) "4"

3.5.3 命令拾遗

1.获得集合中元素个数

SCARD key

SCARD命令用来获得集合中的元素个数,例如:

redis>SMEMBERS letters
1) "b"
2) "a"
redis>SCARD letters
(integer) 2

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

SDIFFSTORE destination key [key …]
SINTERSTORE destination key [key …]
SUNIONSTORE destination key [key …]

SDIFFSTORE命令和SDIFF命令功能一样,唯一的区别就是前者不会直接返回运算结果,而是将结果存储在destination键中。
SDIFFSTORE命令常用于需要进行多步集合运算的场景中,如需要先计算差集再将结果和其他键计算交集。
SINTERSTORESUNIONSTORE命令与之类似,不再赘述。

3.随机获得集合中的元素

SRANDMEMBER key [count]

SRANDMEMBER命令用来随机从集合中获取一个元素,如:

redis>SRANDMEMBER letters
"a"
redis>SRANDMEMBER letters
"b"
redis>SRANDMEMBER letters
"a"

还可以传递count参数来一次随机获得多个元素,根据count的正负不同,具体表现也不同。
(1)当count为正数时,SRANDMEMBER会随机从集合里获得count个不重复的元素。如果count的值大于集合中的元素个数,则SRANDMEMBER会返回集合中的全部元素。
(2)当count为负数时,SRANDMEMBER会随机从集合里获得|count|个的元素,这些元素有可能相同。
为了示例,我们先在letters集合中加入两个元素:

redis>SADD letters c d
(integer) 2

目前letters集合中共有“a”、“b”、“c”、“d”4个元素,下面使用不同的参数对SRANDMEMBER命令进行测试:

redis>SRANDMEMBER letters 2
1) "a"
2) "c"
redis>SRANDMEMBER letters 2
1) "a"
2) "b"
redis>SRANDMEMBER letters 100
1) "b"
2) "a"
3) "c"
4) "d"
redis>SRANDMEMBER letters -2
1) "b"
2) "b"
redis>SRANDMEMBER letters -10
1) "b"
2) "b"
3) "c"
4) "c"
5) "b"
6) "a"
7) "b"
8) "d"
9) "b"
10) "b"

细心的读者可能会发现SRANDMEMBER命令返回的数据似乎并不是非常的随机,从SRANDMEMBER letters -10这个结果中可以很明显地看出这个问题(b元素出现的次数相对较多① ),出现这种情况是由集合类型采用的存储结构(散列表)造成的。散列表使用散列函数将元素映射到不同的存储位置(桶)上以实现0(1)时间复杂度的元素查找,举个例子,当使用散列表存储元素b时,使用散列函数计算出b的散列值是0,所以将b存入编号为0 的桶(bucket)中,下次要查找b时就可以用同样的散列函数再次计算b的散列值并直接到相应的桶中找到b。当两个不同的元素的散列值相同时会出现冲突,Redis使用拉链法来解决冲突,即将散列值冲突的元素以链表的形式存入同一桶中,查找元素时先找到元素对应的桶,然后再从桶中的链表中找到对应的元素。使用SRANDMEMBER命令从集合中获得一个随机元素时,Redis首先会从所有桶中随机选择一个桶,然后再从桶中的所有元素中随机选择一个元素,所以元素所在的桶中的元素数量越少,其被随机选中的可能性就越大,如图3-19所示。注释:①如果你亲自跟着输入了命令可能会发现得到的结果与书中的结果并不相同,这是正常现象,见后文描述。
QQ截图20160317172122.png
图3-19 Redis会先从3个桶中随机挑一个非空的桶,然后再从桶中随机选择一个元素,所以选中元素b的概率会大一些

4.从集合中弹出一个元素

SPOP key

3.4节中我们学习过LPOP命令,作用是从列表左边弹出一个元素(即返回元素的值并删除它)。SPOP命令的作用与之类似,但由于集合类型的元素是无序的,所以SPOP命令会从集合中随机选择一个元素弹出。例如:

redis>SPOP letters
"b"
redis>SMEMBERS letters
1) "a"
2) "c"
3) "d"

3.6 有序集合类型

3.6.1 介绍
有序集合类型(sorted set)的特点从它的名字中就可以猜到,它与上一节介绍的集合类型的区别就是“有序”二字。

在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。
有序集合类型在某些方面和列表类型有些相似。
(1)二者都是有序的。
(2)二者都可以获得某一范围的元素。
但是二者有着很大的区别,这使得它们的应用场景也是不同的。
(1)列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用。
(2)有序集合类型是使用散列表和跳跃表(Skip list)实现的,所以即使读取位于中间部分的数据速度也很快(时间复杂度是O(log(N)))。
(3)列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)。
(4)有序集合要比列表类型更耗费内存。
有序集合类型算得上是 Redis的5种数据类型中最高级的类型了,在学习时可以与列表类型和集合类型对照理解。

3.6.2 命令

1.增加元素

ZADD key score member [score member …]

ZADD命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。ZADD命令的返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)。
假设我们用有序集合模拟计分板,现在要记录Tom、Peter和David三名运动员的分数(分别是89分、67分和100分):

redis>ZADD scoreboard 89 Tom 67 Peter 100 David
(integer) 3

这时我们发现Peter的分数录入有误,实际的分数应该是76分,可以用ZADD命令修改Peter的分数:

redis>ZADD scoreboard 76 Peter
(integer) 0

分数不仅可以是整数,还支持双精度浮点数:

redis>ZADD testboard 17E+307 a
(integer) 1
redis>ZADD testboard 1.5 b
(integer) 1
redis>ZADD testboard +inf c
(integer) 1
redis>ZADD testboard -inf d
(integer) 1

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

2.获得元素的分数

ZSCORE key member

示例如下:

redis>ZSCORE scoreboard Tom
"89"

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

ZRANGE key start stop [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES]

ZRANGE命令会按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)。ZRANGE命令与LRANGE命令十分相似,如索引都是从0开始,负数代表从后向前查找(-1表示最后一个元素)。就像这样:

redis>ZRANGE scoreboard 0 2
1) "Peter"
2) "Tom"
3) "David"
redis>ZRANGE scoreboard 1 -1
1) "Tom"
2) "David"

如果需要同时获得元素的分数的话可以在ZRANGE命令的尾部加上WITHSCORES参数,这时返回的数据格式就从“元素1, 元素2, …, 元素n”变为了“元素1, 分数1, 元素2, 分数2, …, 元素n, 分数n”,例如:

redis>ZRANGE scoreboard 0 -1 WITHSCORES
1) "Peter"
2) "76"
3) "Tom"
4) "89"
5) "David"
6) "100"

ZRANGE命令的时间复杂度为0(logn+m)(其中n为有序集合的基数,m为返回的元素个数)。
如果两个元素的分数相同,Redis会按照字典顺序(即”0″<”9″<”A”<”Z”<”a”<”z”这样的顺序)来进行排列。再进一步,如果元素的值是中文怎么处理呢?答案是取决于中文的编码方式,如使用UTF-8编码:

redis>ZADD chineseName 0 马华 0 刘墉 0 司马光 0 赵哲
(integer) 4
redis>ZRANGE chineseName 0 -1
1) "\xe5\x88\x98\xe5\xa2\x89"
2) "\xe5\x8f\xb8\xe9\xa9\xac\xe5\x85\x89"
3) "\xe8\xb5\xb5\xe5\x93\xb2"
4) "\xe9\xa9\xac\xe5\x8d\x8e"

可见此时Redis依然按照字典顺序排列这些元素。
ZREVRANGE命令和ZRANGE的唯一不同在于ZREVRANGE命令是按照元素分数从大到小的顺序给出结果的。

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"

min和max还支持无穷大,同ZADD命令一样,-inf 和+inf分别表示负无穷和正无穷。比如你希望得到所有分数高于80分(不包含80分)的人的名单,但你却不知道最高分是多少(虽然有些背离现实,但是为了叙述方便,这里假设可以获得的分数是无上限的),这时就可以用上+inf了:

redis>ZRANGEBYSCORE scoreboard (80 +inf
1) "Tom"
2) "David"

WITHSCORES参数的用法与ZRANGE命令一样,不再赘述。
了解SQL语句的读者对LIMIT offset count应该很熟悉,在本命令中LIMIToffset count与SQL中的用法基本相同,即在获得的元素列表的基础上向后偏移offset个元素,并且只获取前count个元素。为了便于演示,我们先向scoreboard键中再增加些元素:

redis>ZADD scoreboard 56 Jerry 92 Wendy 67 Yvonne
(integer) 3

现在scoreboard键中的所有元素为:

redis>ZRANGE scoreboard 0 -1 WITHSCORES
1) "Jerry"
2) "56"
3) "Yvonne"
4) "67"
5) "Peter"
6) "76"
7) "Tom"
8) "89"
9) "Wendy"
10) "92"
11) "David"
12) "100"

想获得分数高于60分的从第二个人开始的3个人:

redis>ZRANGEBYSCORE scoreboard 60 +inf LIMIT 1 3
1) "Peter"
2) "Tom"
3) "Wendy"

那么,如果想获取分数低于或等于100分的前3个人怎么办呢?这时可以借助ZREVRANGEBYSCORE命令实现。对照前文提到的ZRANGE命令和ZREVRANGE命令之间的关系,相信读者很容易能明白ZREVRANGEBYSCORE命令的功能。需要注意的是ZREVRANGEBYSCORE命令不仅是按照元素分数从大往小的顺序给出结果的,而且它的min和max参数的顺序和ZRANGEBYSCORE命令是相反的。就像这样:

redis>ZREVRANGEBYSCORE scoreboard 100 0 LIMIT 0 3
1) "David"
2) "Wendy"
3) "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.6.4 命令拾遗

1.获得集合中元素的数量

ZCARD key

例如:

redis>ZCARD scoreboard
(integer) 6

2.获得指定分数范围內的元素个数

ZCOUNT key min max

例如:

redis>ZCOUNT scoreboard 90 100
(integer) 2

ZCOUNT命令的min和max参数的特性与ZRANGEBYSCORE命令中的一样:

redis>ZCOUNT scoreboard (89 +inf
(integer) 2

3.删除一个或多个元素

ZREM key member [member …]

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

redis>ZREM scoreboard Wendy
(integer) 1
redis>ZCARD scoreboard
(integer) 5

4.按照排名范围删除元素

ZREMRANGEBYRANK key start stop

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

redis>ZADD testRem 1 a 2 b 3 c 4 d 5 e 6 f
(integer) 6
redis>ZREMRANGEBYRANK 0 2
(integer) 3
redis>ZRANGE testRem 0 -1
1) "d"
2) "e"
3) "f"

5.按照分数范围删除元素

ZREMRANGEBYSCORE key min max

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

redis>ZREMRANGEBYSCORE testRem (4 5
(integer) 1
redis>ZRANGE testRem 0 -1
1) "d"
2) "f"

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 …]]
[AGREGATE SUM|MIN|MAX]

ZINTERSTORE命令用来计算多个有序集合的交集并将结果存储在destination键中(同样以有序集合类型存储),返回值为destination键中的元素个数。
destination键中元素的分数是由AGGREGATE参数决定的。
(1)当AGGREGATE是SUM时(也就是默认值),destination键中元素的分数是每个参与计算的集合中该元素分数的和。例如:

redis>ZADD sortedSets1 1 a 2 b
(integer) 2
redis>ZADD sortedSets2 10 a 20 b
(integer) 2
redis>ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2
(integer) 2
redis>ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "11"
3) "b"
4) "22"

(2)当AGGREGATE是MIN时,destination键中元素的分数是每个参与计算的集合中该元素分数的最小值。例如:

redis>ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 AGGREGATE MIN
(integer) 2
redis>ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "1"
3) "b"
4) "2"

(3)当AGGREGATE是MAX时,destination键中元素的分数是每个参与计算的集合中该元素分数的最大值。例如:

redis>ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 AGGREGATE MAX
(integer) 2
redis>ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "10"
3) "b"
4)"20"

ZINTERSTORE命令还能够通过WEIGHTS参数设置每个集合的权重,每个集合在参与计算时元素的分数会被乘上该集合的权重。例如:

redis>ZINTERSTORE sortedSetsResult 2 sortedSets1 sortedSets2 WEIGHTS 1 0.1
(integer) 2
redis>ZRANGE sortedSetsResult 0 -1 WITHSCORES
1) "a"
2) "2"
3) "b"
4) "4"

另外还有一个命令与ZINTERSTORE命令的用法一样,名为ZUNIONSTORE,它的作用是计算集合间的并集,这里不再赘述。

3.7 HyperLogLog

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

实例

以下实例演示了 HyperLogLog 的工作过程:

redis 127.0.0.1:6379> PFADD w3ckey "redis"
1) (integer) 1
redis 127.0.0.1:6379> PFADD w3ckey "mongodb"
1) (integer) 1
redis 127.0.0.1:6379> PFADD w3ckey "mysql"
1) (integer) 1
redis 127.0.0.1:6379> PFCOUNT w3ckey
(integer) 3

Redis HyperLogLog 命令

序号 命令及描述
1 PFADD key element [element …]
添加指定元素到 HyperLogLog 中。
2 PFCOUNT key [key …]
返回给定 HyperLogLog 的基数估算值。
3 PFMERGE destkey sourcekey [sourcekey …]
将多个 HyperLogLog 合并为一个 HyperLogLog

作者:田守芝

来源:http://www.tianshouzhi.com/api/tutorials/redis/179

 


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (0)