Redis基础数据结构

2018/02/04 Redis

Redis基础数据类型

Redis 不仅仅支持简单的key-value类型的数据,同时还提供List,Set,Sorted Set,Hash等数据结构的存储。

  • 字符串(String)
  • 散列(Hash)
  • 列表(List)
  • 集合(Set)
  • 有序集合(Sorted Set)
  • 位图 ( Bitmaps )
  • 基数统计 ( HyperLogLogs )
  • 地理空间信息(Geo)
  • 流(Stream)

字符串(String)

字符串(string)键是Redis最基本的键值对类型,这种类型的键值对会在数据库中把单独的一个键和单独的一个值关联起来,被关联的键和值既可以是普通的文字数据,也可以是图片、视频、音频、压缩文件等更为复杂的二进制数据。

Redis为字符串键提供了一系列操作命令,通过使用这些命令,用户可以:

  • 为字符串键设置值。
  • 获取字符串键的值。
  • 在获取旧值的同时为字符串键设置新值。
  • 同时为多个字符串键设置值,或者同时获取多个字符串键的值。
  • 获取字符串值的长度。
  • 获取字符串值指定索引范围内的内容,或者对字符串值指定索引范围内的内容进行修改。
  • 将一些内容追加到字符串值的末尾。
  • 对字符串键存储的整数值或者浮点数值执行加法操作或减法操作。

SET:为字符串键设置值

创建字符串键最常用的方法就是使用SET命令,这个命令可以为一个字符串键设置相应的值。在最基本的情况下,用户只需要向SET命令提供一个键和一个值就可以了:

SET key value

与之前提到过的一样,这里的键和值既可以是文字也可以是二进制数据。 比如通过执行以下命令,我们可以创建出一个键为”book”,值为”The Design and Implementation of Redis”的字符串键:

redis> SET book "The Design and Implementation of Redis"
OK

Redis数据库是以无序的方式存放数据库键的,一个新加入的键可能会出现在数据库的任何位置上,因此我们在使用Redis的过程中不应该对键在数据库中的摆放位置做任何假设,以免造成错误。

在默认情况下,对一个已经设置了值的字符串键执行SET命令将导致键的旧值被新值覆盖。举个例子,如果我们连续执行以下两条SET命令,那么第一条SET命令设置的值将被第二条SET命令设置的值所覆盖。

从Redis 2.6.12版本开始,用户可以通过向SET命令提供可选的NX选项或者XX选项来指示SET命令是否要覆盖一个已经存在的值:

SET key value [NX|XX]

如果用户在执行SET命令时给定了NX选项,那么SET命令只会在键没有值的情况下执行设置操作,并返回OK表示设置成功;如果键已经存在,那么SET命令将放弃执行设置操作,并返回空值nil表示设置失败。 以下代码展示了带有NX选项的SET命令的行为:

redis> SET password "123456" NX
OK       -- 对尚未有值的password键进行设置,成功
redis> SET password "999999" NX
(nil)    -- password键已经有了值,设置失败

因为第二条SET命令没有改变password键的值,所以password键的值仍然是刚开始时设置的”123456”。

如果用户在执行SET命令时给定了XX选项,那么SET命令只会在键已经有值的情况下执行设置操作,并返回OK表示设置成功;如果给定的键并没有值,那么SET命令将放弃执行设置操作,并返回空值表示设置失败。

redis> SET mysql-homepage "mysql.org"
OK    -- 为键mysql-homepage设置一个值
redis> SET mysql-homepage "mysql.com" XX
OK    -- 对键的值进行更新

在第二条SET命令执行之后,mysql-homepage键的值将从原来的”mysql.org”更新为”mysql.com”。

注意:不带任何可选项的SET命令从Redis 1.0.0版本开始可用;带有NX、XX等可选项的SET命令从Redis 2.6.12版本开始可用。

GET:获取字符串键的值

用户可以使用GET命令从数据库中获取指定字符串键的值:

GET key

GET命令接受一个字符串键作为参数,然后返回与该键相关联的值。 我们可以通过执行以下GET命令来取得各个字符串键相关联的值:

redis> GET message
"hello world"

另外,如果用户给定的字符串键在数据库中并没有与之相关联的值,那么GET命令将返回一个空值:

redis> GET date
(nil)

上面这个GET命令的执行结果表示数据库中并不存在date键,也没有与之相关联的值。

因为Redis的数据库要求所有键必须拥有与之相关联的值,所以如果一个键有值,那么我们就说这个键存在于数据库;相反,如果一个键没有值,那么我们就说这个键不存在于数据库。

GETSET:获取旧值并设置新值

GETSET命令就像GET命令和SET命令的组合版本,GETSET首先获取字符串键目前已有的值,接着为键设置新值,最后把之前获取到的旧值返回给用户:

GETSET key new_value

以下代码展示了如何使用GETSET命令去获取number键的旧值并为它设置新值:

redis> GET number    -- number键现在的值为"10086"
"10086"
redis> GETSET number "12345"
"10086"              -- 返回旧值
redis> GET number    -- number键的值已被更新为"12345"
"12345"

如果被设置的键并不存在于数据库,那么GETSET命令将返回空值作为键的旧值:

锁是一种同步机制,用于保证一项资源在任何时候只能被一个进程使用,如果有其他进程想要使用相同的资源,那么就必须等待,直到正在使用资源的进程放弃使用权为止。

一个锁的实现通常会有获取(acquire)和释放(release)这两种操作:

  • 获取操作用于取得资源的独占使用权。在任何时候,最多只能有一个进程取得锁,我们把成功取得锁的这个进程称为锁的持有者。在锁已经被持有的情况下,所有尝试再次获取锁的操作都会失败。
  • 释放操作用于放弃资源的独占使用权,一般由锁的持有者调用。在锁被释放之后,其他进程就可以再次尝试获取这个锁了。

获取操作acquire()方法是通过执行带有NX选项的SET命令来实现的: NX选项的值确保了代表锁的字符串键只会在没有值的情况下被设置:

  • 如果给定的字符串键没有值,那么说明锁尚未被获取,SET命令将执行设置操作,并将result变量的值设置为True。
  • 如果给定的字符串键已经有值了,那么说明锁已经被获取,SET命令将放弃执行设置操作,并将result变量的值设置为None。

acquire()方法最后会通过检查result变量的值是否为True来判断自己是否成功取得了锁。

release()方法通过检查delete()方法的返回值是否为1来判断删除操作是否执行成功:如果用户尝试对一个尚未被获取的锁执行release()方法,那么方法将返回false,表示没有锁被释放。

在使用DEL命令删除代表锁的字符串键之后,字符串键将重新回到没有值的状态,这时用户就可以再次调用acquire()方法去获取锁了。

锁实现了基本的获取和释放功能,但它并不完美:

  • 因为这个锁的释放操作无法验证进程的身份,所以无论执行释放操作的进程是否为锁的持有者,锁都会被释放。如果锁被持有者以外的其他进程释放,那么系统中可能会同时出现多个锁,导致锁的唯一性被破坏。
  • 这个锁的获取操作不能设置最大加锁时间,因而无法让锁在超过给定的时限之后自动释放。因此,如果持有锁的进程因为故障或者编程错误而没有在退出之前主动释放锁,那么锁就会一直处于已被获取的状态,导致其他进程永远无法取得锁。

MSET:一次为多个字符串键设置值

Redis还提供了MSET命令用于对字符串键进行设置。MSET命令可以一次为多个字符串键设置值:

MSET key value [key value ...]

以下代码展示了如何使用一条MSET命令去设置message、number和homepage这3个键:

redis> MSET message "hello world" number "10086" homepage "redis.io"
OK

MSET命令也会在执行设置操作之后返回OK表示设置成功。此外,如果给定的字符串键已经有相关联的值,那么MSET命令也会直接使用新值去覆盖已有的旧值。

MSET命令除了可以让用户更为方便地执行多个设置操作之外,还能有效地提高程序的效率:执行多条SET命令需要客户端和服务器之间进行多次网络通信,并因此耗费大量的时间;而使用一条MSET命令去代替多条SET命令只需要一次网络通信,从而有效地减少程序执行多个设置操作时的时间。

MGET:一次获取多个字符串键的值

MGET命令就是一个多键版本的GET命令,MGET接受一个或多个字符串键作为参数,并返回这些字符串键的值:

MGET key [key ...]

MGET命令返回一个列表作为结果,这个列表按照用户执行命令时给定键的顺序排列各个键的值。比如,列表的第一个元素就是第一个给定键的值,第二个元素是第二个给定键的值,以此类推。 作为例子,以下代码展示了如何使用一条MGET命令去获取message、number和homepage这3个键的值:

redis> MGET message number homepage
1) "hello world"    -- message键的值
2) "10086"          -- number键的值
3) "redis.io"       -- homepage键的值

MGET命令在碰到不存在的键时也会返回空值:

redis> MGET not-exists-key
1) (nil)

MGET命令也可以将执行多个获取操作所需的网络通信次数从原来的N次降低至只需一次,从而有效地提高程序的运行效率。

MSETNX:只在键不存在的情况下,一次为多个字符串键设置值

MSETNX命令与MSET命令一样,都可以对多个字符串键进行设置:

MSETNX key value [key value ...]

MSETNX与MSET的主要区别在于,MSETNX只会在所有给定键都不存在的情况下对键进行设置,而不会像MSET那样直接覆盖键已有的值:如果在给定键当中,即使有一个键已经有值了,那么MSETNX命令也会放弃对所有给定键的设置操作。MSETNX命令在成功执行设置操作时返回1,在放弃执行设置操作时则返回0。

在以下代码中,因为键k4已经存在,所以MSETNX将放弃对键k1、k2、k3和k4进行设置操作:

redis> MGET k1 k2 k3 k4
1) (nil)            -- 键k1、 k2和k3都不存在
2) (nil)
3) (nil)
4) "hello world"    -- 键k4已存在

redis> MSETNX k1 "one" k2 "two" k3 "three" k4 "four"
(integer) 0    -- 因为键k4已存在,所以MSETNX未能执行设置操作

redis> MGET k1 k2 k3 k4    -- 各个键的值没有变化
1) (nil)
2) (nil)
3) (nil)
4) "hello world"

STRLEN:获取字符串值的字节长度

通过对字符串键执行STRLEN命令,用户可以取得字符串键存储的值的字节长度:

STRLEN key

以下代码展示了如何使用STRLEN去获取不同字符串值的字节长度:

redis> GET number
"10086"
redis> STRLEN number    -- number键的值长5字节
(integer) 5
redis> GET message
"hello world"
redis> STRLEN message   -- message键的值长11字节
(integer) 11

对于不存在的键,STRLEN命令将返回0:

redis> STRLEN not-exists-key
(integer) 0

GETRANGE:获取字符串值指定索引范围上的内容

通过使用GETRANGE命令,用户可以获取字符串值从start索引开始,直到end索引为止的所有内容:

GETRANGE key start end

GETRANGE命令接受的是闭区间索引范围,也就是说,位于start索引和end索引上的值也会被包含在命令返回的内容当中。

举个例子,以下代码展示了如何使用GETRANGE命令去获取message键的值的不同部分:

redis> GETRANGE message 0 4     -- 获取字符串值索引0至索引4上的内容
"hello"
redis> GETRANGE message 6 10    -- 获取字符串值索引6至索引10上的内容
"world"

SETRANGE:对字符串值的指定索引范围进行设置

通过使用SETRANGE命令,用户可以将字符串键的值从索引index开始的部分替换为指定的新内容,被替换内容的长度取决于新内容的长度:

SETRANGE key index substitute

SETRANGE命令在执行完设置操作之后,会返回字符串值当前的长度作为结果。

例如,我们可以通过执行以下命令,将message键的值从原来的”hello world”修改为”hello Redis”:

redis> GET message
"hello world"
redis> SETRANGE message 6 "Redis"
(integer) 11    -- 字符串值当前的长度为11字节
redis> GET message
"hello Redis"

当用户给定的新内容比被替换的内容更长时,SETRANGE命令就会自动扩展被修改的字符串值,从而确保新内容可以顺利写入。 SETRANGE命令除了会根据用户给定的新内容自动扩展字符串值之外,还会根据用户给定的index索引扩展字符串。

当用户给定的index索引超出字符串值的长度时,字符串值末尾直到索引index-1之间的部分将使用空字节进行填充,换句话说,这些字节的所有二进制位都会被设置为0。

APPEND:追加新内容到值的末尾

通过调用APPEND命令,用户可以将给定的内容追加到字符串键已有值的末尾:

APPEND key suffix

APPEND命令在执行追加操作之后,会返回字符串值当前的长度作为命令的返回值。

举个例子,对于以下这个名为description的键来说:

redis> GET description
"Redis"
redis> APPEND description " is a database"
(integer) 19    -- 追加操作执行完毕之后,值的长度
redis> GET description
"Redis is a database" --在执行完追加操作之后的值

如果用户给定的键并不存在,那么APPEND命令会先将键的值初始化为空字符串”“,然后再执行追加操作,最终效果与使用SET命令为键设置值的情况类似:

redis> GET append_msg  -- 键不存在
(nil)
redis> APPEND append_msg "hello"  -- 效果相当于执行SET append_msg "hello"
(integer) 5
redis> GET append_msg
"hello"

使用字符串键存储数字值

每当用户将一个值存储到字符串键里面的时候,Redis都会对这个值进行检测,如果这个值能够被解释为以下两种类型的其中一种,那么Redis就会把这个值当作数字来处理:

  • 第一种类型是能够使用C语言的long long int类型存储的整数,在大多数系统中,这种类型存储的都是64位长度的有符号整数,取值范围介于-9223372036854775808和9223372036854775807之间。
  • 第二种类型是能够使用C语言的long double类型存储的浮点数,在大多数系统中,这种类型存储的都是128位长度的有符号浮点数,取值范围介于3.36210314311209350626e-4932和1.18973149535723176502e+4932L之间。

INCRBY、DECRBY:对整数值执行加法操作和减法操作

INCRBYFLOAT:对数字值执行浮点数加法操作

计数器

计数器也是构建应用程序时必不可少的组件之一,如对于网站的访客数量、用户执行某个操作的次数、某首歌或者某个视频的播放量、论坛帖子的回复数量等,记录这些信息都需要用到计数器。实际上,计数器在互联网中几乎无处不在,因此如何简单、高效地实现计数器一直都是构建应用程序时经常会遇到的一个问题。

计数器实现:这个程序把计数器的值存储在一个字符串键里面,并通过INCRBY命令和DECRBY命令对计数器的值执行加法操作和减法操作,在需要时,用户还可以通过调用GETSET方法来清零计数器并取得清零之前的旧值。

限速器

为了保障系统的安全性和性能,并保证系统的重要资源不被滥用,应用程序常常会对用户的某些行为进行限制,比如:

  • 为了防止网站内容被网络爬虫抓取,网站管理者通常会限制每个IP地址在固定时间段内能够访问的页面数量,比如1min之内最多只能访问30个页面,超过这一限制的用户将被要求进行身份验证,确认本人并非网络爬虫,或者等到限制解除之后再进行访问。
  • 为了防止用户的账号遭到暴力破解,网上银行通常会对访客的密码试错次数进行限制,如果一个访客在尝试登录某个账号的过程中,连续好几次输入了错误的密码,那么这个账号将被冻结,只能等到第二天再尝试登录,有的银行还会向账号持有者的手机发送通知来汇报这一情况。

实现这些限制机制的其中一种方法是使用限速器,它可以限制用户在指定时间段之内能够执行某项操作的次数。 使用字符串键实现的限速器:这个限速器程序会把操作的最大可执行次数存储在一个字符串键里面,然后在用户每次尝试执行被限制的操作之前,使用DECR命令将操作的可执行次数减1,最后通过检查可执行次数的值来判断是否执行该操作。

散列(Hash)

使用多个字符串键存储相关联数据虽然在技术上是可行的,但是在实际应用中并不是最有效的方法,这种存储方法至少存在以下3个问题:

  • 首先,程序每存储一组相关联的数据,就必须在数据库中同时创建多个字符串键,这样的数据越多,数据库包含的键数量也会越多。数量庞大的键会对数据库某些操作的执行速度产生影响,维护这些键也会产生大量的资源消耗。
  • 其次,为了在数据库中标识出相关联的字符串键,程序需要为它们加上相同的前缀。但键名实际上也是一种数据,存储键名也需要耗费内存空间,因此重复出现的键名前缀实际上导致很多内存空间被白白浪费了。此外,带前缀的键名降低了键名的可读性,让人无法一眼看清键的真正用途,比如键名article::10086::author就远不如键名author简洁,键名article::10086::title也不如键名title简洁。
  • 最后,虽然程序在逻辑上会把带有相同前缀的字符串键看作相关联的一组数据,但是在Redis看来,它们只不过是存储在同一个数据库中的不同字符串键而已,因此当程序需要处理一组相关联的数据时,就必须对所有有关的字符串键都执行相同的操作。比如,如果程序想要删除ID为10086的文章,那么它就必须把article::10086::title、article::10086::content等4个字符串键都删掉才行,这给文章的删除操作带来了额外的麻烦,并且还可能会因为漏删或者错删了某个键而出现错误。

为了解决以上问题,我们需要一种能够真正地把相关联的数据打包起来存储的数据结构,而这种数据结构就是本章要介绍的散列(hash)键。

Redis的散列键会将一个键和一个散列在数据库里关联起来,用户可以在散列中为任意多个字段(field)设置值。与字符串键一样,散列的字段和值既可以是文本数据,也可以是二进制数据。

通过使用散列键,用户可以把相关联的多项数据存储到同一个散列里面,以便对这些数据进行管理,或者针对它们执行批量操作。 Redis为散列键提供了一系列操作命令,通过使用这些命令,用户可以:

  • 为散列的字段设置值,或者只在字段不存在的情况下为它设置值。
  • 从散列里面获取给定字段的值。
  • 对存储着数字值的字段执行加法操作或者减法操作。
  • 检查给定字段是否存在于散列当中。
  • 从散列中删除指定字段。
  • 查看散列包含的字段数量。
  • 一次为散列的多个字段设置值,或者一次从散列中获取多个字段的值。
  • 获取散列包含的所有字段、所有值或者所有字段和值。

HSET:为字段设置值

用户可以通过执行HSET命令为散列中的指定字段设置值:

HSET hash field value

根据给定的字段是否已经存在于散列中,HSET命令的行为也会有所不同:

  • 如果给定字段并不存在于散列当中,那么这次设置就是一次创建操作,命令将在散列里面关联起给定的字段和值,然后返回1。
  • 如果给定的字段原本已经存在于散列里面,那么这次设置就是一次更新操作,命令将使用用户给定的新值去覆盖字段原有的旧值,然后返回0。

HSETNX:只在字段不存在的情况下为它设置值

HSETNX命令只会在指定字段不存在的情况下执行设置操作:

HSETNX hash field value

HSETNX命令在字段不存在并且成功为它设置值时返回1,在字段已经存在并导致设置操作未能成功执行时返回0。

HGET:获取字段的值

HGET命令可以根据用户给定的字段,从散列中获取该字段的值:

HGET hash field

执行以下命令可以从article::10086散列中获取author字段的值:

redis> HGET article::10086 author
"peter"

如果用户给定的字段并不存在于散列当中,那么HGET命令将返回一个空值。 举个例子,在以下代码中,我们尝试从account::54321散列里面获取location字段的值,但由于location字段并不存在于account::54321散列当中,所以HGET命令将返回一个空值:

redis> HGET account::54321 location
(nil)

HINCRBY:对字段存储的整数值执行加法或减法操作

与字符串键的INCRBY命令一样,如果散列的字段里面存储着能够被Redis解释为整数的数字,那么用户就可以使用HINCRBY命令为该字段的值加上指定的整数增量:

HINCRBY hash field increment

HINCRBY命令在成功执行加法操作之后将返回字段当前的值作为命令的结果。

比如,对article::10086散列,我们可以通过执行以下命令为view_count字段的值加上1:

redis> HINCRBY article::10086 view_count 1
(integer) 101

因为Redis只为散列提供了用于执行加法操作的HINCRBY命令,但是没有为散列提供相应的用于执行减法操作的命令,所以如果用户需要对字段存储的整数值执行减法操作,就需要将一个负数增量传给HINCRBY命令,从而达到对值执行减法计算的目的。

以下代码展示了如何使用HINCRBY命令去对view_count字段存储的整数值执行减法计算:

redis> HGET article::10086 view_count           -- 文章现在的浏览次数为131次
"131"
redis> HINCRBY article::10086 view_count -10    -- 将文章的浏览次数减少10次
"121"

HINCRBYFLOAT:对字段存储的数字值执行浮点数加法或减法操作

HINCRBYFLOAT命令的作用和HINCRBY命令的作用类似,它们之间的主要区别在于HINCRBYFLOAT命令不仅可以使用整数作为增量,还可以使用浮点数作为增量:

HINCRBYFLOAT hash field increment

HINCRBYFLOAT命令在成功执行加法操作之后,将返回给定字段的当前值作为结果。

举个例子,通过执行以下HINCRBYFLOAT命令,我们可以将geo::peter散列longitude字段的值从原来的100.0099647修改为113.2099647:

redis> HGET geo::peter longitude
"100.0099647"
redis> HINCRBYFLOAT geo::peter longitude 13.2  -- 将字段的值加上13.2
"113.2099647"

使用散列键重新实现计数器

在学习了HINCRBY命令之后,我们同样可以通过类似的原理来构建一个使用散列实现的计数器程序

  • 它允许用户将多个相关联的计数器存储到同一个散列键中实行集中管理,而不必像字符串计数器那样,为每个计数器单独设置一个字符串键。
  • 与此同时,通过对散列中的不同字段执行HINCRBY命令,程序可以对指定的计数器执行加法操作和减法操作,而不会影响到存储在同一散列中的其他计数器。

HEXISTS:检查字段是否存在

HEXISTS命令可用于检查用户给定的字段是否存在于散列当中:如果散列包含了给定的字段,那么命令返回1,否则命令返回0。

HEXISTS hash field

以下代码就展示了如何使用HEXISTS命令检查article::10086散列是否包含某些字段:

redis> HEXISTS article::10086 author
(integer) 1    -- 包含该字段
redis> HEXISTS article::10086 last_updated_at
(integer) 0    -- 不包含该字段

如果用户给定的散列并不存在,那么HEXISTS命令对于这个散列所有字段的检查结果都是不存在:

HDEL:删除字段

HDEL命令用于删除散列中的指定字段及其相关联的值:当给定字段存在于散列当中并且被成功删除时,命令返回1;如果给定字段并不存在于散列当中,或者给定的散列并不存在,那么命令将返回0表示删除失败。

HDEL hash field

举个例子,对于article::10086散列,我们可以使用以下命令删除散列的author字段和created_at字段,以及与这些字段相关联的值:

redis> HDEL article::10086 author
(integer) 1
redis> HDEL article::10086 created_at
(integer) 1

HLEN:获取散列包含的字段数量

用户可以通过使用HLEN命令获取给定散列包含的字段数量。如果用户给定的散列并不存在,那么HLEN命令将返回0作为结果:

HLEN hash

对于article::10086散列来说,我们可以通过执行以下命令来获取article::10086散列包含的字段数量

redis> HLEN article::10086
(integer) 4    -- 这个散列包含4个字段

如果用户给定的散列并不存在,那么HLEN命令将返回0作为结果:

redis> HLEN not-exists-hash
(integer) 0

HMSET:一次为多个字段设置值

用户可以使用HMSET命令一次为散列中的多个字段设置值:HMSET命令在设置成功时返回OK。如果用户给定的字段已经存在于散列当中,那么HMSET命令将使用用户给定的新值去覆盖字段已有的旧值。

HMSET hash field value [field value ...]

比如,HMSET命令可以更方便地构建散列

redis> HMSET article::10086 title "greeting" content "hello world" author "peter" created_at "1442744762.631885" 
OK

此外,因为客户端在执行这条HMSET命令时只需要与Redis服务器进行一次通信,而上面的4条HSET命令则需要客户端与Redis服务器进行4次通信,所以前者的执行速度要比后者快得多。

如果用户给定的字段已经存在于散列当中,那么HMSET命令将使用用户给定的新值去覆盖字段已有的旧值。

比如对于title和content这两个已经存在于article::10086散列的字段来说:

redis> HGET article::10086 title
"greeting"
redis> HGET article::10086 content
"hello world"
redis> HMSET article::10086 title "Redis Tutorial" content "Redis is a data structure store, ..."
OK
redis> HGET article::10086 title
"Redis Tutorial"
redis> HGET article::10086 content
"Redis is a data structure store, ..."

HMGET:一次获取多个字段的值

通过使用HMGET命令,用户可以一次从散列中获取多个字段的值:HMGET命令将按照用户给定字段的顺序依次返回与之对应的值。 如果用户向HMGET命令提供的字段或者散列不存在,那么HMGET命令将返回空值作为结果。

HMGET hash field [field ...]

例如:对于article::10086散列来说,我们可以使用以下命令来获取它的author字段和created_at字段的值:

redis> HMGET article::10086 author created_at last_updated_at
1) "peter"                -- author字段的值
2) "1442744762.631885"    -- created_at字段的值
3) (nil)    -- last_updated_at字段不存在于article::10086散列

HKEYS、HVALS、HGETALL:获取所有字段、所有值、所有字段和值

Redis为散列提供了HKEYS、HVALS和HGETALL这3个命令,可以分别用于获取散列包含的所有字段、所有值以及所有字段和值:

HKEYS hash

HVALS hash

HGETALL hash

存储图数据

在构建地图应用、设计电路图、进行任务调度、分析网络流量等多种任务中,都需要对图(graph)数据结构实施建模,并存储相关的图数据。对于不少数据库来说,想要高效、直观地存储图数据并不是一件容易的事情,但是Redis却能够以多种不同的方式表示图数据结构,其中一种方式就是使用散列。

例如,假设我们想要存储带权重有向图,那么可以创建一个散列键,这个散列键会以start_vertex->end_vertex的形式将各个顶点之间的边存储到散列的字段中,并将字段的值设置成边的权重。通过这种方法,我们可以将图的相关数据全部存储到散列中。

这个图数据存储程序的核心概念就是把边(edge)的起点和终点组合成一个字段名,并把边的权重(weight)用作字段的值,然后使用HSET命令或者HMSET命令把它们存储到散列中。比如,如果用户输入的边起点为”a”,终点为”b”,权重为”30”,那么程序将执行命令HSET hash”a->b”30,把”a”至”b”的这条边及其权重30存储到散列中。

在此之后,程序就可以使用HDEL命令删除图的某条边,使用HGET命令或者HMGET命令获取边的权重,使用HEXISTS命令检查边是否存在,使用HKEYS命令和HGETALL命令获取图的所有边以及权重。

列表

Redis的列表(list)是一种线性的有序结构,可以按照元素被推入列表中的顺序来存储元素,这些元素既可以是文字数据,又可以是二进制数据,并且列表中的元素可以重复出现。

作为例子,一个包含多个字符串的列表,这个列表按照从左到右的方式,依次存储了”one”、”two”、”three”、”four”这4个元素。

Redis为列表提供了丰富的操作命令,通过这些命令,用户可以:

  • 将新元素推入列表的左端或者右端。
  • 移除位于列表最左端或者最右端的元素。
  • 移除列表最右端的元素,然后把被移除的元素推入另一个列表的左端。
  • 获取列表包含的元素数量。
  • 获取列表在指定索引上的单个元素,或者获取列表在指定索引范围内的多个元素。
  • 为列表的指定索引设置新元素,或者把新元素添加到某个指定元素的前面或者后面。
  • 对列表进行修剪,只保留指定索引范围内的元素。
  • 从列表中移除指定元素。
  • 执行能够阻塞客户端的推入和移除操作。

LPUSH:将元素推入列表左端

用户可以通过LPUSH命令,将一个或多个元素推入给定列表的左端:在推入操作执行完毕之后,LPUSH命令会返回列表当前包含的元素数量作为返回值。 LPUSH命令允许用户一次将多个元素推入列表左端:如果用户在执行LPUSH命令时给定了多个元素,那么LPUSH命令将按照元素给定的顺序,从左到右依次将所有给定元素推入列表左端。

LPUSH list item [item item ...]

例如,以下代码就展示了如何通过LPUSH命令将”buy some milk”、”watch tv”等元素依次推入todo列表的左端:

redis> LPUSH todo "buy some milk"
(integer) 1    -- 列表现在包含1个元素
redis> LPUSH todo "watch tv"
(integer) 2    -- 列表现在包含2个元素

以上3个LPUSH命令的执行过程:

  • 在执行操作之前,todo列表为空,即不存在于数据库中。
  • 执行第1个LPUSH命令,将元素”buy some milk”推入列表左端。
  • 执行完第1个LPUSH命令的列表现在包含一个元素。
  • 执行第2个LPUSH命令,将元素”watch tv”推入列表左端。
  • 执行完第2个LPUSH命令的列表现在包含两个元素。

一次推入多个元素举个例子,如果用户执行以下命令:

redis> LPUSH another-todo "buy some milk" "watch tv" 
(integer) 2

那么LPUSH命令将顺序,将3个给定元素依次推入another-todo列表的左端。

RPUSH:将元素推入列表右端

RPUSH命令和LPUSH命令类似,这两个命令执行的都是元素推入操作,唯一区别就在于LPUSH命令会将元素推入列表左端,而RPUSH命令会将元素推入列表右端: 在推入操作执行完毕之后,RPUSH命令会返回列表当前包含的元素数量作为返回值。

RPUSH list item [item item ...]

LPUSHX、RPUSHX:只对已存在的列表执行推入操作

当用户调用LPUSH命令或RPUSH命令尝试将元素推入列表的时候,如果给定的列表并不存在,那么命令将自动创建一个空列表,并将元素推入刚刚创建的列表中。

LPOP:弹出列表最左端的元素

RPOPLPUSH:将右端弹出的元素推入左端

先进先出队列

LRANGE:获取指定索引范围上的元素

LSET:为指定索引设置新元素

LINSERT:将元素插入列表

LTRIM:修剪列表

BLPOP:阻塞式左端弹出操作

BRPOP:阻塞式右端弹出操作

BRPOPLPUSH:阻塞式弹出并推入操作

集合

Redis的集合(set)键允许用户将任意多个各不相同的元素存储到集合中,这些元素既可以是文本数据,也可以是二进制数据。 虽然列表键也允许我们存储多个元素,但集合与列表有以下两个明显的区别:

  • 列表可以存储重复元素,而集合只会存储非重复元素,尝试将一个已存在的元素添加到集合将被忽略。
  • 列表以有序方式存储元素,而集合则以无序方式存储元素。

这两个区别带来的差异主要跟命令的复杂度有关:

  • 在执行像LINSERT和LREM这样的列表命令时,即使命令只针对单个列表元素,程序有时也不得不遍历整个列表以确定指定的元素是否存在,因此这些命令的复杂度都为O(N)。
  • 对于集合来说,因为所有针对单个元素的集合命令都不需要遍历整个集合,所以复杂度都为O(1)。

因此当我们需要存储多个元素时,就可以考虑这些元素是否可以以无序的方式存储,并且是否不会出现重复,如果是,那么就可以使用集合来存储这些元素,从而有效地利用集合操作的效率优势。

Redis为集合键提供了一系列操作命令,通过使用这些命令,用户可以:

  • 将新元素添加到集合中,或者从集合中移除已有的元素。
  • 将指定的元素从一个集合移动到另一个集合。
  • 获取集合包含的所有元素。
  • 获取集合包含的元素数量。
  • 检查给定元素是否存在于集合中。
  • 从集合中随机地获取指定数量的元素。
  • 对多个集合执行交集、并集、差集计算。

有序集合

Redis的有序集合(sorted set)同时具有“有序”和“集合”两种性质,这种数据结构中的每个元素都由一个成员和一个与成员相关联的分值组成,其中成员以字符串方式存储,而分值则以64位双精度浮点数格式存储。

与集合一样,有序集合中的每个成员都是独一无二的,同一个有序集合中不会出现重复的成员。与此同时,有序集合的成员将按照它们各自的分值大小进行排序:比如,分值为3.14的成员将小于分值为10.24的成员,而分值为999的成员也会小于分值为10086的成员。有序集合的分值除了可以是数字之外,还可以是字符串”+inf”或者”-inf”,这两个特殊值分别用于表示无穷大和无穷小。

需要注意的是,虽然同一个有序集合不能存储相同的成员,但不同成员的分值却可以是相同的。当两个或多个成员拥有相同的分值时,Redis将按照这些成员在字典序中的大小对其进行排列:举个例子,如果成员”apple”和成员”zero”都拥有相同的分值100,那么Redis将认为成员”apple”小于成员”zero”,这是因为在字典序中,字母”a”开头的单词要小于字母”z”开头的单词。

有序集合是Redis提供的所有数据结构中最为灵活的一种,它可以以多种不同的方式获取数据,比如根据成员获取分值、根据分值获取成员、根据成员的排名获取成员、根据指定的分值范围获取多个成员等。

Search

    微信好友

    博士的沙漏

    Table of Contents