《Redis入门指南(第2版)》一3.3 散列类型

3.3 散列类型

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

7http://twitter.github.com/bootstrap。

接着小白发问:

接下来我想要做的功能是博客的文章列表页,我设想在列表页中每个文章只显示标题部分,可是使用您刚才介绍的方法,若想取得文章的标题,必须把整个文章数据字符串取出来反序列化,而其中占用空间最大的文章内容部分却是不需要的,这样难道不会在传输和处理时造成资源浪费吗?

老师有些惊喜地看着小白答道:“很对!”同时以一个夸张的幅度点了下头,接着说:

这正是我接下来准备讲的。不仅取数据时会有资源浪费,在修改数据时也会有这个问题,比如当你只想更改文章的标题时也不得不把整个文章数据字符串更新一遍。

没等小白再问,老师就又继续说道:

前面我说过Redis的强大特性之一就是提供了多种实用的数据类型,其中的散列类型可以非常好地解决这个问题。

3.3.1 介绍

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

提示
除了散列类型,Redis 的其他数据类型同样不支持数据类型嵌套。比如集合类型的每个元素都只能是字符串,不能是另一个集合或散列表等。
散列类型适合存储对象:使用对象类别和ID构成键名,使用字段表示对象的属性,而字段值则存储属性值。例如要存储ID为2的汽车对象,可以分别使用名为color、name和price的3个字段来存储该辆汽车的颜色、名称和价格。存储结构如图3-5所示。

回想在关系数据库中如果要存储汽车对象,存储结构如表3-2所示。

数据是以二维表的形式存储的,这就要求所有的记录都拥有同样的属性,无法单独为某条记录增减属性。如果想为ID为1的汽车增加生产日期属性,就需要把数据表更改为如表3-3所示的结构。

对于ID为2和3的两条记录而言date字段是冗余的。可想而知当不同的记录需要不同的属性时,表的字段数量会越来越多以至于难以维护。而且当使用ORM8将关系数据库中的对象实体映射成程序中的实体时,修改表的结构往往意味着要中断服务(重启网站程序)。为了防止这些问题,在关系数据库中存储这种半结构化数据还需要额外的表才行。

8即Object-Relational Mapping(对象关系映射)。
而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"9。

9并不是所有命令都是如此,比如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的返回结果封装成编程语言中的对象,处理起来就非常方便了。例如,在Node.js中:

redis.hgetall("car", function (error, car) {
  // hgetall方法的返回的值被封装成了JavaScript的对象
  console.log(car.price);
  console.log(car.name);
});

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

HSETNX 10命令与HSET命令类似,区别在于如果字段已经存在,HSETNX`命令将不执行任何操作。其实现可以表示为如下伪代码:

10HSETNX中的“NX”表示“if Note Xists”(如果不存在)。`

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所示。

使用这种方法的好处在于无论获取还是修改文章数据,都可以只对某一属性进行操作,十分方便。而本章介绍的散列类型则更适合此场景,使用散列类型的存储结构如图3-7所示。

从图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了。

现在发布文章可以修改成如下代码:

$postID =INCR posts:count

# 判断用户输入的slug是否可用,如果可用则记录
$isSlugAvailable =HSETNX slug.to.id, $slug, $postID
if $isSlugAvailable is 0
  # slug已经用过了,需要提示用户更换slug,
  # 这里为了演示方便直接退出。
  exit

HMSET post:$postID, title, $title, content, $content, slug, $slug,...

这段代码使用了HSETNX命令原子地实现了HEXISTS和HSET两个命令以避免竞态条件。当用户访问文章时,我们从网址中得到文章的缩略名,并查询slug.to.id键来获取文章ID:

$postID =HGET slug.to.id, $slug
if not $postID
  print 文章不存在
  exit

$post =HGETALL post:$postID
print 文章标题:$post.title

需要注意的是如果要修改文章的缩略名一定不能忘了修改slug.to.id键对应的字段。如要修改ID为42的文章的缩略名为newSlug变量的值:

# 判断新的slug是否可用,如果可用则记录
$isSlugAvailable =HSETNX slug.to.id, $newSlug, 42
if $isSlugAvailable is 0
  exit

# 获得旧的缩略名
$oldSlug =HGET post:42, slug
# 设置新的缩略名
HSET post:42, slug, $newSlug
# 删除旧的缩略名
HDEL slug.to.id, $oldSlug

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
时间: 2024-10-30 01:03:53

《Redis入门指南(第2版)》一3.3 散列类型的相关文章

《Redis入门指南(第2版)》一第3章 入门

第3章 入门 Redis入门指南(第2版) 学会如何安装和运行Redis,并了解Redis的基础知识后,本章将详细介绍Redis的5种主要数据类型及相应的命令,带领读者真正进入Redis的世界.在学习的时候,手边打开一个redis-cli程序来跟着一起输入命令将会极大地提高学习效率.尽管在目前多数公司和团队的Redis的应用是以缓存和队列为主. 在之后的章节中你会遇到两个学习伙伴:小白和宋老师.小白是一个标准的极客,最近刚开始他的Redis学习之旅,而他大学时的计算机老师宋老师恰好对Redis颇

《Redis入门指南(第2版)》一第2章 准备

第2章 准备 Redis入门指南(第2版)"纸上得来终觉浅,绝知此事要躬行." --陆游<冬夜读书示子聿> 学习Redis最好的办法就是动手尝试它.在介绍Redis最核心的内容之前,本章先来介绍一下如何安装和运行Redis,以及Redis的基础知识,使读者可以在之后的章节中一边学习一边实践.

《Redis入门指南(第2版)》一导读

前 言 Redis入门指南(第2版)Redis如今已经成为Web开发社区中最火热的内存数据库之一,而它的诞生距现在不过才4年.随着Web 2.0的蓬勃发展,网站数据快速增长,对高性能读写的需求也越来越多,再加上半结构化的数据比重逐渐变大,人们对早已被铺天盖地地运用着的关系数据库能否适应现今的存储需求产生了疑问.而Redis的迅猛发展,为这个领域注入了全新的思维. Redis凭借其全面的功能得到越来越多的公司的青睐,从初创企业到新浪微博这样拥有着几百台Redis服务器的大公司,都能看到Redis的

《Redis入门指南(第2版)》一3.5 集合类型

3.5 集合类型 博客首页,文章页面,评论页面--眼看着博客逐渐成型,小白的心情也是越来越好.时间已经到了深夜,小白却还陶醉于编码之中.不过一个他无法解决的问题最终还是让他不得不提早睡觉去:小白不知道该怎么在Redis中存储文章标签(tag).他想过使用散列类型或列表类型存储,虽然都能实现,但是总觉得颇有不妥,再加上之前几天领略了Redis的强大功能后,小白相信一定有一种合适的数据类型能满足他的需求.于是小白给宋老师发了封询问邮件后就睡觉去了. 转天一早就收到了宋老师的回复: 你很善于思考嘛!你

《Redis入门指南(第2版)》一3.1 热身

3.1 热身 在介绍Redis的数据类型之前,我们先来了解几个比较基础的命令作为热身,赶快打开redis-cli,跟着样例亲自输入命令来体验一下吧! 1.获得符合规则的键名列表 KEYS pattern pattern支持glob风格通配符格式,具体规则如表3-1所示. 现在Redis中空空如也(如果你从第2章开始就一直跟着本书的进度输入命令,此时数据库中可能还会有个foo键),为了演示KEYS命令,首先我们得给Redis加点料.使用SET命令(会在3.2节介绍)建立一个名为bar的键: red

《Redis入门指南(第2版)》一3.4 列表类型

3.4 列表类型 正当小白踌躇满志地写着文章列表页的代码时,一个很重要的问题阻碍了他的开发,于是他请来了宋老师为他讲解. 原来小白是使用如下流程获得文章列表的: 读取posts:count键获得博客中最大的文章ID:根据这个ID来计算当前列表页面中需要展示的文章ID列表(小白规定博客每页只显示10篇文章,按照ID的倒序排列),如第n页的文章ID范围是从最大的文章ID - (n - 1) 10"到"max(最大的文章ID - n 10 + 1, 1)":对每个ID使用HMGET

《Redis入门指南》一4.3 排序

4.3 排序 Redis入门指南 午后,宋老师正在批改学生们提交的程序,再过几天就会迎来第一次计算机全市联考.他在每个学生的程序代码末尾都用注释详细地做了批注--严谨的治学态度让他备受学生们的爱戴. 一个电话打来."小白的?"宋老师拿出手机,"博客最近怎么样了?"未及小白开口,他就抢先问道. 特别好!现在平均每天都有50多人访问我的博客.不过昨天我收到一个访客的邮件,他向我反映了一个问题:查看一个标签下的文章列表时文章不是按照时间顺序排列的,找起来很麻烦.我看了一下

《Redis入门指南》一5.1 PHP与Redis

5.1 PHP与Redis Redis入门指南 Redis官方推荐的PHP客户端是Predis1和phpredis2.前者是完全使用PHP代码实现的原生客户端,而后者则是使用C语言编写的PHP扩展.在功能上两者区别并不大,就性能而言后者会更胜一筹.考虑到很多主机并未提供安装PHP扩展的权限,本节会以Predis为示例介绍如何在PHP中使用Redis. 虽然Predis的性能逊于phpredis,但是除非执行大量Redis命令,否则很难区分二者的性能.而且实际应用中执行Redis命令的开销更多在网

《Redis入门指南》一5.3 Python与Redis

5.3 Python与Redis Redis入门指南 Redis官方推荐的Python客户端是redis-py1. 5.3.1 安装 推荐使用pip install redis安装最新版本的redis-py,也可以使用easy_install:easy_install redis. 5.3.2 使用方法 首先需要引入redis-py: import redis 下面的代码将创建一个默认连接到地址127.0.0.1,端口6379的Redis连接: r = redis.StrictRedis() 也