2026/4/20 12:31:12
网站建设
项目流程
免费建立自己的网站空间,淘宝联盟建微网站,室内设计好不好学,90自己做网站视频看了几百小时还迷糊#xff1f;关注我#xff0c;几分钟让你秒懂#xff01; 你是否曾好奇#xff1a;当你在 Java 代码中调用 redisTemplate.opsForValue().set(user:1001, 张三) 时#xff0c;Redis 内部到底发生了什么#xff1f; 一条数…视频看了几百小时还迷糊关注我几分钟让你秒懂你是否曾好奇当你在 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 2RESP 协议编码客户端 → 网络Redis 使用RESPRedis 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 服务端默认端口 6379Redis 使用I/O 多路复用epoll/kqueue监听连接非阻塞接收数据数据进入 Redis 的输入缓冲区querybuf 注意若网络延迟高或带宽不足会影响写入性能。建议 Redis 与应用部署在同一内网。五、Step 4服务端处理Event Loop 命令分发Redis 是单线程事件驱动模型事件循环检测到 socket 可读读取数据到client-querybuf解析命令按 RESP 格式拆分为argv[]数组argv[0] SET argv[1] user:1001 argv[2] 张三 argv[3] EX argv[4] 1800查找命令表找到setCommand函数指针调用setCommand(client)执行✅关键点整个过程在主线程完成无锁无上下文切换。六、Step 5内存存储核心这是最核心的一步如何把 key-value 存入内存6.1 创建 Redis 对象redisObjectRedis 为每个 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 特征底层编码说明是整数如 123int直接用 long 存储字符串 ≤ 44 字节embstr一次性分配 redisObject SDS字符串 44 字节rawredisObject 和 SDS 分开分配为什么是 44 字节RedisObject (16B) SDS header (8B) 字符串 \0 ≤ 64B内存分配器最小单元避免内存碎片。6.3 使用 SDS 存储字符串Redis 不用 C 原生字符串而是SDSSimple Dynamic Stringstruct 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 会异步记录写操作方式 1RDB快照不立即触发由配置的save规则决定如 900 秒 1 次修改主进程 fork 子进程子进程将内存数据写入.rdb文件方式 2AOF追加日志将命令追加到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 没关系”❌ 会导致内存泄漏务必设置合理过期时间十、总结一条数据的旅程客户端构造 SET 命令编码为 RESP网络TCP 传输到 Redis 服务端服务端事件循环接收 → 解析命令 → 执行 setCommand内存创建 redisObject根据大小选择 int/embstr/raw 编码用 SDS 存储字符串插入全局哈希表持久化异步记录到 AOF 或等待 RDB 快照核心思想Redis 的快源于内存操作 单线程无锁 精细内存管理。结语下次当你调用redisTemplate.set()时不妨想象一下此刻Redis 正在用 SDS、redisObject、哈希表为你精心安放这条数据而这一切都在微秒级完成理解这个过程你不仅能写出更高效的代码还能在面试中惊艳面试官“我知道 Redis 为什么用 embstr……”视频看了几百小时还迷糊关注我几分钟让你秒懂