Redis开发与运维. 2.3 哈希

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介:

2.3 哈希

几乎所有的编程语言都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数组。在Redis中,哈希类型是指键值本身又是一个键值对结构,形如value={{field1,value1},...{fieldN,valueN}},Redis键值对和哈希类型二者的关系可以用图2-14来表示。

 

图2-14 字符串和哈希类型对比

哈希类型中的映射关系叫作field-value,注意这里的value是指field对应的值,不是键对应的值,请注意value在不同上下文的作用。

2.3.1 命令

(1)设置值

hset key field value

下面为user:1添加一对field-value:

127.0.0.1:6379> hset user:1 name tom?

(integer) 1

如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命令,它们的关系就像set和setnx命令一样,只不过作用域由键变为field。

(2)获取值

hget key field

例如,下面操作获取user:1的name域(属性)对应的值:

127.0.0.1:6379> hget user:1 name?

"tom"

如果键或field不存在,会返回nil:

127.0.0.1:6379> hget user:2 name?

(nil)?

127.0.0.1:6379> hget user:1 age?

(nil)

(3)删除field

hdel key field [field ...]

hdel会删除一个或多个field,返回结果为成功删除field的个数,例如:

127.0.0.1:6379> hdel user:1 name?

(integer) 1

127.0.0.1:6379> hdel user:1 age?

(integer) 0

(4)计算field个数

hlen key

例如user:1有3个field:

127.0.0.1:6379> hset user:1 name tom?

(integer) 1?

127.0.0.1:6379> hset user:1 age 23

(integer) 1?

127.0.0.1:6379> hset user:1 city tianjin

(integer) 1

127.0.0.1:6379> hlen user:1

(integer) 3

(5)批量设置或获取field-value

hmget key field [field ...]?

hmset key field value [field value ...]

hmset和hmget分别是批量设置和获取field-value,hmset需要的参数是key和多对field-value,hmget需要的参数是key和多个field。例如:

127.0.0.1:6379> hmset user:1 name mike age 12 city tianjin

OK?

127.0.0.1:6379> hmget user:1 name city

1) "mike"

2) "tianjin"

(6)判断field是否存在

hexists key field

例如,user:1包含name域,所以返回结果为1,不包含时返回0:

127.0.0.1:6379> hexists user:1 name

(integer) 1

(7)获取所有field

hkeys key

hkeys命令应该叫hfields更为恰当,它返回指定哈希键所有的field,例如:

127.0.0.1:6379> hkeys user:1

1) "name"?

2) "age"?

3) "city"

(8)获取所有value

hvals key

下面操作获取user:1全部value:

127.0.0.1:6379> hvals user:1?

1) "mike"?

2) "12"?

3) "tianjin"

(9)获取所有的field-value

hgetall key

下面操作获取user:1所有的field-value:

127.0.0.1:6379> hgetall user:1

1) "name"

2) "mike"

3) "age"

4) "12"

5) "city"

6) "tianjin"

在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果开发人员只需要获取部分field,可以使用hmget,如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型,hscan将在2.7节介绍。

(10)hincrby hincrbyfloat

hincrby key field

hincrbyfloat key field

hincrby和hincrbyfloat,就像incrby和incrbyfloat命令一样,但是它们的作用域是filed。

(11)计算value的字符串长度(需要Redis 3.2以上)

hstrlen key field

例如hget user:1 name的value是tom,那么hstrlen的返回结果是3:

127.0.0.1:6379> hstrlen user:1 name

(integer) 3

表2-3是哈希类型命令的时间复杂度,开发人员可以参考此表选择适合的命令。

表2-3 哈希类型命令的时间复杂度

命  令         时间复杂度

hset key field value  O(1)

hget key field   O(1)

hdel key field [field ...]      O(k),k是field个数

hlen key   O(1)

hgetall key        O(n),n是field总数

hmget field [field ...]         O(k),k是field的个数

hmset field value [field value ...]      O(k),k是field的个数

hexists key field        O(1)

hkeys key O(n),n是field总数

hvals key  O(n),n是field总数

hsetnx key field value       O(1)

hincrby key field increment      O(1)

hincrbyfloat key field increment      O(1)

hstrlen key field        O(1)?

 

2.3.2 内部编码

哈希类型的内部编码有两种:

ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。

hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

下面的示例演示了哈希类型的内部编码,以及相应的变化。

1)当field个数比较少且没有大的value时,内部编码为ziplist:

127.0.0.1:6379> hmset hashkey f1 v1 f2 v2

OK

127.0.0.1:6379> object encoding hashkey

"ziplist"

2.1)当有value大于64字节,内部编码会由ziplist变为hashtable:

127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 byte...忽略..."

OK

127.0.0.1:6379> object encoding hashkey

"hashtable"

2.2)当field个数超过512,内部编码也会由ziplist变为hashtable:

127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 f3 v3 ...忽略... f513 v513

OK

127.0.0.1:6379> object encoding hashkey

"hashtable"

有关哈希类型的内存优化技巧将在8.3节中详细介绍。

2.3.3 使用场景

图2-15为关系型数据表记录的两条用户信息,用户的属性作为表的列,每条用户信息作为行。

如果将其用哈希类型存储,如图2-16所示。

相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在更新操作上会更加便捷。可以将每个用户的id定义为键后缀,多对field-value对应每个用户的属性,类似如下伪代码:

UserInfo getUserInfo(long id){?

    // 用户id作为key后缀

    userRedisKey = "user:info:" + id;

    // 使用hgetall获取所有用户信息映射关系

    userInfoMap = redis.hgetAll(userRedisKey);

    UserInfo userInfo;?

    if (userInfoMap != null) {?

        // 将映射关系转换为UserInfo

        userInfo = transferMapToUserInfo(userInfoMap);?

    } else {

        // 从MySQL中获取用户信息

        userInfo = mysql.get(id);

        // 将userInfo变为映射关系使用hmset保存到Redis中

        redis.hmset(userRedisKey, transferUserInfoToMap(userInfo));

        // 添加过期时间?

        redis.expire(userRedisKey, 3600);

    }?

    return userInfo;?

}

但是需要注意的是哈希类型和关系型数据库有两点不同之处:

哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的field,而关系型数据库一旦添加新的列,所有行都要为其设置值(即使为NULL),如图2-17所示。

关系型数据库可以做复杂的关系查询,而Redis去模拟关系型复杂查询开发困难,维护成本高。

 

图2-17 关系型数据库稀疏性

开发人员需要将两者的特点搞清楚,才能在适合的场景使用适合的技术。到目前为止,我们已经能够用三种方法缓存用户信息,下面给出三种方案的实现方法和优缺点

分析。

1)原生字符串类型:每个属性一个键。

set user:1:name tom

set user:1:age 23?

set user:1:city beijing

优点:简单直观,每个属性都支持更新操作。

缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种方案一般不会在生产环境使用。

2)序列化字符串类型:将用户信息序列化后用一个键保存。

set user:1 serialize(userInfo)

优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。

缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。

3)哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。

hmset user:1 name tom?age 23 city beijing

优点:简单直观,如果使用合理可以减少内存空间的使用。

缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
17天前
|
运维 Java Linux
【运维基础知识】掌握VI编辑器:提升你的Java开发效率
本文详细介绍了VI编辑器的常用命令,包括模式切换、文本编辑、搜索替换及退出操作,帮助Java开发者提高在Linux环境下的编码效率。掌握这些命令,将使你在开发过程中更加得心应手。
18 2
|
19天前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
21 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
14天前
|
存储 运维 监控
实时计算Flink版在稳定性、性能、开发运维、安全能力等等跟其他引擎及自建Flink集群比较。
实时计算Flink版在稳定性、性能、开发运维和安全能力等方面表现出色。其自研的高性能状态存储引擎GeminiStateBackend显著提升了作业稳定性,状态管理优化使性能提升40%以上。核心性能较开源Flink提升2-3倍,资源利用率提高100%。提供一站式开发管理、自动化运维和丰富的监控告警功能,支持多语言开发和智能调优。安全方面,具备访问控制、高可用保障和全链路容错能力,确保企业级应用的安全与稳定。
26 0
|
15天前
|
NoSQL API Redis
如何使用 C++ 开发 Redis 模块
如何使用 C++ 开发 Redis 模块
|
3月前
|
运维 Devops 持续交付
自动化运维之路:从脚本到DevOps探索后端开发:从基础到高级实践
【8月更文挑战第28天】在数字化时代的浪潮中,企业对于IT运维的要求越来越高。从最初的手动执行脚本,到如今的自动化运维和DevOps实践,本文将带你领略运维的演变之旅。我们将探索如何通过编写简单的自动化脚本来提升效率,进而介绍DevOps文化的兴起及其对现代运维的影响。文章将为你揭示,通过持续集成、持续部署和微服务架构的实践,如何构建一个高效、可靠的运维体系。准备好让你的运维工作变得更加智能化和自动化了吗?让我们一起踏上这段旅程。 【8月更文挑战第28天】 本文旨在为初学者和有一定经验的开发者提供一个深入浅出的后端开发之旅。我们将一起探索后端开发的多个方面,包括语言选择、框架应用、数据库设计
|
3月前
|
运维 Devops 数据库
太卷了!DevOps,就是开发要把运维卷跑了?
太卷了!DevOps,就是开发要把运维卷跑了?
|
28天前
|
机器学习/深度学习 人工智能 运维
构建高效运维体系:从自动化到智能化的演进
本文探讨了如何通过自动化和智能化手段,提升IT运维效率与质量。首先介绍了自动化在简化操作、减少错误中的作用;然后阐述了智能化技术如AI在预测故障、优化资源中的应用;最后讨论了如何构建一个既自动化又智能的运维体系,以实现高效、稳定和安全的IT环境。
52 4
|
19天前
|
运维 Linux Apache
,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具
【10月更文挑战第7天】随着云计算和容器化技术的发展,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具,通过定义资源状态和关系,确保系统始终处于期望配置状态。本文介绍Puppet的基本概念、安装配置及使用示例,帮助读者快速掌握Puppet,实现高效自动化运维。
42 4
|
15天前
|
存储 运维 监控
高效运维:从基础架构到自动化管理的全面指南
【10月更文挑战第11天】 本文将深入探讨如何通过优化基础架构和引入自动化管理来提升企业IT运维效率。我们将从服务器的选择与配置、存储解决方案的评估,到网络的设计与监控,逐一解析每个环节的关键技术点。同时,重点讨论自动化工具在现代运维中的应用,包括配置管理、持续集成与部署(CI/CD)、自动化测试及故障排除等方面。通过实际案例分析,展示这些技术如何协同工作,实现高效的运维管理。无论是IT初学者还是经验丰富的专业人员,都能从中获得有价值的见解和实操经验。
37 1
|
17天前
|
运维 监控 测试技术
构建高效运维体系:从监控到自动化的实践之路
【10月更文挑战第9天】 在当今信息技术飞速发展的时代,运维作为保障系统稳定性与效率的关键角色,正面临前所未有的挑战。本文将探讨如何通过构建一个高效的运维体系来应对这些挑战,包括监控系统的搭建、自动化工具的应用以及故障应急处理机制的制定。我们将结合具体案例,分析这些措施如何帮助提升系统的可靠性和运维团队的工作效率。
35 1