散列简介

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

通过使用散列键,用户可以把相关联的多项数据存储到同一个散列里面,以便对这些数据进行管理,或者针对它们执行批量操作。比如图 3-2 就展示了一个使用散列存储文章数据的例子,在这个例子中,散列的键为article::10086,而这个键对应的散列则包含了 4 个字段,其中:

"title" 字段存储文章的标题 "greeting"。
"content" 字段存储文章的内容 "hello world"。
"author" 字段存储文章的作者名字 "peter"。
"create_at" 字段存储文章的创建时间 "1442744762.631885"。



redis能按照字段查询吗_redis能按照字段查询吗

使用散列存储文章数据

与之前使用字符串键存储文章数据的做法相比,使用散列存储文章数据只需要在数据库里面创建一个键,并且因为散列的字段名不需要添加任何前缀,所以它们可以直接反映字段值存储的是什么数据。

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

为散列的字段设置值,或者只在字段不存在的情况下为它设置值。

从散列里面获取给定字段的值。

对存储着数字值的字段执行加法操作或者减法操作。

检查给定字段是否存在于散列当中。

从散列中删除指定字段。

查看散列包含的字段数量。

一次为散列的多个字段设置值,或者一次从散列中获取多个字段的值。

获取散列包含的所有字段、所有值或者所有字段和值。

本章接下来将对以上提到的散列操作进行介绍,说明如何使用这些操作去构建各种有用的应用程序,并在最后详细地说明散列键与字符串键之间的区别。

HSET:为字段设置值

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

HSET hash field value

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

如果给定字段并不存在于散列当中,那么这次设置就是一次创建操作,命令将在散列里面关联起给定的字段和值,然后返回 1。

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

举个例子,通过执行以下 HSET 命令,我们可以创建出一个包含了 4 个字段的散列,这 4 个字段分别存储了文章的标题、内容、作者以及创建日期:

redis> HSET article::10086 title "greeting"
(integer) 1
redis> HSET article::10086 content "hello world"
(integer) 1
redis> HSET article::10086 author "peter"
(integer) 1
redis> HSET article::10086 created_at "1442744762.631885"
(integer) 1

图 3-3 展示了以上 HSET 命令对散列 article::10086 进行设置的整个过程。



redis能按照字段查询吗_redis能按照字段查询吗_02

HSET前

redis能按照字段查询吗_redis_03

HSET后

提示:散列包含的字段就像数据库包含的键一样,在实际中都是以无序方式进行排列的,不过本书为了展示方便,一般都会把新字段添加到散列的末尾,排在所有已有字段的后面。

使用新值覆盖旧值

正如之前所说,如果用户在调用 HSET 命令时给定的字段已经存在于散列当中,那么 HSET 命令将使用用户给定的新值去覆盖字段已有的旧值,并返回 0 表示这是一次更新操作。

比如,以下代码就展示了如何使用 HSET 命令去更新 article::10086 散列的 title 字段以及 content 字段:

redis> HSET article::10086 title "Redis Tutorial" 
(integer) 0
redis> HSET article::10086 content "Redis is a data structure store, ..."
(integer) 0

图 3-4 展示了被更新之后的 article::10086 散列。



redis能按照字段查询吗_redis能按照字段查询吗_04

更新后的散列

其他信息

复杂度:O (1)。

版本要求:HSET 命令从 Redis 2.0.0 版本开始可用。

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

HSETNX 命令的作用和 HSET 命令的作用非常相似,它们之间的区别在于,HSETNX 命令只会在指定字段不存在的情况下执行设置操作:

HSETNX hash field value

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

举个例子,对于图 3-5 所示的 article::10086 散列来说,执行以下 HSETNX 命令将不会对散列产生任何影响,因为 HSETNX 命令想要设置的 title 字段已经存在:

redis> HSETNX article::10086 title "Redis Performance Test"
(integer) 0   -- 设置失败



redis能按照字段查询吗_redis能按照字段查询吗_05

HSETNX前

相反,如果我们使用 HSETNX 命令去对尚未存在的 view_count 字段进行设置,那么这个命令将会顺利执行,并将 view_count 字段的值设置为 100:

redis> HSETNX article::10086 view_count 100
(integer) 1   -- 设置成功

图 3-6 展示了 HSETNX 命令成功执行之后的 article::10086 散列。



redis能按照字段查询吗_用同一uuid作为两个字段的值_06

HSETNX命令后

其他信息

复杂度:O (1)。

版本要求:HSETNX 命令从 Redis 2.0.0 版本开始可用。

HGET:获取字段的值

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

HGET hash field

例如,对于图 3-7 所示的两个散列键来说,执行以下命令可以从 article::10086 散列中获取 author 字段的值:

redis> HGET article::10086 author
"peter"

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

redis> HGET article::10086 created_at
"1442744762.631885"



redis能按照字段查询吗_redis能按照字段查询吗_07

两个散列

再例如,如果我们想要从 account::54321 散列中获取 email 字段的值,那么可以执行以下命令:

redis> HGET account::54321 email
"peter1984@spam_mail.com"

处理不存在的字段或者不存在的散列

如果用户给定的字段并不存在于散列当中,那么 HGET 命令将返回一个空值。

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

redis> HGET account::54321 location
(nil)

尝试从一个不存在的散列里面获取一个不存在的字段值,得到的结果也是一样的:

redis> HGET not-exists-hash not-exists-field
(nil)

其他信息

复杂度:O (1)。

版本要求:HGET 命令从 Redis 2.0.0 版本开始可用。

示例:实现短网址生成程序

为了给用户提供更多发言空间,并记录用户在网站上的链接点击行为,大部分社交网站都会将用户输入的网址转换为相应的短网址。比如,如果我们在新浪微博中发言时输入网址 http://redisdoc.com/geo/index.html,那么微博将把这个网址转换为相应的短网址 http://t.cn/RqRRZ8n,当用户访问这个短网址时,微博在后台就会对这次点击进行一些数据统计,然后再引导用户的浏览器跳转到 http://redisdoc.com/geo/index.html上面。

创建短网址本质上就是要创建出短网址 ID 与目标网址之间的映射,并在用户访问短网址时,根据短网址的 ID 从映射记录中找出与之相对应的目标网址。比如在前面的例子中,微博的短网址程序就将短网址 http://t.cn/RqRRZ8n 中的 ID 值 RqRRZ8n 映射到了 http://redisdoc.com/geo/index.html 这个网址上面,当用户访问短网址 http://t.cn/RqRRZ8n 时,程序就会根据这个短网址的 ID 值 RqRRZ8n 找出与之对应的目标网址 http://redisdoc.com/geo/index.html,并将用户引导至目标网址上面去。

作为示例,图 3-8 展示了几个微博短网址 ID 与目标网址之间的映射关系。



redis能按照字段查询吗_redis_08

映射关系

因为 Redis 的散列非常适合用来存储短网址 ID 与目标网址之间的映射,所以我们可以基于 Redis 的散列实现一个短网址程序,代码清单 3-1 展示了一个这样的例子。

代码清单 3-1 使用散列实现的短网址程序:/hash/shorty_url.py

from base36 import base10_to_base36

ID_COUNTER = "ShortyUrl::id_counter"
URL_HASH = "ShortyUrl::url_hash"

class ShortyUrl:
  def __init__(self, client):
    self.client = client

  def shorten(self, target_url):
    """
    为目标网址创建并存储相应的短网址ID
    """
    # 为目标网址创建新的数字ID
    new_id = self.client.incr(ID_COUNTER)
    # 通过将十进制数字转换为三十六进制数字来创建短网址ID,
    # 比如,十进制数字10086将被转换为三十六进制数字7S6
    short_id = base10_to_base36(new_id)
    # 把短网址ID用作字段,目标网址用作值,将它们之间的映射关系存储到散列里面
    self.client.hset(URL_HASH, short_id, target_url)
    return short_id

  def restore(self, short_id):
    """
    根据给定的短网址ID,返回与之对应的目标网址
    """
    return self.client.hget(URL_HASH, short_id)

ShortyUrl 类的 shorten() 方法负责为输入的网址生成短网址 ID,它的工作包括以下 4 个步骤:

1)为每个给定的网址创建一个十进制数字 ID。

2)将十进制数字ID转换为三十六进制,并将这个三十六进制数字用作给定网址的短网址 ID,这种方法在数字 ID 长度较大时可以有效地缩短数字 ID 的长度。代码清单 3-2 展示了将数字从十进制转换成三十六进制的 base10_to_base36 函数的具体实现。

3)将短网址 ID 和目标网址之间的映射关系存储到散列中。

4)向调用者返回刚刚生成的短网址 ID。

代码清单 3-2 将十进制数字转换成三十六进制数字的程序:/hash/base36.py

def base10_to_base36(number):
  alphabets = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  result = ""

  while number != 0 :
    number, i = divmod(number, 36)
    result = (alphabets[i] + result)
return result or alphabets[0]

restore() 方法要做的事情和 shorten() 方法正好相反,它会从存储着映射关系的散列里面取出与给定短网址ID相对应的目标网址,然后将其返回给调用者。

以下代码简单地展示了使用 ShortyUrl 程序创建短网址 ID 的方法,以及根据短网址 ID 获取目标网址的方法:

>>> from redis import Redis
>>> from shorty_url import ShortyUrl
>>> client = Redis(decode_responses=True)
>>> shorty_url = ShortyUrl(client)
>>> shorty_url.shorten("RedisGuide.com")    # 创建短网址 ID
'1'
>>> shorty_url.shorten("RedisBook.com")
'2'
>>> shorty_url.shorten("RedisDoc.com")
'3'
>>> shorty_url.restore("1")          # 根据短网址 ID 查找目标网址
'RedisGuide.com'
>>> shorty_url.restore("2")
'RedisBook.com'

图 3-9 展示了上面这段代码在数据库中创建的散列结构。



redis能按照字段查询吗_redis能按照字段查询吗_09

散列结构

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

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

HINCRBY hash field increment

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

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

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

也可以通过执行以下命令,为 view_count 字段的值加上 30:

redis> HINCRBY article::10086 view_count 30
(integer) 131



redis能按照字段查询吗_字段_10

存储文章数量的散列

本文摘选自《Redis 使用手册》



redis能按照字段查询吗_Redis_11

Redis使用手册