news 2026/10/14 22:46:58

Redis哈希类型详解:结构、原子性与内部编码优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis哈希类型详解:结构、原子性与内部编码优化

开课之前先问一个很实际的问题:你项目里的用户信息、商品参数、博客详情,是不是还在用一堆带前缀的字符串key硬扛?比如user:1001:name、user:1001:age、user:1001:email,存倒是能存,读倒是能读,但等你缓存的数据量上来,这玩意就是定时炸弹——key太多、内存碎片化严重、修改一个字段要维护一大堆key,业务代码也写得又臭又长。这堂课咱们就解决这个事。

今天这节《Redis魔法学院·第四课》,把哈希(Hash)这个结构彻底讲透。哈希在Redis里的地位,基本等同于字典在Python、HashMap在Java里的地位,属于日常开发中用到最频繁、最顺手、也最容易用出花来的数据类型。我会从它的Field-Value层级结构讲起,说明白它和普通字符串key的本质区别;然后带你细看HSET、HINCRBY这批命令的原子性到底怎么用、能帮你省掉多少并发上的麻烦;最后把内部编码机制(ziplist、listpack、hashtable)掰开揉碎,让你清楚知道一个Redis key在内存里到底是怎么变形的、什么时候会触发转换、转换后又该怎么优化。

无论你是刚接触Redis的初学者,还是已经写了几年后端但只会用get/set的老开发,这节课都值得认真看一遍。哈希用得好不好,直接决定你的缓存方案是优雅还是粗糙,也直接关系到线上内存和延迟指标的成色。

1. 哈希的Field-Value层级结构:一张表吃透它

1.1 从经典的"用户信息存储"场景看哈希的存在意义

先复现一个最常见的需求:存一个用户的ID、昵称、头像、年龄、手机号。很多人的第一反应是"那就建五个key呗",于是代码里出现这样的写法:

SET user:1001:name "张三" SET user:1001:age 28 SET user:1001:email "zhangsan@example.com"

这么写当时看起来没问题,但后续的麻烦是叠加的:

  • 想拿到用户的完整信息,需要发三次网络请求(或三次Redis命令),每次都产生一次RTT(往返延迟);
  • 想批量取20个用户的某几个字段,拼接key的数量直接失控;
  • 想单独更新一个年龄字段,没毛病,但想统一个过期时间,对不起——你只能给每个key分别设置EXPIRE,漏了哪个,这个用户的信息就会变成"残缺状态";
  • 更麻烦的是key的碎片化:大量相似前缀的key占用了更多内存空间和字典槽位,对后续的逐出策略、集群分片都不友好。

哈希把这些问题一刀切了。哈希的核心结构是:一个key内部挂着一张由Field和Value组成的小表。所以你可以只用一个keyuser:1001,然后在它下面挂name、age、email这堆字段:

HSET user:1001 name "张三" age 28 email "zhangsan@example.com"

从使用者的角度来看,哈希相当于给Redis的key-value模型多套了一层"内层key"。外层的key负责定位"这是谁的数据",内层的field负责定位"这是哪个属性"。这种嵌套的层级设计,跟我们脑子里"一个用户、多个属性"的天然认知完全对齐,所以它才成为存储对象型数据的首选结构,没有之一。

1.2 和JSON字符串方案对比,哈希赢在哪、输在哪

看到这里肯定有人问:那我把用户信息序列化成一个JSON字符串,塞到单个key里不也能解决key碎片化问题吗?比如:

SET user:1001 '{"name":"张三","age":28,"email":"..."}'

确实,一个key搞定一切,简洁省事,看起来哈希的优势被抹平了。但紧接着问题就来了:JSON字符串方案在字段级操作上完全瘫痪。你想只改一下age,流程是GET整个字符串、反序列化成对象、改字段、再序列化、再SET回去。这一套操作别说原子性了,光是并发场景下两个客户端同时改不同字段,后写的那个就会把前面那个的修改完全覆盖掉。而且每次操作都要承担序列化和反序列化的CPU开销,数据大了以后这开销一点不低。

哈希天生就是为了"按需取用、按需更新"服务的:

  • 想读单个字段:HGET user:1001 name,不读整个对象;
  • 想更新单个字段:HSET user:1001 age 29,不碰其他字段;
  • 想同时读多个字段:HMGET user:1001 name age,一次命令返回多个值;
  • 想同时更新多个字段:HSET user:1001 age 29 email "new@example.com",一次命令多字段全部生效。

当然哈希也不是没有缺点。最大的短板在于它保存的value只能是字符串,不像JSON那样天然可以嵌套对象和数组。比如用户信息里有个"收货地址列表",每个地址又有省市区街道,这种两层以上的结构哈希没法直接映射,硬塞的话要么搞"字段名用冒号拼接"疯狂展平,要么拆成多个哈希key,要么还是得求助于JSON序列化。

所以我的习惯是:简洁扁平的对象型数据用哈希,结构复杂有嵌套的数据才用JSON字符串。别一根筋。

1.3 从内部视角看:哈希到底长什么样

虽然我们用起来是一个key下面挂着field-value,但哈希在Redis内部的真实存在方式,跟"一个key塞一张map"差不多。当哈希字段比较少、值比较短的时候,Redis用的是紧凑的连续内存布局(ziplist或者listpack),数据一个挨着一个存在内存里;当字段数增多或某个值变大时,Redis会自动切换成真正的哈希表结构(hashtable),用数组加链表的方式组织,从而保证随机访问依然高效。

这个"根据规模自动调整内部形态"的设计,是哈希性能优雅的根源。不过凡是内部有自动转换机制的东西,就一定有边界值和坑,后面第3节我专门拆这个,这里先记住结论就够了:同一个HSET命令写进去的数据,在不同阶段底层可能是完全不同的两种结构,但对外命令行为完全一致,Redis自动帮你搞定切换。

2. 原子性操作:为什么哈希是并发更新的救星

2.1 先把"原子性"三个字说透

很多人听到"原子性操作"就头大,觉得是高并发论文里面的词。其实理解起来特别简单:原子操作就是"要么不做,要么做完整"的操作,中间不允许被打断。类比一下,你去银行转账,转账这个动作要么成功完成(钱从A扣了、到B账上了),要么完全失败(两边都没变化),绝不可能出现"A的钱扣了、B没收到"这种中间状态。

Redis的命令天然具备原子性,因为Redis本身是单线程处理命令的。也就是说,在任何时刻,Redis只执行一条命令,执行期间不会有其他命令插进来打断。单个命令的原子性是从这个单线程模型里白捡的。哈希的所有基础命令,比如HSET、HDEL、HINCRBY、HSETNX,全都满足这个特性——命令一旦发出,要么整个执行成功,要么不执行,绝不会执行到一半。

但这还不是哈希原子性真正的重点。重点是:一条HSET命令可以同时设置多个字段,多个字段要么全部写入成功,要么全部不动。这就解决了前面JSON方案里"部分更新覆盖"的顽疾。

2.2 HINCRBY:计数器的原子自增

哈希最具原子性价值的命令,是HINCRBY。如果你老老实实走"读出来、加一、写回去"的老路,在并发场景下十有八九要出事。两个请求同时读到同一个count值100,同时加一,同时写回101——实际上应该加两次变成102,结果丢了一次更新。经典的丢失更新问题。

用HINCRBY就完全不同了:

HINCRBY stats:goods:1001 sales 1

这一条命令在Redis内部直接把field对应的value加上指定的步长,整个过程原子完成,不管外面有多少并发请求同时来,这个值都绝对不会加漏,也不会加错到别的地方。Redis单线程模型注定了这条命令的处理过程是串行化的,每个请求之间互不干扰。

如果你需要在业务里维护一个"计数器但又不想单独造key"的数据,比如商品的销售数量、文章的点赞数、用户的积分余额,全都适合用哈希+HINCRBY实现。顺便提一句,HINCRBY只对整数有效,如果field的value不是纯数字字符串,执行的时候Redis会直接报错,这个坑后面排查部分细讲。

2.3 HSETNX:让"首次写入"不再纠结

还有一个容易被忽略的命令:HSETNX。它表示"仅当field不存在时才设置,否则什么都不做"。它的原子性意味着"判断field是否存在"和"写入field的值"这两步是绑在一起的,别的客户端不可能在你判断完之后、写入之前插一脚。

这个命令最典型的用法是实现分布式锁的"加锁"动作。Redis官方的分布式锁方案Redlock,原子命令的基础就是SET key value NX,而哈希场景下如果要在某个对象的某个字段上加锁,就可以用HSETNX lock:resource owner_id token来实现。同样的道理:如果你在业务里要做的逻辑是"这个用户第一次注册时写默认值、之后不许覆盖",HSETNX是你最顺手的工具。

2.4 用MULTI/EXEC还是用原子命令?

聊到这里必须做一次澄清。我经常看到有同学一谈到原子性,就说"那我用MULTI/EXEC事务包一下不就行了"。MULTI/EXEC确实是Redis提供的事务机制,它保证一批命令连续执行、中间不会被其他客户端的命令插入。但它的语义跟单命令原子性不太一样:MULTI/EXEC不具备"如果中间某条命令失败则回滚前面的命令"的能力——Redis没有回滚机制,前面成功的命令依然是生效的。所以MULTI/EXEC更像"打包串行执行",而不是"要么全做要么全不做"。

如果你的需求是"A字段减一,B字段加一,这两个必须同步发生",那才需要MULTI/EXEC或者Lua脚本。但哈希大部分日常需求其实都可以浓缩成"一条命令完成",这就不需要额外的包装了。

3. 内部编码优化:小数据紧凑省钱,大数据随机访问高效

3.1 两种内部编码与转换阈值

哈希在Redis的内部编码一共有两种阶段。老版本Redis(3.2及之前)里用的是ziplist(压缩列表),从Redis 7.0开始、以及不少中间版本中,ziplist逐步被listpack(紧凑列表)取代。不管名字怎么变,设计思想一脉相承:字段少、值短的时候用连续内存块,省空间省指针开销;一旦规模变大,就整体切换成hashtable。

我能直接告诉你现阶段的转换触发条件。在Redis默认配置下,哈希字段数超过hash-max-ziplist-entries(默认128),或者任何一个字段的value长度超过hash-max-ziplist-value(默认64字节),哈希就会从紧凑编码转换成hashtable。注意,这个转换是单向的:一旦从compact编码升级成hashtable,就算后来你把多余的字段删掉,它也不会自动降级回listpack/ziplist。

对于这个设计,我的评价是:它非常聪明。因为Redis判断的依据不是"现在有多少字段",而是"曾经最多有多少字段"。做这种历史峰值判断,可以防止频繁升降级导致的内存抖动和CPU浪费。毕竟转换的成本不低,得把所有数据重新拷贝进新的内存结构里。

3.2 查看当前哈希的编码方式

不知道大家的习惯如何,反正我在调优哈希之前第一件事永远是看它的OBJECT ENCODING。举个实际例子:

127.0.0.1:6379> HSET user:1001 name "张三" age 28 (integer) 1 127.0.0.1:6379> OBJECT ENCODING user:1001 "listpack"

字段少、值短,就返回listpack。然后我们往里面塞一个大字段:

127.0.0.1:6379> HSET user:1001 intro "我是一个很长的自我介绍,内容超过六十四字节的话这个哈希就会被转换编码……" (integer) 1 127.0.0.1:6379> OBJECT ENCODING user:1001 "hashtable"

看到没有,一个超过64字节的大字段,整个哈希直接升级成了hashtable。这对小对象的存储来说是一个必须避开的坑:如果你设计了一个业务模型,每个用户有一个"个人简介"并且普遍超过64字节,那么哈希的压缩编码基本形同虚设。此时就要冷静评估:哈希到底还能不能帮你省钱,还是说应该换别的存储方案。

3.3 修改阈值:让哈希更省内存的实战调优

有人问:那我能让阈值变大吗?比如让hash-max-ziplist-entries从128改成1024,让hash-max-ziplist-value从64改成512,这样更多字段、更长的值也能继续享受压缩内存的好处?

技术上完全支持。修改redis.conf里的配置或直接用CONFIG SET动态调整:

CONFIG SET hash-max-ziplist-entries 512 CONFIG SET hash-max-ziplist-value 128

但是!调整之前必须想清楚后果。压缩编码的底层是连续内存块,它并不是真正的"哈希表",随机读取一个field的时候,Redis得在连续内存里逐个遍历查找。字段越多,查找效率越差,最坏情况下等于遍历一个列表。哈希表hashtable虽然占内存,但它的随机访问时间复杂度是O(1),不管有多少field都能一跳定位。

所以这个调整的本质,是你用"查找性能"换"内存空间"。如果业务里对哈希的读取量大、且每次可能只是读个别字段,那么把阈值调得过大就是慢性自杀——内存省了,延迟涨了,得不偿失。我个人的建议是:默认配置已经经过官方大量测试场景的验证,除非你有非常明确的内存瓶颈证据,否则不要轻易动这个阈值。

3.4 hashtable的内存放大效应

再展开说说为什幺大哈希要警惕。当哈希编码升级成hashtable之后,Redis内部维护的是一张真正的哈希表,哈希表的初始桶数量、装载因子(load factor)都有各自的规则。哈希表为了维持O(1)的查询效率,桶的数量通常是2的幂次增长,负载到一定程度就要扩容,扩容又要重新hash所有的field。如果你有一个hash的field特别多,比如到了几万、几十万,那么这张hashtable本身消耗的内存除了存储数据之外,还有一大部分是桶数组和链表指针的额外开支。

这种场景下的哈希,内存占用跟存储的数据大小已经不完全成正比了。你用一个field一万条数据的哈希存小额数据,可能比拆成一百个哈希、每个哈希一百个field占用内存更多。这就是"大哈希"的内存放大效应,很多老手踩过这个坑。所以哈希在业务设计上的经验法则是:单个哈希的字段数尽量控制在1000以内,值内容保持在合理长度,宁可多拆几个key,不要让一个key无限膨胀。

4. 实操环节:从拿连接到业务封装,一步不落

4.1 基础命令梳理与边界条件

哈希命令看起来多,其实核心就十来个,我做一个速查级别的归纳:

命令作用边界注意点
HSET key field value [field value ...]设置一个或多个字段field或value为空字符串,算合法但容易滋生脏数据
HGET key field读取单个字段field不存在返回nil
HMGET key field [field ...]读取多个字段不存在的字段对应位置返回nil
HGETALL key读取全部字段和值大hash慎用!返回元素是field和value交替排列
HDEL key field [field ...]删除一个或多个字段字段不存在时返回0
HEXISTS key field判断字段是否存在返回1存在,0不存在
HINCRBY key field increment整数值自增value不是整数会报错
HINCRBYFLOAT key field increment浮点值自增精度问题注意一下
HSETNX key field value字段不存在时写入原子操作,见上文
HLEN key统计字段数量查询哈希整体规模用
HKEYS key/HVALS key只取字段名/只取值大hash慎用,容易阻塞

别小看"边界注意点"这一列。实际生产环境里,HGETALL一个大哈希导致的Redis阻塞,我见过不止一次。当哈希已经切换成hashtable编码,里面存了几万、几十万个field,一条HGETALL要把全部字段和值打包返回给客户端,数据量可能以MB为单位计算,生成响应和网络传输的过程会让Redis主线程忙很长时间。Redis是单线程处理命令的,这意味着在它忙碌的这段时间里,所有其他客户端请求全部排队等待。

所以线上调用的铁律是:只取你需要的字段,用HMGET精确指定,而不是无脑HGETALL。需求说需要整个对象,你就得评估一下这个hash到底多大,承受不了就改造。

4.2 用哈希改造业务:用户购物车示例

理论说完,上一个完整的实操示例。假设我们要做一个用户购物车的缓存,每个用户一个hash,field是商品ID,value是加入购物车时的数量:

# 用户1001先把商品101加进购物车,数量2 HSET cart:1001 101 2 # 又往购物车里加了商品202,数量1 HSET cart:1001 202 1 # 用户想把商品101的数量改成5 HSET cart:1001 101 5 # 购物车数量+1 HINCRBY cart:1001 101 1 # 查看这个用户购物车里的所有商品和数量 HGETALL cart:1001 # 返回:101 6 202 1 # 用户删掉了商品202 HDEL cart:1001 202 # 购物车里还剩几种商品 HLEN cart:1001 # (integer) 1

这一整套操作天然就是"购物车"这个业务场景的完美映射。对比一下用字符串方案实现同样的功能,光是"商品101数量+1"就得先GET整个JSON再改回去再SET,又慢又容易丢更新。

4.3 代码层面封装:RedisTemplate和redis-py的使用差异

用实操代码展示一下。以Python的redis-py为例:

import redis r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True) # 对象整体写入 user_data = { "name": "张三", "age": 28, "email": "zhangsan@example.com", "avatar": "https://cdn.example.com/avatar/1001.jpg", } r.hset("user:1001", mapping=user_data) # 读取多个字段 name, age = r.hmget("user:1001", "name", "age") # 读取所有字段 all_data = r.hgetall("user:1001") # 年龄自增1 r.hincrby("user:1001", "age", 1) # 删除一个字段 r.hdel("user:1001", "avatar")

需要注意,redis-py的hset在旧版本里的用法是hset(name, key, value),新版本推荐使用mapping参数传入字典。如果你在写代码时发现hset的第二个参数一会儿是field名一会儿是dict,那就是版本API差异在作祟。建议统一用关键字参数mapping=来规避歧义。

如果你用的是Java的Spring Data Redis,操作方式稍有不同,RedisTemplate提供了专门的HashOperations接口:

HashOperations<String, String, Object> hashOps = redisTemplate.opsForHash(); Map<String, Object> userData = new HashMap<>(); userData.put("name", "张三"); userData.put("age", 28); userData.put("email", "zhangsan@example.com"); hashOps.putAll("user:1001", userData); Object age = hashOps.get("user:1001", "age"); hashOps.increment("user:1001", "age", 1);

这里有一个Java场景常见的坑:HashOperations内部value的泛型是Object,如果你存的是Integer类型的值,increment命令没有问题,但如果你存的是一开始用字符串拼出来的数字,increment执行时可能会因为类型不一致而报错。建议在业务代码里统一value的类型,别一会儿存字符串一会儿存数字。

4.4 管道(Pipeline)与批量操作技巧

其实很多人忽略了一个细节:哈希的批量操作,还可以跟Redis的Pipeline机制配合,进一步降低网络消耗。比如你要批量读取100个用户的姓名和年龄,如果发100次HMGET,每次都走一遍网络往返,时间开销是100乘以RTT。如果把这100条HMGET命令一次性打包发给Redis,再一次性接收所有响应,整体耗时大约等于一次RTT加上100条命令的实际处理时间,差距非常可观。

pipe = r.pipeline(transaction=False) for uid in user_ids: pipe.hmget(f"user:{uid}", "name", "age") results = pipe.execute()

Pipeline在批量场景下几乎是必选方案。它的本质是"牺牲一点命令的独立性,换来吞吐量的巨大提升",非常契合"批量读取一批哈希的某些字段"这种高频业务。

5. 常见问题与实战避坑速查

5.1 问题:哈希的expire只能作用在key上,不能作用在field上

这是一个高频面试也爱问的点:能不能给哈希里的某个field设置过期时间?答案是不能。所有过期时间相关命令(EXPIRE、TTL)都只能作用于Redis的外层key上。你要么给整个哈希key设置统一过期时间,要么就得自己设计"惰性过期逻辑"——判断某个field的value里存的过期时间戳,读的时候手动检查。

常见的处理方案有两个:

  • 字段级别过期:在field对应value里拼上过期时间戳,比如value存成json{"expire_at": 1700000000, "data": "..."},读取时自己判断;
  • 整体过期:如果整个哈希的所有field都是同一时间过期的(比如购物车30分钟后清空),直接EXPIRE cart:1001 1800就完事,非常干净。

5.2 问题:cmd报错"hash value is not an integer"怎么办

这是HINCRBY最容易踩的坑。你对某个field执行HINCRBY时,如果Redis发现这个field存的值不是"可以被解析成64位整数"的字符串,就会直接报错。我遇到过一种情况:同一个哈希里的count字段本来是用来计数的,结果有个同事在业务里顺手把它改成存订单号了,后面的自增逻辑全部崩掉。

排查思路很简单:先HGET key field看当前实际存的什么值,用TYPE或OBJECT ENCODING辅助判断数据形态。修复手段要么改回合法的整数字符串,要么在写代码前增加强校验,绝不能依赖Redis的类型宽容性。

5.3 问题:BigKey导致阻塞,如何发现和治理

这是生产环境中最危险、最隐蔽的问题。BigKey指的就是单个key存储的数据量过大,大到Redis在处理它的时候占用了太多CPU和内存资源,阻塞了其他命令。对于哈希类型来说,BigKey通常是"字段数量过多"或者"某个字段值超大"。

排查的时候可以用Redis自带的工具:

redis-cli --bigkeys

它会扫描整个实例,统计各类数据结构里最大的key。如果发现哈希类型的某个key字段数达到几万甚至几十万,就要对它执行"拆哈希"的治理操作了。

拆哈希怎么拆?最常用的做法是按业务维度分桶。比如一个"行为埋点缓存"的哈希,原本维度是用户ID,所有用户在同一个key下面,字段数随用户量无限上涨。拆解时可以按用户ID的hash值取模,分成event:0、event:1、event:2等若干个key,取模数可以根据预计的用户量设定。分发时算一下用户ID属于哪个桶,就把数据写到对应的哈希key里。这样每个哈希的字段数保持在可控范围内,HGETALL和遍历的性能都好很多。

5.4 问题:哈希升级编码之后,内存降不下去

前面已经说过,编码从listpack升级成hashtable之后是回不去的。如果你发现线上某个哈希曾经字段特别多,后来删掉了大半的字段,内存占用依然居高不下,这就是编码不再降级所致。

遇到这种情况,唯一有效的办法是将数据迁移到一个全新的key里,比如用一个新的时间戳后缀重建缓存,通过缓存双写、逐步切换流量的方式让旧key过期淘汰。如果你的Redis版本支持7.4以上,会看到listpack已经成为主要紧凑编码,行为模型基本上跟旧版的ziplist一样,依然存在单向切换的问题。所以从根上治理,还是控制每个哈希的字段数量。

5.5 问题:热Key导致延迟毛刺,怎么发现怎么缓解

跟BigKey经常并列出现的一个问题是热Key——某个哈希被大量请求集中读写,单节点的CPU被打满,延迟出现周期性毛刺。哈希场景下热Key最常见于"爆款商品的详情计数"这种一个key被所有用户打的情况。

缓解手段大致有三类:

  • 本地缓存:在应用层用Caffeine等本地缓存把高频读的字段缓存一份,降低打向Redis的量;
  • 读写分离/分片:把读请求分散到多个从节点;
  • 热Key本身的拆分:把一个"所有用户都在读"的哈希key拆成多份副本,比如hot-goods-1、hot-goods-2、hot-goods-3,读请求随机访问其中一个副本,写请求同时更新所有副本。

第三种手段看起来笨,但实测效果立竿见影。需要注意保证各个副本的数据一致性,最简单的策略是写操作利用pipeline同时写多个副本,读一方随机挑一个副本读。这是典型的"用一致性复杂度和CPU换延迟稳定性"的方案,适合对实时性要求极高的场景。

5.6 经验:哈希+JSON二次序列化的组合拳

最后分享一个我自己用下来很顺手的组合玩法:当遇到"字段需要扁平保存、但业务上又需要嵌套结构"的冲突时,不要死磕纯哈希,也不要无脑JSON。可以把复杂的子结构序列化成JSON字符串作为哈希的一个field,其余的扁平字段直接用哈希存储。比如用户信息:

HSET user:1001 name "张三" age 28 profile '{"tags":["vip","老用户"],"preferences":{"theme":"dark"}}'

这样既保住了哈希的字段级原子操作能力,又让复杂的嵌套数据有了安身之所。读取profile时反序列化一次即可,字段级更新依然高效。这个方案在实际项目里非常好用,属于性价比极高的折中设计。

6. 课后练习与扩展思考

不知道你们有没有发现,哈希虽然看似简单,但它把"数据结构设计"和"底层存储优化"两件事天然结合在了一起。学废哈希,等于同时理解了一种数据结构、一批命令的原子语义、一套内存编码机制。这份理解一旦建立,再去看列表、集合、有序集合的内部实现,会顺畅很多,因为它们在设计思路上有大量相似之处——紧凑编码、转换阈值、命令原子性,几乎是一套模板。

想加深理解的,我建议做三个练习:

  • 给自己项目的用户资料存储切换到哈希,对比一下key数量、内存占用、读写耗时的变化;
  • 用DEBUG OBJECT key命令,从返回信息里看serializedlength,评估哈希key在Redis内部实际占用的内存;
  • 逼自己手写一段并发自增的压测脚本,分别用"GET+SET"和"HINCRBY",观察一下服务端和客户端两种方案的差异。

做完这三件事,你对哈希的掌握就不是停留在"我知道它有field"这个表面层级,而是真正建立了"哈希该如何设计、何时该用、内存怎么表现"的全局认知。下一课我会继续沿着这个节奏,把Redis的列表(List)结构、以及它和流(Stream)的边界讲清楚。哈希的很多底层设计思想,在后面列表类型里还会再次遇到,所以这节课的底子,务必要打牢。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 22:46:13

网络故障排查实战:从物理层到应用层的五步定位法

1. 这不是教科书里的“数据通信与计算机网络”&#xff0c;而是我拆了三台路由器、重装七次系统后理出来的实操骨架“数据通信与计算机网络”这八个字&#xff0c;听起来像高校课程表上那个永远排在周三下午、教室空调总罢工的必修课。但如果你真在机房里蹲过通宵&#xff0c;在…

作者头像 李华
网站建设 2026/10/14 22:46:10

Linux内核伙伴系统原理与API详解:从页面分配到碎片排查

如果你写过内核模块&#xff0c;一定被页面分配失败折磨过。无论是kmalloc、dma_alloc_coherent还是mmap的后端&#xff0c;底层都绕不开 Linux 内核的“伙伴系统”&#xff08;Buddy System&#xff09;。每次调试时oom、page allocation failure或者Bad page state&#xff0…

作者头像 李华
网站建设 2026/10/14 22:45:40

编码器速度反馈怎么选?软件微分与硬件测量的原理、对比与实战

编码器速度反馈这件事&#xff0c;我在不同项目里翻过好几次车。最开始做伺服驱动器的时候&#xff0c;我图省事直接对位置做微分算速度&#xff0c;结果低速段抖得没法看&#xff0c;高速段又滞后严重。后来换成硬件定时器测频&#xff0c;噪声倒是小了&#xff0c;但分辨率又…

作者头像 李华
网站建设 2026/10/14 22:45:39

Web集群初始化配置全指南:从主机名到内核参数的基线搭建

接手一个 web 集群项目&#xff0c;第一步要做的事&#xff0c;往往不是写业务代码&#xff0c;也不是急着搭负载均衡和数据库&#xff0c;而是先把 6 台工作主机的初始化配置做完。这个环节看着基础&#xff0c;实际上决定了项目后续是“省心”还是“天天救火”。我这次负责的…

作者头像 李华
网站建设 2026/10/14 22:45:07

Chrome HTTP安全设置全解析:从强制HTTPS到证书警告

第一次看到Chrome设置页里那个“始终使用安全连接”的开关时&#xff0c;我其实没太当回事。直到有次在公共Wi-Fi下打开一个不认识的HTTP页面&#xff0c;旁边懂行的朋友指给我看&#xff1a;这个页面加载过程里&#xff0c;你的账号密码、会话Cookie全是明文在网络里传。从那之…

作者头像 李华
网站建设 2026/10/14 22:44:48

基于OpenCV+ImageAI+TensorFlow的动漫人物识别系统实践

简介&#xff1a;这份资源是一套基于深度学习的智能动漫人物识别系统完整实现包&#xff0c;面向对OpenCV图像处理、TensorFlow模型训练及ImageAI框架感兴趣的开发者、学生或算法入门者。项目从数据采集到Web应用落地均有覆盖&#xff1a;通过Selenium爬虫批量获取图片&#xf…

作者头像 李华