news 2026/6/9 21:35:07

保存一条数据到 Redis 的全过程:从客户端到内存存储,深入底层细节(附 Spring Boot 实战)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
保存一条数据到 Redis 的全过程:从客户端到内存存储,深入底层细节(附 Spring Boot 实战)

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

你是否曾好奇:当你在 Java 代码中调用redisTemplate.opsForValue().set("user:1001", "张三")时,Redis 内部到底发生了什么?
一条数据是如何从你的 Spring Boot 应用,穿越网络,最终安全地存入 Redis 内存中的?

本文将带你逐层拆解这个过程,涵盖客户端、网络协议、服务端处理、内存结构、持久化等关键环节,并结合反例与注意事项,让你真正理解 Redis 的写入机制。


一、整体流程概览

graph LR A[Spring Boot 应用] -->|1. 构造命令| B(Lettuce/Jedis 客户端) B -->|2. RESP 协议编码| C[网络传输 TCP] C -->|3. Redis 服务端接收| D[事件循环 Event Loop] D -->|4. 命令解析| E[执行 SET 命令] E -->|5. 内存分配| F[SDS + Redis Object] F -->|6. 可选持久化| G[RDB/AOF]

下面,我们一步步深入。


二、Step 1:客户端构造命令(Spring Boot 层)

✅ 正确写法:

@Autowired private StringRedisTemplate redisTemplate; public void saveUser(Long id, String name) { // key = "user:1001", value = "张三" redisTemplate.opsForValue().set("user:" + id, name, 30, TimeUnit.MINUTES); }

🔍 背后发生了什么?

  • StringRedisTemplate使用Lettuce(默认)或 Jedis 客户端
  • 将 Java 对象转换为 Redis 命令:SET user:1001 张三 EX 1800
  • 自动序列化:String 类型无需额外序列化器

❌ 反例:存储大对象不设 TTL

// 错误:未设置过期时间,导致内存泄漏! redisTemplate.opsForValue().set("user:full:info:1001", hugeJsonString);

⚠️ 后果:key 永久存在,内存持续增长,最终 OOM。


三、Step 2:RESP 协议编码(客户端 → 网络)

Redis 使用RESP(Redis Serialization Protocol)作为通信协议,简单高效。

示例:SET user:1001 张三 EX 1800的 RESP 编码

*5 $3 SET $10 user:1001 $6 张三 $2 EX $4 1800
  • *5:表示有 5 个参数
  • $3:下一个字符串长度为 3(即 "SET")
  • 所有数据以\r\n结尾

优势:文本协议,易解析;二进制安全(支持任意字节)


四、Step 3:网络传输(TCP 连接)

  • 客户端通过TCP 连接将 RESP 数据包发送到 Redis 服务端(默认端口 6379)
  • Redis 使用I/O 多路复用(epoll/kqueue)监听连接,非阻塞接收数据
  • 数据进入 Redis 的输入缓冲区(querybuf)

📌 注意:若网络延迟高或带宽不足,会影响写入性能。建议 Redis 与应用部署在同一内网。


五、Step 4:服务端处理(Event Loop + 命令分发)

Redis 是单线程事件驱动模型

  1. 事件循环检测到 socket 可读
  2. 读取数据到client->querybuf
  3. 解析命令:按 RESP 格式拆分为argv[]数组
    argv[0] = "SET" argv[1] = "user:1001" argv[2] = "张三" argv[3] = "EX" argv[4] = "1800"
  4. 查找命令表,找到setCommand函数指针
  5. 调用setCommand(client)执行

关键点:整个过程在主线程完成,无锁,无上下文切换。


六、Step 5:内存存储(核心!)

这是最核心的一步:如何把 key-value 存入内存?

6.1 创建 Redis 对象(redisObject)

Redis 为每个 value 包装一个redisObject,包含类型、编码、引用计数等:

typedef struct redisObject { unsigned type:4; // OBJ_STRING unsigned encoding:4; // OBJ_ENCODING_EMBSTR 或 RAW int refcount; // 引用计数 void *ptr; // 指向实际数据 } robj;

6.2 选择底层编码(Encoding)

根据 value 大小,自动选择最优结构:

value 特征底层编码说明
是整数(如 "123")int直接用 long 存储
字符串 ≤ 44 字节embstr一次性分配 redisObject + SDS
字符串 > 44 字节rawredisObject 和 SDS 分开分配

💡为什么是 44 字节?
RedisObject (16B) + SDS header (8B) + 字符串 + '\0' ≤ 64B(内存分配器最小单元),避免内存碎片。

6.3 使用 SDS 存储字符串

Redis 不用 C 原生字符串,而是SDS(Simple Dynamic String)

struct sdshdr8 { uint8_t len; // 已用长度 uint8_t alloc; // 总分配长度 unsigned char flags; // 类型标识 char buf[]; // 实际字符数组 };

优势

  • O(1) 获取长度
  • 杜绝缓冲区溢出
  • 二进制安全(可存图片、序列化对象)

6.4 插入全局哈希表

Redis 将 key-value 存入全局字典(dict),本质是哈希表 + 链地址法

dictEntry *entry = dictAddRaw(db->dict, key, NULL); entry->v.val = val; // val 是 redisObject 指针

📌注意:Redis 会在哈希冲突严重时自动 rehash(渐进式,不阻塞)。


七、Step 6:持久化(可选,异步)

如果开启了持久化,Redis 会异步记录写操作

方式 1:RDB(快照)

  • 不立即触发,由配置的save规则决定(如 900 秒 1 次修改)
  • 主进程 fork 子进程,子进程将内存数据写入.rdb文件

方式 2:AOF(追加日志)

  • 将命令追加到aof_buf缓冲区
  • 根据appendfsync策略(always/everysec/no)刷盘
  • 不影响主线程性能(刷盘由后台线程或 OS 负责)

重要:持久化不会阻塞 SET 命令返回!客户端在步骤 5 完成就收到 OK。


八、完整 Spring Boot 实战示例

@Service public class UserService { @Autowired private StringRedisTemplate redisTemplate; // 保存用户信息(带过期时间) public void saveUser(User user) { String key = "user:" + user.getId(); String value = JSON.toJSONString(user); // 转为 JSON // 自动选择 embstr 或 raw 编码 redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES); // 验证存储编码(开发环境可用) // redis-cli> OBJECT ENCODING user:1001 } }

如何验证底层编码?

# 连接 Redis redis-cli # 查看 key 的编码 127.0.0.1:6379> SET small "hello" OK 127.0.0.1:6379> OBJECT ENCODING small "embstr" 127.0.0.1:6379> SET large "这是一个超过44字节的字符串,用于测试raw编码" OK 127.0.0.1:6379> OBJECT ENCODING large "raw"

九、常见误区与注意事项

误区正确认知
“SET 命令会立刻写磁盘”❌ 持久化是异步的,SET 返回时只保证内存写入
“Redis 用 HashMap 存数据”❌ 用的是自研 dict(哈希表 + 渐进 rehash)
“字符串都用 embstr”❌ >44 字节会转为 raw,内存开销更大
“不设 TTL 没关系”❌ 会导致内存泄漏,务必设置合理过期时间

十、总结:一条数据的旅程

  1. 客户端:构造 SET 命令,编码为 RESP
  2. 网络:TCP 传输到 Redis 服务端
  3. 服务端:事件循环接收 → 解析命令 → 执行 setCommand
  4. 内存
    • 创建 redisObject
    • 根据大小选择 int/embstr/raw 编码
    • 用 SDS 存储字符串
    • 插入全局哈希表
  5. 持久化(异步):记录到 AOF 或等待 RDB 快照

💡核心思想:Redis 的快,源于内存操作 + 单线程无锁 + 精细内存管理


结语

下次当你调用redisTemplate.set()时,不妨想象一下:此刻,Redis 正在用 SDS、redisObject、哈希表为你精心安放这条数据,而这一切,都在微秒级完成!

理解这个过程,你不仅能写出更高效的代码,还能在面试中惊艳面试官:“我知道 Redis 为什么用 embstr……”

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

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

CI/CD 工具怎么选?Jenkins vs Tekton vs Arbess 全方位对比测评

面对众多的CI/CD工具,如何根据功能、价格和易用性做出选择?本文旨在通过多款工具的横向对比,为你提供清晰的梳理与参考。1、Jenkins 1.1 产品介绍Jenkins 作为开源CI/CD领域的领导者,支持超过 1000 个插件,覆盖构建、部…

作者头像 李华
网站建设 2026/6/9 16:13:56

WD5030K,耐高压输出电流10A,WD5030A输出电流12A

产品概述WD5030K是高效率同步降压型DC/DC转换器,专为解决工业场景中宽电压输入、大电流输出及高可靠性需求而设计。其采用平均电流模式控制架构,结合抖动频率功能——通过随机调整开关频率降低电磁干扰(EMI),实现优异的…

作者头像 李华
网站建设 2026/6/9 16:11:44

AI时代,测试工程师如何转型产品经理?

一、引言:AI时代的转型机遇与测试工程师的独特优势 在人工智能技术迅猛发展的背景下,产品经理角色正经历深刻变革,AI产品经理成为行业新风口。对于软件测试从业者而言,转型并非遥不可及——测试工作中积累的系统性思维、细节把控…

作者头像 李华
网站建设 2026/6/9 16:11:45

从功能测试到AI淘金:一个测试工程师的副业觉醒

心数据:2025年全球AI测试工具市场规模突破$7.8亿(Gartner),而中国软件测试从业者平均薪资仅1.8万元/月(智联招聘) 一、主业困局:测试工程师的职场天花板 技术代际断层 graph LR A[手工测试]--&g…

作者头像 李华
网站建设 2026/6/9 16:15:04

救命神器9个一键生成论文工具,继续教育学生轻松搞定论文!

救命神器9个一键生成论文工具,继续教育学生轻松搞定论文! AI 工具如何成为论文写作的得力助手 在当前继续教育学生面临论文写作压力日益增大的背景下,AI 工具逐渐成为不可或缺的辅助工具。这些工具不仅能够帮助用户快速生成内容,还…

作者头像 李华