首页

第9章 Redis的高级数据结构

关灯 护眼    字体:

上一章 目录 下一章




Redis有着丰富的数据结构,一些功能天然就适合使用Redis来开发。本章将介绍Redis中几个比较高级的数据结构和应用。



9.1  哈希表的功能和应用


哈希表(Hash  Table)是一种数据结构,它实现了“键-值”(Key-Value)的映射。根据Key就能快速找到  Value。并且,无论有多少个键值对,查询时间始终不变。Python  的字典就是基于哈希表实现的。

在Redis中也有一个数据结构叫作哈希表。

在Redis中,使用哈希表可以保存大量数据,且无论有多少数据,查询时间始终保持不变。Redis的一个哈希表里面可以储存232  ─1(约等于43亿)个键值对。



9.1.1  实例31:使用Redis记录用户在线状态


现在,一些论坛网站能够显示用户当前是在线状态还是离线状态。那这个功能是怎么实现的呢?其中一种实现方法就是基于Redis来实现。

实例描述

分别使用字符串和哈希表记录用户的在线信息,并比较在这个场景下哈希表相对于字符串有什么优势。

1.使用字符串记录用户的在线状态

程序的逻辑非常简单,包括以下几个步骤:

(1)用户登录时,在Redis中添加一个字符串,Key为用户账号,Value为1。

(2)用户退出网站时,从Redis中删除账号名对应的Key。

(3)查询时,程序尝试从Redis中获取用户账号对应的字符串:如果值为1,则表示“在线”;如果值为None,则表示“不在线”。

完整的查询代码如下:

代码9-1  使用Redis字符串记录用户在线信息

其中,主要代码说明如下。

●  第3行代码:连接本地的Redis。

●  第12行代码:使用用户帐号作为Key,在Redis中设置字符串。

●  第21行代码:从Redis中删除Key为用户账号的字符串。

●  第30行代码:从Redis中获取Key为用户帐号的字符串的值。如果这个字符串存在,则返回里面的值(在第12行设置的1);如果Redis没有这个Key,则返回None。

●  第31~33行代码:根据返回的值进行判断。如果返回1,则说明用户现在在线;否则说明用户现在不在线。

整个逻辑过程非常简单而直观。功能也正常,看起来没有什么问题。

2.使用字符串保存在线状态的弊端

现在有10个账号同时在线,当对  Redis  执行列出所有  Key  的操作以后,看到的结果如图9-1所示。

如果有1000个用户同时在线,则Redis列出所有Key后的结果如图9-2所示。

图9-1  10个用户同时在线时的Redis  Key

图9-2  1000个用户同时在线的Redis  Key

有多少个用户在线,就有多少个Key。

现在,网站又加入了一个积分机制。每个用户都有一个积分数据,由于这个数据需要经常查询和修改,因此也使用Redis来保存。显然,如果使用用户账号作为Key,积分作为Value,现在Redis看起来也没有什么问题。

那问题来了,在线信息使用用户账号作为  Key,积分信息也使用账号作为  Key,这不就冲突了吗?

于是有人给不同的  Key  加上了后缀。例如,记录用户是否在线,使用的  Key  为“账号:online”。如果用户账号为10032,那他的在线状态Key就是“10032:online”。记录用户积分的Key为“账号:score”,例如用户10032对应的积分Key为“10032:score”。

提示:

在Redis中,Key中的冒号就是普通的字符,用来分割前缀和后缀,没有什么特殊意义。写成“10032_online”或者“10032-score”效果完全一样。

这样一来,假如有一万个用户同时在线,可能会在Redis中出现2万个Key,或者更多。

3.使用哈希表记录用户在线状态

使用哈希表来记录用户在线状态,只需要1个Key。若要记录用户的积分信息,则再加一个Key。原来用字符串时需要2万个Key实现的功能,现在使用哈希表只需要两个Key就能解决。

提示:

哈希表与字符串的不同之处——哈希表在Key里面还有“字段”的概念。“字段”下面才是“值”。即一个哈希表的Key里面可以设置成百上千个键值对。

查询用户在线状态的小程序,如果使用哈希表来重构,则代码如下:

代码9-2  使用哈希表记录用户在线状态



其中,主要代码说明如下。

●  第12行代码:向Redis中名为user_online_status的哈希表中添加一个字段,字段名为用户账号,值为1。如果不存在名为user_online_status的哈希表,则自动创建一个。

●  第21行代码:从Redis中名为user_online_status的哈希表中删除一个字段,字段名为用户账号。

●  第30行代码:检查名为user_online_status的哈希表中是否有某个特定的字段,如果没有这个字段就返回False,如果有这个字段就返回True。

使用哈希表来保存1000个用户在线状态,运行效果如图9-3所示。

列出Redis中的所有Key,可以看到1000个用户在线状态都储存在名为user_online_status这个Key里面。

列出这个Key中的所有键值,可以看到,在Redis命令行交互界面里面,输出结果是按照“Key-Value-Key-Value”的间隔顺序输出的。

图9-3  使用哈希表保存1000个用户在线状态

使用哈希表不仅可以减少Redis的个数,还能优化储存空间。Redis官方就特别说明,哈希表对存储结构进行过特殊的优化,储存相同的内容,占用的内存比字符串要小很多。

提示:

著名图片社区Instagram在官方博客中发布过一篇文章,详细介绍了把一百万个键值对从Redis字符串迁移到哈希表的过程。

https://instagram-engineering/storing-hundreds-of-millions-of-simple-key-value-pairs-inredis-1091ae80f74c

使用字符串保存一百万个键值对需要21GB的存储空间,而改为哈希表以后,只需要5GB的存储空间。



9.1.2  实例32:使用Python向哈希表中添加数据


哈希表一共有15个操作命令,对应到Python中就是15个方法。9.1.2和9.1.3小节将介绍其中最常用的几个方法。

向哈希表中添加数据,使用的方法名为hset或者hmset。

●  hset一次只能添加一个键值对。

●  hmset一次可以添加多个键值对。

代码格式为:

client.hset('Key',  ’字段名’,  ’值’)

client.hmset('Key',  {’字段名1':  ’值1',  ’字段名2':  ’值2',  ’字段名n':  ’值n'})

实例描述

向Redis中添加一个哈希表用来记录用户信息,Key为“people_info”,字段名为“姓名”,值为用户详细信息对应的JSON字符串。

代码如下:

代码9-3  向哈希表中逐条添加数据和批量添加数据

其中,主要代码说明如下。

●  第5行代码:向名为“people_info”的哈希表中添加一个字段,字段名为“张小二”,值为一个JSON字符串。

●  第7~12行代码:创建一个用户信息字典,字典的Key是不同的人名,值为每个人信息的JSON字符串。

●  第14行代码:批量插入多人信息到名为“people_info”的哈希表中。

运行效果如图9-4所示。图中的中文被  Redis  转码了,但从英文和数字可以看出信息添加成功。

图9-4  使用Python向哈希表中添加数据



9.1.3  实例33:使用Python从哈希表中读取数据


实例描述

分别使用4个不同的命令(hkeys、hget、hmget和hgetall)从哈希表中读取数据,并对比这四个命令的不同。

1.hkeys

hkeys用于获取所有字段的字段名,返回的数据是包含bytes型数据的列表。

使用格式为:

field_names  =  client.hkeys(’哈希表名’)

例如:

代码9-4  读取哈希表的字段名



其中,主要代码说明如下。

●  第5行代码:使用hkeys方法,获取people_info哈希表中的所有字段名,结果为一个列表。

●  第6、7行代码:展开field_names列表,并将结果解码为字符串后打印出来。

运行效果如图9-5所示。

图9-5  获取哈希表所有字段名

2.hget、hmget、hgetall

●  hget:获取一个字段的值。

●  hmget:一次性获取多个字段的值。

●  hgetall:获取一个哈希表中的所有字段名和值。

它们的使用格式为:

client.hget(’哈希表名’,  ’字段名’)

client.hmget(’哈希表名’,  [’字段名1',  ’字段名2',  ’字段名n'])

client.hgetall(’哈希表名’)

例如:分别实现从哈希表中读取一个、多个和全部字段的值,见代码9-5。

代码9-5  从哈希表中读取数据



其中,主要代码说明如下。

●  第5行代码:从名为people_info的哈希表中获取字段名为“张小二”的值。

●  第6行代码:由于从Redis获取的值是bytes型数据,所以要将其解码为字符串后再打印。

●  第9行代码:从名为“people_info”的哈希表中同时获取,名为“王小二”和“刘小五”这两个字段的值。

●  第10、11行代码:由于hmget返回的结果是列表,所以用for循环展开。

●  第14行代码:获取“people_info”哈希表中的所有字段名和值。返回的结果是一个字典,但是字典的Key和Value全都是bytes型的数据。

运行效果如图9-6所示。

图9-6  分别使用hget、hmget和hgetall获取数据

需要注意以下几点:

●  使用hget方法时,无论是哈希表名不存在或者字段名不存在,都会返回None。

●  使用hmget时,如果哈希表名不存在,则返回的列表所有元素都是None;如果哈希表中部分字段存在,部分字段不存在,则返回值列表中不存在的字段值表示为None。

●  在hgetall方法返回的字典中,Key和Value都是bytes型的数据,因此如果要查询里面的结果,也需要使用bytes型的数据,例如:

代码9-6  对哈希表中的中文进行解码以显示



9.1.4  实例34:使用Python判断哈希表中是否存在某字段,并获取字段数量


实例描述

判断一个哈希表中是否存在某个字段,并获取哈希表字段的个数。

1.判断一个哈希表中是否有某个字段。

如果要判断一个哈希表中是否有某个字段,有两个方法:

(1)获取这个字段的值,如果值为None,则这个字段就是不存在的。

(2)使用hexists方法。hexists方法的格式如下:

client.hexists(’哈希表名’,  ’字段名’)

如果字段存在,则返回True;如果字段不存在,则返回False。例如:

代码9-7  判断哈希表中是否有某个字段

提示:

如果哈希表名不存在,则hexists的第2个参数无论是什么都会返回False。

2.查看一个哈希表中有多少个字段。

如果需要知道一个哈希表中有多少个字段,则可以使用hlen方法。

hlen方法的格式如下:

client.hlen(’哈希表名’)

如果哈希表名存在,则返回字段数;如果哈希表不存在,则返回0。例如:

field_num  =  client.hlen('people_info')

print(f'people_info哈希表中一个有{field_num}个字段’)



9.1.5  实例35:在Redis交互环境redis-cli中读/写哈希表


Redis命令行交互环境对哈希表的显示不太直观,因此只做简单介绍。

实例描述

在redis-cli中,分别实现以下功能:

(1)向哈希表中添加内容。

(2)从哈希表中读取数据。

(3)判断字段是否存在。

(4)查看字段个数。

1.向哈希表中添加内容

在redis-cli中,向哈希表中添加数据使用的命令是“hset”和“hmset”,它们的格式如下:

hset  哈希表名  字段名  值

hmset  哈希表名  字段名1  值1  字段名2  值2  字段名n  值n

例如:

hset  people_info  赵老六  ’{"age":  100,  "salary":  10,  "address":  "北京"}'

hmset  book_info  论语  32  中庸  48  大学  50

运行效果如图9-7所示。

图9-7  在redis-cli中添加数据

2.从哈希表中读取数据

从哈希表中读取数据,分别对应的命令为“hkeys”“hget”“hmget”“hgetall”。它们的格式如下:

hkeys  哈希表名

hget  哈希表名  字段名

hmget  哈希表名  字段名1  字段名2  字段名3

hgetall  哈希表名

例如:

hkeys  book_info

hget  book_info  论语

hmget  book_info  论语  大学

hgetall  book_info

运行效果如图9-8所示。

图9-8  在redis-cli中获取哈希表数据

3.判断字段是否存在和获取字段数量

判断字段是否存在使用的命令为“hexists”,获取字段数量使用的关键字为“hlen”。它们的格式如下:

hexists  哈希表名  字段名

hlen  哈希表名

执行hexists时,如果字段存在,则返回1;如果字段不存在,则返回0。

例如:

hexists  book_info  论语

hlen  book_info

运行效果如图9-9所示。

图9-9  在redis-cli中判断字段是否存在并获取字段数


上一章 目录 下一章