资讯详情

第五章:进入Redis的Hash核心

📅 2026/10/21 1:20:30 | 华诺云谱 👁 阅读
第五章:进入Redis的Hash核心
redis小知识Redis 所有的 key 都是字符串但 value 的类型存在差异且一般来说遇到乱码问题的概率更小。Redis 中的字符串直接按二进制数据方式存储不会做任何编码转换存啥取啥不仅能存储整数、普通文本字符串、JSON、xml还能存储图片、视频、音频等二进制数据。而像 MySQL若使用默认的拉丁文字符集插入中文就会失败。需要注意的是音频视频类二进制数据体积可能较大Redis 对 string 类型限制了大小最大为 512M并且 Redis 采用单线程模型期望进行的操作都能比较快速。一. redis内部编码字符串类型的内部编码有 3 种• int8 个字节的长整型。• embstr小于等于 39 个字节的字符串。• raw大于 39 个字节的字符串。Redis 会根据当前值的类型和长度动态决定使用哪种内部编码实现。整型类型示例如下127.0.0.1:6379 set key 6379 OK 127.0.0.1:6379 object encoding key int短字符串示例如下# 一般⼩于等于 39 个字节的字符串 # 不要记数字要根据业务场景灵活应对 127.0.0.1:6379 set key hello OK 127.0.0.1:6379 object encoding key embstr长字符串示例如下# ⼤于 39 个字节的字符串 # 不要记数字要根据业务场景灵活应对 127.0.0.1:6379 set key one string greater than 39 bytes ........ OK 127.0.0.1:6379 object encoding key raw二关于redis的经典应用场景2.1缓存Cache功能整体思路应用服务器访问数据时先查询 Redis。如果 Redis 上数据存在直接从 Redis 取数据交给应用服务器不再访问数据库。如果 Redis 上数据不存在读取 MySQL将读到的结果返回给应用服务器同时把这个数据写入到 Redis 中。Redis 缓存特点Redis 这样的缓存经常用来存储 “热点” 数据即高频使用的数据。这个定义方式结合业务场景有很多种方式刚才上述描述的过程相当于把最近使用到的数据作为热点数据暗含假设某个数据一旦被用到很可能在最近这段时间被反复用到。缓存问题及解决上述缓存存在明显问题随着时间推移会有越来越多的 key 在 redis 上访问不到从而从 mysql 读取并写入 redis导致 redis 中的数据越来越多。解决方法在把数据写给 redis 的同时给这个 key 设置一个过期时间。Redis 在内存不足时提供了淘汰策略后面再细说。Redis MySQL 组成的缓存存储架构举个栗子~企业收集用户数据主要是为了统计以此进一步明确用户需求进而根据需求改进和迭代产品。不过 Redis 并不擅长数据统计比如在上述 Redis 中统计播放量前 100 的视频就很麻烦而用 mysql 存储数据一个 sql 就能搞定。以视频网站为例其从视频存储获取视频数据执行 Redis 命令如 incr video:5253返回数据同时将播放量异步同步到统计数据仓库统计数据仓库可能是 mysql 或 hdfs 等这里写入统计数据仓库的步骤是异步的并非来一个播放请求就立即写一个数据。在实际中开发成熟、稳定的真实计数系统面临诸多挑战像防作弊、按不同维度计数、避免单点问题、数据持久化到底层数据源等需要根据实际业务需求场景来应对。2.1共享会话Session⼀个分布式 Web 服务将用户的 Session 信息例如用户登录信息保存在各⾃的服务器中但这样会造成⼀个问题出于负载均衡的考虑分布式服务会将⽤⼾的访问请求均衡到不同的服务器上并且通常⽆法保证用户每次请求都会被均衡到同⼀台服务器上这样当用户刷新⼀次访问是可能会发现需要重新登录这个问题是用户⽆法容忍的。如果每个应用服务器维护自己的会话数据此时彼此之间不共享用户请求访问到不同的服务器上就可能会出现一些不能正确处理的情况了~为了解决这个问题可以使用 Redis 将用户的 Session 信息进⾏集中管理如上所示在这种模式下只要保证 Redis 是高可用和可扩展性的⽆论用户被均衡到哪台 Web 服务器上都集中从Redis 中查询、更新 Session 信息。2.2手机验证码很多应⽤出于安全考虑会在每次进⾏登录时让用户输⼊⼿机号并且配合给⼿机发送验证码然后让⽤⼾再次输⼊收到的验证码并进⾏验证从⽽确定是否是用户本⼈。为了短信接⼝不会频繁访 问会限制用户每分钟获取验证码的频率例如⼀分钟不能超过 5 次此功能可以⽤以下伪代码说明基本实现思路String 发送验证码(phoneNumber) { key shortMsg:limit: phoneNumber; // 设置过期时间为 1 分钟60 秒 // 使⽤ NX只在不存在 key 时才能设置成功 bool r Redis 执⾏命令set key 1 ex 60 nx if (r false) { // 说明之前设置过该⼿机的验证码了 long c Redis 执⾏命令incr key if (c 5) { // 说明超过了⼀分钟 5 次的限制了 // 限制发送 return null; } } // 说明要么之前没有设置过⼿机的验证码要么次数没有超过 5 次 String validationCode ⽣成随机的 6 位数的验证码(); validationKey validation: phoneNumber; // 验证码 5 分钟300 秒内有效 Redis 执⾏命令set validationKey validationCode ex 300; // 返回验证码随后通过⼿机短信发送给⽤⼾ return validationCode ; } // 验证⽤⼾输⼊的验证码是否正确 bool 验证验证码(phoneNumber, validationCode) { validationKey validation: phoneNumber; String value Redis 执⾏命令get validationKey; if (value null) { // 说明没有这个⼿机的验证码记录验证失败 return false; } if (value validationCode) { return true; } else { return false; } }2.2计数Counter功能许多应用都会使用Redis 作为计数的基础⼯具它可以实现快速计数、查询缓存的功能同时数据可以异步处理或者落地到其他数据源。如图 2-11 所⽰例如视频⽹站的视频播放次数可以使⽤ Redis 来完成⽤⼾每播放⼀次视频相应的视频播放数就会⾃增 1。这张图展示了视频网站播放量统计的一个流程。视频网站从视频存储获取视频数据然后通过执行 Redis 命令如 incr video:5253来记录视频的播放量Redis 中存储着各个视频的播放量数据。之后会以异步的方式将播放量同步到统计数据仓库统计数据仓库可能是 mysql 或者 hdfs 等。这里的写入步骤是异步的并不是每来一个播放请求就立即写入数据三.Redis 哈希类型核心命令汇总几乎所有的主流编程语言都提供了哈希hash类型它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中哈希类型是指值本身又是⼀个键值对结构形如 key keyvalue { {field1, value1 }, ..., {fieldN, valueN } }Redis 自身已经是键值对结构了。Redis 自身的键值对就是通过哈希的方式来组织的。把 key 这一层组织完成之后到了 value 这一层value 的其中一种类型还可以再是哈希。注意哈希类型中的映射关系通常称为 field-value用于区分 Redis 整体的键值对key-value注意这⾥的 value 是指 field 对应的值不是键key对应的值请注意 value 在不同上下⽂的作用3.1HSET HGETHSET命令设置 hash 中指定的字段field的值value可以传多组HGET命令获取 hash 中指定字段的值。返回值是设置成功的键值对field - value的个数。时间复杂度插⼊⼀组 field 为 O(1), 插⼊ N 组 field 为 O(N)返回值添加的字段的个数。3.2HEXISTSHEXISTS命令判断 hash 中是否有指定的字段HEXISTS key field时间复杂度O(1)返回值1 表⽰存在0 表⽰不存在。3.3HDEL删除 hash 中指定的字段。返回值是本次操作删除的字段个数。HDEL key field [field ...]注意del删除的是 keyhdel删除的是 field。3.4HKEYS 和 HVALSHKEYS命令获取 hash 中的所有字段先根据 key 找到对应的 hashO (1)然后再遍历 hashO (N)N 是 hash 的元素个数。HVALS 命令获取 hash 中的所有的值和hkeys相对能够获取到 hash 中的所有 value时间复杂度O(N), N 为 field 的个数.返回值所有的值3.5HGETALL 和 HMGETHGETALL命令获取 hash 中的所有字段以及对应的值HMGET命令⼀次获取 hash 中多个字段的值类似于之前的 MGET可以一次查询多个 field而 HGET 一次只能查一个 field。HMGET key field [field ...]时间复杂度只查询⼀个元素为 O(1), 查询多个元素为 O(N), N 为查询元素个数.返回值字段对应的值或者 nil。注意多个 value 的顺序和 field 的顺序是匹配的。有没有 hmmset一次设置多个 field 和 value 呢有但是并不需要使用hset 已经支持一次设置多个 field 和 value 了。一条命令都能完成所有的遍历操作。上述hkeyshvalshgetall都是存在一定风险的hash 的元素个数太多执行的耗时会比较长从而阻塞 redis。补充hscan遍历 redis 的 hash但是它属于 “渐进式遍历”敲一次命令遍历一小部分再敲一次再遍历一小部分…… 连续执行多次就可以完成整个的遍历过程了时间就是可控的。这个哈希表在扩容的时候也是按照化整为零的方式进行的ConcurrentHashMap是线程安全的哈希表。3.6HLENHLEN获取 hash 中的所有字段的个数。类似于 setnx不存在的时候才能设置成功如果存在则失败。HLEN key时间复杂度O(1)返回值字段个数3.7HSETNXHSETNX命令在字段不存在的情况下设置 hash 中的字段和值类似于 setnx不存在的时候才能设置成功如果存在则失败返回值1 表示设置成功0 表示失败。3.8HINCRBY 和 HINCRBYFLOATHINCRBY 命令将 hash 中字段对应的数值添加指定的值HINCRBY key field increment时间复杂度O(1)返回值该字段变化之后的值HINCRBYFLOAT命令HINCRBY 的浮点数版本时间复杂度O(1)返回值该字段变化之后的值hash 这里的 value也可以当做数字来处理。hincrby 就可以加减整数hincrbyfloat 就可以加减小数。使用频率不算很高redis 没有提供类似于 incr decr总结四.hset的内部编码4.1ziplist与hashtable哈希的内部编码有两种ziplist压缩列表当哈希类型元素个数小于 hash-max-ziplist-entries 配置默认 512 个同时所有值都小于 hash-max-ziplist-value 配置默认 64 字节时Redis 会使用 ziplist 作为哈希的内部实现ziplist 使⽤更加紧凑的结构实现多个元素的连续存储所以在节省内存方面比hashtable 更加优秀。hashtable哈希表当哈希类型⽆法满⾜ ziplist 的条件时Redis 会使⽤ hashtable 作为哈希的内部实现因为此时 ziplist 的读写效率会下降而hashtable 的读写时间复杂度为 O(1)。压缩相关常见压缩算法有 rar、zip、gzip、7z 等压缩本质是对数据重新编码。不同数据有不同特点结合这些特点精妙设计重新编码后可缩小体积。举例如字符串 “abbbccddddddeeeee” 可重新编码为 “1a2b3c4d5e”对于含大量重复字符 “0” 的文件内容可编码为更简洁形式像 “abcd0 [100] efgh”。ziplistziplist 原理与压缩类似内部数据结构精心设计目的是节省内存空间。表示普通哈希表可能会浪费一定空间哈希表是数组部分位置可能无元素但 ziplist 进行读写元素操作时速度较慢元素个数少影响不明显个数多则影响较大。4.2哈希类型存储策略字符串作为缓存string 也可以作为缓存使用但存储结构化的数据使用 hash 类型更合适一些类似于数据库表结构。例如存储用户信息用 string 类型需使用 json 格式若想获取或修改其中某个字段需读取整个 json 解析成对象操作后再写回而用 hash 类型可方便操作每个字段。哈希作为缓存使用 hash 的方式表示 UserInfo就可以使用 field 表示对象的每个属性数据表的每个列此时就可以非常方便的修改 / 获取任何一个属性的值。使用 hash 的方式确实读写 field 更直观高效但是付出的是空间的代价需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换可能会造成内存的较大消耗。缓存方式对比截⾄目前为⽌我们已经能够⽤三种⽅法缓存⽤⼾信息下⾯给出三种⽅案的实现⽅法和优缺点分析。1.原生字符串类型⸺使⽤字符串类型每个属性⼀个键。set user:1:name James set user:1:age 23 set user:1:city Beijing优点实现简单针对个别属性变更也很灵活。缺点占⽤过多的键内存占⽤量较⼤同时⽤⼾信息在 Redis 中⽐较分散缺少内聚性所以这种方案基本没有实⽤性。2.序列化字符串类型例如 JSON 格式1 set user:1 经过序列化后的⽤⼾对象字符串优点针对总是以整体作为操作的信息⽐较合适编程也简单。同时如果序列化⽅案选择合适内 存的使⽤效率很高。缺点本⾝序列化和反序列需要⼀定开销同时如果总是操作个别属性则非常不灵活。3.哈希类型hmset user:1 name James age 23 city Beijing优点简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。缺点需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换可能会造成内存的较⼤消耗4.3原生字符串类型与高内聚低耦合原生字符串类型使用字符串类型每个属性一个键这种方式相当于把同一个数据的各个属性给分散开表示低内聚写代码 / 管理数据 高内聚。高内聚低耦合高内聚把有关联的东西放在一起最好能放在指定的地方。低耦合两个模块 / 代码之间的关联关系越小越容易相互影响追求的是 “低耦合”避免 “牵一发而动全身”。举例高耦合如女朋友生病住院“我” 会推掉所有事去照顾低耦合如 “我” 的前女友住院“我” 不会有任何影响甚至还会给她朋友圈点赞。关于用户信息存储中 uid 的思考思考用户信息存储中 uid 是否必要若不存 uid 在工程实践中一般也会再存一份方便后续相关代码使用。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑