Redis 终极实战宝典:Hash 存数据像对象,List 队列秒级响应,性能优化黑科技全解析!
文章目录
- **`本篇摘要`**
- Redis之哈希(Hash)
- Redis之列表(List)
- List常见指令
- 1. **LPUSH key value1 [value2 ...]**
- 2. **RPUSH key value1 [value2 ...]**
- 3. **LPOP key [count](目前的redis5不支持count版本)**
- 4. **RPOP key [count](目前的redis5不支持count版本)**
- 5. **BLPOP key1 [key2 ...] timeout**
- 6. **BRPOP key1 [key2 ...] timeout**
- 7. **LLEN key**
- 8. **LINDEX key index**
- 9. **LRANGE key start stop(双闭区间)**
- 10.LINSERT
- 11. **LPUSHX key value**
- 12. **RPUSHX key value**
- 13. **LREM key count value(L表示list,故无rrem)**
- 14. **LTRIM key start stop**
- 15.LSET
- 基于指令的小结
- List常见指令
- List内部编码
- List应用场景
- **本篇小结**

本篇摘要
本文详解Redis的Hash和List结构,包括操作指令、内部编码、应用场景(如消息队列、微博Timeline)及优化策略(如ziplist与hashtable转换、pipeline批量操作)。
Redis之哈希(Hash)
Redis哈希(Hash)操作指令
注:这里对应的是key-hash结构,而hash里面又对应的是field-value结构,也就是插入的后面就当成pair就行。
1. 基础键值操作
1.HSET
- 功能:为哈希表中的一个或多个字段设置值。
- 时间复杂度:O(1)(每个字段操作)
示例:
HSET user:1000 name "Alice" age 30 email "[email protected]" 
- 注意事项:
- 如果字段已存在,值会被覆盖。
- 支持一次性设置多个字段值(Redis 4.0+)。
2.HGET
- 功能:获取哈希表中指定字段的值。
- 时间复杂度:O(1)
示例:
HGET user:1000 name 
- 注意事项:
- 如果字段不存在,返回
nil。
- 如果字段不存在,返回
HDEL
- 功能:删除哈希表中的一个或多个字段。
- 时间复杂度:O(N)(N为删除的字段数量)
示例:
HDEL user:1000 age 发现对应的年龄没了:

- 返回值:实际删除的字段数量。
4.HEXISTS
- 功能:检查哈希表中是否存在指定字段。
- 时间复杂度:O(1)
示例:
HEXISTS user:1000 name 
HSETNX
- 功能:仅当字段不存在时设置值(原子操作)。
- 时间复杂度:O(1)
示例:
HSETNX user:1000 gender "female" 
- 返回值:1(设置成功)或 0(字段已存在)。
2. 批量操作
HMSET(已弃用)
- 功能:批量设置多个字段值(Redis 4.0+ 推荐用
HSET替代)。 - 注意:与
HSET功能相同,但HSET更通用。
示例:
HMSET user:1000 name "Alice" age 30 2.HMGET
- 功能:批量获取多个字段的值。
- 时间复杂度:O(N)(N为请求的字段数量)
示例:
HMGET user:1000 name email 

- 返回值:按请求顺序返回值的列表,不存在字段返回
nil。
HGETALL
- 功能:获取哈希表中所有字段和值(交替返回字段名和值)。
- 时间复杂度:O(N)(N为哈希表大小)
示例:
HGETALL user:1000 
- 注意事项:
- 大数据量时可能阻塞 Redis,建议用
HSCAN替代。
- 大数据量时可能阻塞 Redis,建议用
3. 键值列表与统计
HKEYS
- 功能:获取哈希表中所有字段名(field)。
- 时间复杂度:O(N)(N为哈希表大小)
示例:
HKEYS user:1000 # 返回 ["name", "age", "email"] 
HVALS
- 功能:获取哈希表中所有字段值(value)。
- 时间复杂度:O(N)
示例:
HVALS user:1000 
HLEN
- 功能:返回哈希表中字段的数量。
- 时间复杂度:O(1)
示例:
HLEN user:1000 # 返回 3 
. HSTRLEN
- 功能:返回字段值的字符串长度(非字节长度)。
- 时间复杂度:O(1)
示例:
HSTRLEN user:1000 name 
4. 数值操作
HINCRBY
- 功能:将字段的整数值增加指定增量(支持负数)。
- 时间复杂度:O(1)
示例:
hincrby key1 1 2 

- 注意事项:
- 字段值必须是整数,否则返回错误。
HINCRBYFLOAT
- 功能:将字段的浮点数值增加指定增量。
- 时间复杂度:O(1)
示例:
hincrbyfloat key1 1 1.5 
- 返回值:操作后的新值(科学计数法可能被转换为字符串)。
5. 高级遍历
HSCAN- 功能:增量式迭代哈希表中的字段(解决
HGETALL阻塞问题)。 - 时间复杂度:每次调用 O(1),完整迭代 O(N)。
- 示例:
- 功能:增量式迭代哈希表中的字段(解决
redis hscan user:1000 0 match n* 
- 参数说明:
0:起始游标(0 表示新迭代)。MATCH:可选模式匹配(如n*)。COUNT:可选返回数量(默认 10)。
- 返回值:
[新游标, [字段1, 值1, 字段2, 值2, ...]]。
应用场景与最佳实践
- 大数据量优化:
- 避免
HGETALL,改用HSCAN分批次获取。 - 对频繁更新的字段使用
HINCRBY。
- 避免
动态字段管理:
HSET user:1000 settings:dark_mode true 计数器聚合:
HINCRBY stats:2023 page_views 1 对象存储:
HSET product:100 name "Laptop" price 999 stock 50 常见问题
HSET和HMSET的区别?- 功能相同,但
HSET自 Redis 4.0 起支持多字段操作,HMSET已弃用。
- 功能相同,但
- 如何高效获取大哈希表的所有字段?
- 使用
HSCAN分批次迭代,避免阻塞 Redis。
- 使用
HINCRBYFLOAT的精度问题?- Redis 内部使用 IEEE 754 浮点数,可能存在精度损失,建议客户端处理舍入。
这张技术笔记围绕 Redis 数据存储、序列化、Hash 结构、数据编码与优化 等核心内容展开,以下是分条总结的要点:
Redis 序列化与数据编码
- 序列化本质:把对象/数据转化为字节流(如 JSON、Protobuf 等格式),方便存储或传输;反序列化则是字节流转回对象。
- 序列化选择考量:
- 不同场景选不同序列化方式(如性能优先选 Protobuf,可读性优先选 JSON)。
- 关注序列化后的体积大小(影响存储/网络开销)、兼容性(多语言/版本间解析)、性能(序列化/反序列化速度)。
- Redis 中的编码:
- Redis 底层对数据类型(如 String、Hash、List 等)有多种编码实现(如 String 可用 raw/int 编码)。
- 不同编码的选择由 Redis 自动优化(如小整数用 int 编码,短字符串用 raw 编码),也可手动指定(如
hash-max-ziplist-entries控制 Hash 用 ziplist 还是 hashtable)。
Hash 结构的应用与优化
- Hash 适用场景:存储“对象 - 属性”类数据(如用户信息:
uid → {name, age, city}),比 String 存 JSON 更节省内存(尤其字段多但单个值小时)。 - Hash 编码优化:

- 小 Hash(字段数少、值小如:对应的512/64)用 ziplist 编码(紧凑存储,节省内存)。
- 当字段数/值大小超过阈值(由
hash-max-ziplist-entries/hash-max-ziplist-value控制),自动转为 hashtable 编码(查询效率更高,但内存开销大)。
为什么储存对应用户信息不选择String而选择Hash呢?
首先两者都能存储,但是Hash更有优势:
- 当用String存储,如果要修改某个量就需要把对应的整个json串拿到进行解析在进行修改再放回去,而如果是hash直接通过对应field修改即可如:

- hash的选择真正做到了高内聚低耦合。
- 也就是进行查找某个值的时候不用完全遍历就能做到(比如文件找bug,bug很集中)+修改某某个值的时候无需整体进行修改(比如改bug,发现改一个位置,其他地方不会受影响)。
数据存储的“权衡”与优化思路
- 空间 vs 性能的权衡:
- 若追求极致内存,可牺牲部分读写性能(如用 ziplist 编码、压缩序列化)。
- 若追求极致读写,可牺牲部分内存(如用 hashtable 编码、快速序列化)。
- 业务场景驱动选择:
- 冷数据(访问少)→ 优先压缩/省内存;热数据(访问多)→ 优先高性能。
- 单 key 访问多 → 优化单 key 操作;批量访问多 → 用 pipeline 或批量命令。
Redis之列表(List)
上文Hash缺点缺点
需控制哈希在ziplist和hashtable两种内部编码的转换,可能造成较大内存消耗。
List列表

- 定义与存储:用于存储多个有序字符串,每个字符串为元素,一个列表最多存储2³² - 1个元素。
- 操作:可在列表两端进行插入(push)和弹出(pop)操作,还能获取指定范围元素列表、指定索引下标的元素等。
- 特性与应用:是较灵活的数据结构,可充当栈和队列角色,在实际开发中有诸多应用场景。
注意:list 内部的结果(编码方式)并非是一个简单的数组,而是更接近于 “双端队列” (deque),而且是有序的,是按照一定顺序排好的,每次取出都是有序并不一定是升序或降序。
List常见指令
注:这里不仅是list类型,对应非list也是,必须保证里面的值类型相同,比如插入了list一个hash类型,以后只能插入hash类型否则报错!
1. LPUSH key value1 [value2 …]
- 功能:将一个或多个值从列表的左侧(前端/头部)插入,新元素会排在原有元素之前。
- 时间复杂度:O(1)(单个元素插入时),批量插入时为 O(N)(N 为插入元素数量)。
- 示例:
LPUSH tasks "task1" "task2"→ 在 tasks 列表头部依次插入 task1 和 task2,返回插入后列表的总长度(如 2)。

- 其他要点:支持一次性插入多个值;常用于实现栈的“入栈”操作(后进先出)或优先级队列的头部插入。
2. RPUSH key value1 [value2 …]
- 功能:将一个或多个值从列表的右侧(后端/尾部)插入,新元素会排在原有元素之后。
- 时间复杂度:O(1)(单个元素插入时),批量插入时为 O(N)。
- 示例:
RPUSH messages "hello" "world"→ 在 messages 列表尾部依次追加 hello 和 world,返回插入后列表的长度。

- 其他要点:常用于实现队列的“入队”操作(先进先出);与 LPUSH 对应,操作方向相反。
3. LPOP key [count](目前的redis5不支持count版本)
- 功能:从列表的左侧(头部)弹出(移除并返回)一个或多个元素;若不指定 count 则默认弹出 1 个,指定 count 时可批量弹出多个元素(如
LPOP tasks 2返回并移除前两个元素)。 - 时间复杂度:O(1)(单个元素弹出时),批量弹出时为 O(N)。
- 示例:
LPOP tasks→ 返回并移除列表最左侧的第一个元素(如 “task1”);LPOP tasks 2→ 返回并移除前两个元素(数组形式)。


- 其他要点:常用于实现队列的“出队”操作(头部取出);若列表为空则返回 nil。
4. RPOP key [count](目前的redis5不支持count版本)
- 功能:从列表的右侧(尾部)弹出(移除并返回)一个或多个元素;支持批量弹出(如
RPOP messages 3返回并移除最后三个元素)。 - 时间复杂度:O(1)(单个元素弹出时),批量弹出时为 O(N)。
- 示例:
RPOP messages→ 返回并移除列表最右侧的最后一个元素(如 “world”);RPOP messages 3→ 返回并移除最后三个元素。


- 其他要点:常用于栈的“出栈”操作(尾部取出)或消息队列的消费端;若列表为空则返回 nil。
5. BLPOP key1 [key2 …] timeout
- 功能:阻塞式地从多个 List 中左侧弹出元素:若所有 List 当前均为空,则客户端会阻塞等待,直到某个 List 有新元素插入或达到超时时间(timeout 秒)。
- 时间复杂度:O(1)(当有元素可弹出时),阻塞期间不占用 CPU 资源。
- 示例:
BLPOP tasks 5→ 最多等待 5秒,若 tasks 列表在此期间有新元素,则弹出最左侧元素并返回 [key, value];超时则返回 nil。


如果要是里面没有数据,而且是多个list,那么就返回第一个预约的list:

正在阻塞。

返回对应pair以及延迟等待时间。
- 其他要点:是实现阻塞队列的核心指令(如生产者-消费者模型),确保消费者在没有数据时等待而非空轮询;常用于任务调度场景。
6. BRPOP key1 [key2 …] timeout
- 功能:与 BLPOP 类似,但从多个 List 的右侧弹出元素(尾部操作),同样支持阻塞等待和超时机制。
- 时间复杂度:O(1)(当有元素可弹出时)。
- 示例:
BRPOP messages 5→ 最多等待 5 秒,从 messages 列表右侧弹出最后一个元素;超时则返回 nil。 - 其他要点:适合需要从尾部消费的场景(如特定顺序的任务处理)。
(brpop与blpop)需要注意:
- 阻塞版本 Redis 会根据 timeout 阻塞一段时间,期间可执行其他命令。
- 使用 brpop 和 blpop 可显式设置阻塞时间,并非无休止等待。
- 命令设多个键时,从左到右遍历,有键对应列表能弹出元素则立即返回。
- blpop 和 brpop 可同时尝试获取多个 key 列表元素,多个 list 中哪个有元素就返回哪个。
- 多个客户端同时对一个键执行 pop,最先执行命令的客户端得到弹出元素。
- blpop 和 brpop 看似耗时久,实际对 redis 服务器无负面影响。
- 可指定一个或多个 key,每个 key 对应一个 list。
- 若有非空 list,blpop 能获取元素并立即返回;若 list 都为空,阻塞等待其他客户端插入元素。
- 可指定超时时间,单位为秒,Redis 6 超时可设小数,Redis 5 超时需为整数。
10.除了这点用法其他和rpop,lpop一致。
7. LLEN key
- 功能:获取指定 List 的当前长度(元素个数)。
- 时间复杂度:O(1)。
- 示例:
LLEN tasks→ 返回 tasks 列表中的任务总数(如 3)。

- 其他要点:时间复杂度为 O(1),适合快速检查列表规模;图片中可能通过红色注释提示“高效查询,无需遍历”。
8. LINDEX key index
- 功能:获取列表中指定索引位置(index)的元素值;索引从 0 开始(0 表示第一个元素),负数表示从右侧倒数(如 -1 为最后一个元素,-2 为倒数第二个)。
- 时间复杂度:O(1)。
- 其他要点:若索引越界则返回 nil;Redis 对越界索引不严格报错(相比 C++/Java),支持灵活的正负索引访问。
示例:LINDEX tasks 1 → 返回第一个任务(如 “t3”);LINDEX tasks -1 → 返回最后一个任务。

9. LRANGE key start stop(双闭区间)
- 功能:获取列表中指定范围(start 到 stop)内的所有元素;支持正数索引(从左到右)和负数索引(从右到左),包含 start 和 stop 位置的元素。
- 时间复杂度:O(S)(S 为返回的元素数量)。
- 其他要点:常用于分页查询或获取列表的局部数据(如最新 N 条记录);返回值为元素值的数组。
示例:LRANGE tasks 0 2 → 返回第 1~3 个元素;LRANGE tasks -3 -1 → 返回倒数第 3 个到最后一个元素。

这里没有检查越界机制(鲁棒性强大)。
10.LINSERT
- 功能:用于在列表的元素前或者后插入元素。当指定元素有多个时,会在第一个匹配的元素前或后插入。
- 语法:
LINSERT key BEFORE|AFTER pivot value - 示例:若有列表
mylist包含元素a b c ....,执行LINSERT mylist BEFORE a h后,列表变为a new_element b c。


返回插入后的个数。

这里没有rinsert,然后就是插入的时候从左往右找第一个(如果有多个的话)。
- 应用场景:适用于需要在列表特定元素附近插入新元素的场景,比如维护一个有序的任务列表,在某个任务前或后插入新任务。
11. LPUSHX key value
- 功能:仅当指定的 key 已存在且为 List 类型时,才将单个值插入到列表的左侧头部;若 key 不存在或对应的 value 不是列表,则不执行任何操作,返回 0。
- 时间复杂度:O(1)。
- 示例:
LPUSHX mlist 2→ 若 mlist已存在且为 List,则插入 2 到头部并返回 (新长度);否则返回 0。

- 其他要点:适合需要确保操作对象已存在的场景(如避免误创建非 List 类型的 key)。
12. RPUSHX key value
- 功能:仅当 key 已存在且为 List 类型时,才将单个值插入到列表的右侧尾部;其他情况不执行操作,返回 0。
- 时间复杂度:O(1)。
- 示例:
RPUSHX logs "log_entry"→ 若 logs 已存在且为 List,则追加 log_entry 到尾部并返回新长度;否则返回 0。 - 其他要点:与 RPUSH 对应,用于尾部追加时的条件性操作,确保类型安全。
13. LREM key count value(L表示list,故无rrem)
- 功能:从列表中移除指定数量的等于 value 的元素;count 参数控制移除逻辑——正数表示从左到右移除前 count 个匹配值,负数表示从右到左移除后 |count| 个匹配值,0 表示移除所有匹配值。
- 时间复杂度:O(N)(N 为列表长度)。
- 示例:
LREM tasks 2 "task1"→ 移除左侧前 2 个值为 task1 的元素;LREM tasks 0 "old"→ 移除所有值为 old 的元素。
左右移除效果:

全部移除效果:

- 其他要点:返回值为实际被移除的元素个数;适合清理旧数据或重复项。
14. LTRIM key start stop
- 功能:对列表进行修剪(保留指定范围内的元素,删除范围外的其他元素);保留 start 到 stop 之间的元素(包含两端),超出范围的元素被删除。
- 时间复杂度:O(N)(N 为被删除的元素数量)。
- 示例:
LTRIM tasks 0 5→ 只保留前 6 个元素(索引 0~5),其余全部删除;常用于限制列表长度(如只保存最新的 100 条记录)。

- 其他要点:返回值为操作成功标识(如 OK);适合控制列表规模,避免内存过度占用。
15.LSET
- 功能:通过索引来设置列表中指定位置的元素。
- 语法:
LSET key index value - 示例:对于列表
tasks包含元素6个task1,执行LSET tasks 0 1后,列表变为1 与5个tasks。

- 应用场景:当需要直接修改列表中特定位置的元素时会用到,例如在存储用户信息的列表中,修改某个用户的特定信息字段。
基于指令的小结

Redis中的list是一个双端队列(dequeue)。从list的两头插入或删除元素效率非常高,时间复杂度为O(1)。搭配使用rpush(从列表右端插入元素)和lpop(从列表左端删除并返回元素),其功能相当于队列。搭配使用rpush和rpop(从列表右端删除并返回元素),其功能相当于栈。
List内部编码
最新版本的就是以QuickList存储,然后里面多个ziplist连接起来,整体为Quicklist。

如下:

List应用场景
利用数组特性存储学生信息
如:

比如这里每个学生就是对应hash,而班级+学生就构成list:
创建对应hash:

插入对应班级list并进行检查:

做消息队列使用
Redis可以使⽤lpush+brpop命令组合实现经典的阻塞式⽣产者-消费者模型队列,⽣产者客⼾端使⽤lpush从列表左侧插⼊元素,多个消费者客⼾端使⽤brpop命令阻塞式地从队列中"争抢"队⾸元素。通过多个客⼾端来保证消费的负载均衡和⾼可⽤性。

- 阻塞式弹出
BRPOP是阻塞操作,当列表为空时,消费者会等待新元素到达。
- 轮询式消费
- 只有一个消费者能获取元素,遵循“先执行
BRPOP先获取”的规则。 - 消费者按
BRPOP执行顺序依次获取新元素(如 1→2→3)。
- 只有一个消费者能获取元素,遵循“先执行
- 消费流程
- 消费者1 执行
BRPOP→ 拿到新元素 → 命令结束。 - 若想继续消费,需重新执行
BRPOP,否则不参与下一轮竞争。 - 新元素到达后,由下一个执行
BRPOP的消费者(如消费者2)获取。
- 消费者1 执行
- 适用场景
- 实现多消费者公平轮询的任务队列(如订单分发、消息处理)。
- 避免单个消费者独占资源,确保任务均衡分配。
分频道阻塞消息队列
Redis同样使⽤push+brpop命令,但通过不同的键模拟频道的概念,不同的消费者可以通过brpop不同的键值。

- 在消息队列里设置多个通道(比如 Redis 的 List 或 Stream),每个通道专门传一类数据。就像抖音,用不同通道分别传短视频、弹幕、点赞这些数据。
- 比如弹幕通道出问题,不影响视频和点赞通道。像抖音里弹幕加载不出来,视频还能正常播,实现解耦合。
微博获取blog使用
1. 微博存储(哈希结构)
- 存储字段:
title(标题)、timestamp(发布时间)、content(内容)。
每篇微博使用 Hash 存储,如:
HMSET mblog:1 title "xx" timestamp 1476536196 content "xxxxx" 2. Timeline 构建(列表结构)
- 最新微博在列表头部(
LPUSH保证时间倒序)。
用户 Timeline 使用 List 存储微博ID,如:
LPUSH user:1:mblogs mblog:1 mblog:3 3. 分页获取微博
- 步骤2:遍历ID,用
HGETALL获取每条微博详情。
步骤1:用 LRANGE 获取微博ID列表(如第1页前10条):
LRANGE user:1:mblogs 0 9 解释下:
- 用户名字作为key,以list形式连接内容。
- 如果每次分页获取博客内容特别多,也就是需要多次向服务器发送请求,这样效率低,因此可以利用pipeline技术进行封装成一个网络请求。
- 但是避免不了如果当某一页内容正好索引落在了list中部,正着和倒着找都不方便,因此可以考虑进行多个list储存,精确判断最后落在哪个list,然后进行返回。
本篇小结
学习本篇,我们最终可以知道Hash适合存储对象属性,List可实现栈、队列及分页查询。通过合理选择数据结构和编码(如ziplist),能显著提升Redis性能和内存效率。