Redis数据结构深度解析:从String到Stream的奇幻之旅(一)
Redis系列文章
《半小时掌握Redis核心操作:从零开始的实战指南》-CSDN博客
Redis数据结构深度解析:从String到Stream的奇幻之旅(一)-CSDN博客
Redis数据结构深度解析:从String到Stream的奇幻之旅(二)-CSDN博客
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
Redis数据结构深度解析
Redis作为全球最受欢迎的内存数据库,其核心竞争力不仅在于高性能的读写能力,更在于其灵活多变的数据结构体系。从最基础的字符串(String)到革命性的消息流(Stream),Redis的数据结构如同瑞士军刀般,为开发者提供了应对各类场景的“武器库”。本文将带您深入探索这些数据结构的底层原理、设计哲学以及实战应用,揭开Redis的“奇幻之旅”。
一、String:轻量级存储的SDS魔法
Redis的String类型看似简单,实则一点也不容易。它基于SDS(Simple Dynamic String)结构实现,SDS不仅保存了实际字符串内容,还维护了字符串长度、已分配空间大小等元信息。完美解决了C语言字符串的天然缺陷,同时支持原子操作(如INCR/DECR),成为实现计数器、分布式锁等场景的首选。
核心优势:
- O(1)获取长度:通过len字段记录字符串长度,避免遍历计算。
- 二进制安全:无需依赖\0结尾,支持存储任意二进制数据。
- 动态扩容与惰性释放:
- 预分配:当字符串扩展时,按需分配额外空间(如扩展为原容量的1.5倍),减少频繁分配开销。
- 惰性释放:缩短字符串时保留未使用空间,供后续扩展复用。
内存优化编码策略:
| 编码类型 | 适用场景 | 存储方式 | 优势 |
|---|---|---|---|
| int | 纯整数字符串(如"10086") | 直接转为long类型存储,无需SDS结构 | 内存节省80%,适合计数器场景 |
| embstr | 字符串长度≤39字节(SDS+redisObject总长≤44B) | 将SDS结构与Redis对象头合并为连续内存块(避免内存碎片) | 高效小对象存储(如Session ID) |
| raw | 大字符串(如文件缓存) | 独立分配内存块,SDS结构与数据分离 | 支持超大容量,避免embstr限制 |
编码自动切换示例:
# 存储小整数(触发int编码)
SET counter 10086 # 内存占用约8字节(long类型) # 存储短字符串(触发embstr编码)
SET user:1001 "Alice" # SDS与对象头合并为连续内存 # 存储大文件(触发raw编码)
SET image:avatar "<base64 encoded data>" # 独立分配大内存块
原子操作与分布式场景
Redis的原子性特性使其非常适合用于实现计数器和分布式锁。通过INCR/DECR命令,可以轻松实现计数器功能。而SETNX结合EXPIRE则可以构建简单的分布式锁机制,确保在分布式环境下对资源的安全访问。
-
计数器(原子增减):
# 电商库存管理(原子性保证) INCR stock:product_1001 # 库存+1 DECRBY stock:product_1001 5 # 批量减库存(如秒杀场景) -
分布式锁(SETNX+EXPIRE):
# 实现30秒有效期的分布式锁 SET lock:order_1234 "locked" NX EX 30 # NX=仅当锁不存在时设置,EX=设置过期时间 # 释放锁(需确保线程安全) DEL lock:order_1234 -
值替换(GETSET):
# 原子地获取旧值并设置新值 GETSET cache:key "new_value" # 返回旧值并更新为"new_value"
实战场景:
-
缓存用户Session:
# 存储用户Session(键值对形式) SET session:1001 "{user_id:1001, username:'Alice', expire:1704537600}" EX 3600 # 获取并刷新过期时间 GET session:1001 EXPIRE session:1001 3600 -
小文件缓存(如配置文件):
# 缓存Nginx配置文件内容 SET config:nginx "<文件内容>" EX 86400 # 高性能读取避免磁盘IO GET config:nginx -
分布式唯一ID生成:
# 使用INCR生成自增ID INCR id:order # 每次返回递增的数值(如1001,1002,...) -
热点数据缓存(如排行榜Top1):
# 实时存储当前最高分 SET highscore:game_1001 999999
二、List:双链表与压缩包的智慧选择
List是链表结构,适用于实现队列和栈。LPUSH/RPUSH分别用于在列表头部或尾部添加元素,LPOP/RPOP用于移除并返回头/尾元素,完美模拟了栈和队列的操作。对于小型列表,Redis会自动使用ziplist以节省内存;而对于较大的列表,则转换为quicklist7。
两种底层实现:
-
ziplist(压缩列表):
将小数据量列表(默认≤8KB或≤512个元素)以紧凑的连续内存块存储。每个元素包含类型标记和长度字段,适合频繁读写的小列表。
优势:内存效率高,适合会话队列、排行榜等场景。 -
linkedlist(双向链表):
当列表超过阈值时自动切换为双向链表,支持无限长度,但内存占用更大。
优势:LPOP/RPOP操作复杂度为O(1),常用于消息队列(如订单处理流水线)。
核心特性:
- 有序性:元素按插入顺序排列,支持通过索引访问(
LRANGE)。 - 高效双端操作:
LPUSH/RPUSH(头/尾添加)、LPOP/RPOP(头/尾弹出)复杂度均为O(1)。 - 灵活编码:根据数据量自动选择ziplist或linkedlist,平衡内存与性能。
- 阻塞式弹出:
BLPOP/BRPOP支持阻塞等待消息,适用于消息队列。
实战案例(生产者-消费者模型)
利用Lists的阻塞式弹出操作(BRPOP/BLPOP),可以方便地实现生产者-消费者模式的消息队列,确保消息不会丢失且能够及时处理。
# 生产者:向订单队列发送消息
RPUSH mq:orders "order_1001"
RPUSH mq:orders "order_1002" # 消费者:阻塞式消费消息(等待最多5秒)
BRPOP mq:orders 5 # 若5秒内无消息返回空,否则返回消息
> 1) "mq:orders"
> 2) "order_1001" # 处理消息后移入完成队列
RPUSH mq:finished_orders "order_1001"
进阶技巧
-
动态调整编码阈值:
CONFIG SET list-max-ziplist-size 1024 # 扩大ziplist内存阈值 CONFIG SET list-max-ziplist-entries 1000 # 扩大ziplist元素数量阈值适用场景:当业务需要存储稍大规模的小元素列表时,可优化内存占用。
-
分页遍历列表:
LRANGE my_list 0 99 # 获取前100条 LRANGE my_list 100 199 # 获取下一页 -
原子性操作组合:
# 实现“如果列表长度≤1000,则添加新元素” EVAL "if redis.call('LLEN', KEYS[1]) < 1000 then return redis.call('RPUSH', KEYS[1], ARGV[1]) else return 0 end" 1 my_list "new_item" -
阻塞弹出与超时控制:
BRPOP mq:orders 10 # 最多等待10秒,超时返回空
三、Set:无序集合的高效管理
基本介绍
Redis的Set(集合类型) 是一种无序、不重复的字符串元素集合,支持快速的增删查改和集合运算(交集、并集、差集)。其底层通过两种编码实现:
- intset:当所有元素为整数且数量≤512时,使用紧凑的整数数组存储,元素按升序排列,内存效率高。
- hashtable:当元素类型混合(如字符串与整数共存)或数量超过阈值时,转为哈希表存储,键为元素值,值固定为
NULL。
核心特性:
- 自动去重:添加重复元素会被静默忽略。
- 高效集合运算:通过
SINTER(交集)、SUNION(并集)、SDIFF(差集)等命令实现复杂逻辑。 - 随机访问:通过
SRANDMEMBER可随机获取元素,适合抽奖、随机推荐场景。
内存优化策略
-
intset编码
- 实现原理:元素按升序存储在连续内存块中,支持快速查找和遍历。
- 优势:
- 查找、添加、删除复杂度均为O(1)(基于二分查找)。
- 内存占用极低,适合小规模整数集合。
- 适用场景:存储用户ID、订单编号等整数类型的唯一标识。
SADD user_ids 1001 1002 1003 # 存储用户ID集合 SCARD user_ids # 返回集合元素个数 -
hashtable编码
- 实现原理:基于哈希表实现,键为元素值,值固定为
NULL。 - 优势:
- 支持任意类型元素(字符串、浮点数等)。
- 可扩展性高,适应大规模数据存储。
- 适用场景:混合类型元素或超大集合(如百万级用户标签)。
- 实现原理:基于哈希表实现,键为元素值,值固定为
实战案例
# 用户A关注列表
SADD userA_followers 1001 1002 1003 1004 # 用户B关注列表
SADD userB_followers 1002 1003 1005 1006 # 计算共同好友(交集)
SINTER userA_followers userB_followers # 返回 [1002, 1003]
性能与优化技巧
-
避免大集合全量拉取:
使用SSCAN命令迭代遍历集合,避免因SMEMBERS返回大数据量导致内存溢出。SSCAN user_ids 0 COUNT 100 MATCH "user_*" # 分批获取匹配的元素 -
集合运算性能优化:
- 交集/并集/差集操作的时间复杂度与集合大小相关,建议先对小集合执行运算。
- 使用
SORTED SET替代复杂多集合运算(如需按权重排序)。
-
编码自动转换:
Redis会根据数据变化自动切换编码(如intset→hashtable),可通过OBJECT ENCODING命令查看当前编码类型:OBJECT ENCODING user_ids # 返回"intset"或"hashtable"
四、Hash:键值对的极致压缩
Hash类型是存储对象的天然选择,其内部实现了高效的空间利用率。每个字段都直接映射到哈希表中的一个位置,访问速度极快。底层支持两种编码:
-
ziplist编码:字段名+值总大小≤64KB且字段数≤512时,以压缩列表存储,内存效率提升50%以上。
示例:HSET user:1001 name "Alice" age 25。 -
hashtable编码:大对象自动切换为标准哈希表,支持快速增删改查。
两种编码性能对比:
| 场景 | ziplist(字段数≤512) | hashtable |
|---|---|---|
| 内存占用 | 更低 | 较高 |
| 单次操作速度 | 略慢(需遍历) | 更快 |
| 适用场景 | 小对象缓存 | 大对象存储 |
用户画像存储优化
# 传统String存储(需要序列化)
SET user:1001 "{'name':'Bob','age':28,'vip':true}"# Hash存储(支持字段级操作)
HSET user:1001 name "Bob" age 28 vip true
HINCRBY user:1001 age 1 # 原子更新年龄
存储效率对比:
| 操作 | String方式 | Hash方式 |
|---|---|---|
| 读取单个字段 | 需反序列化整个对象 | HGET直接获取 |
| 更新单个字段 | 全量覆盖 | 局部更新 |
| 网络传输量(1个字段) | 整个JSON字符串 | 单个字段值 |
五、Sorted Set:跳跃表的优雅平衡
Redis的Sorted Set(有序集合) 是一种无重复成员、按分数(score)排序的键值对集合。每个成员(member)关联一个唯一分数(score),通过跳跃表(SkipList) 和哈希表(Hash Table) 的双重结构实现高效操作。
- 跳跃表:按分数(score)排序,支持O(logN)的范围查询(如
ZRANGEBYSCORE)。 - 哈希表:实现O(1)的成员存在性判断(
ZSCORE)。
核心特性:
- 有序性:成员按分数从小到大自动排序,相同分数按插入顺序排列。
- 唯一性:成员唯一,重复添加时会更新分数并保持顺序。
- 高效范围查询:支持按分数范围(
ZRANGEBYSCORE)、排名范围(ZRANGE)快速获取数据。 - 原子操作:
ZADD/ZREM等命令保证操作原子性,适合高并发场景。
双结构协作示例
ZADD leaderboard 95 "Alice" 88 "Bob" # 插入成员时:
# 1. 哈希表记录"Alice"→指向跳跃表节点
# 2. 跳跃表按score=95插入并维护有序性
内存优化策略
Redis通过以下机制优化Sorted Set的内存使用:
- 跳跃表压缩:
- 跨度(span)字段:记录相邻节点间的距离,支持快速计算排名(如
ZRANK) - 层级自适应:根据数据量动态调整跳跃表层数,平衡查找效率与内存占用。
- 跨度(span)字段:记录相邻节点间的距离,支持快速计算排名(如
- 哈希表与跳跃表的分离存储:
- 成员与分数存储在跳跃表节点中,哈希表仅存储成员到节点的映射,避免重复存储。
- 小集合优化:
- 对于小规模Sorted Set,Redis可能使用ziplist编码(连续内存块)压缩存储,节省空间。
实战案例
案例1:游戏排行榜(实时TOP100)
# 添加用户得分
ZADD game_leaderboard 95000 "PlayerA" 88000 "PlayerB" # 获取TOP10玩家及分数
ZRANGE game_leaderboard 0 9 WITHSCORES
> 1) "PlayerB" 2) "88000" 3) "PlayerA" 4) "95000" # 动态更新得分(自动排序)
ZADD game_leaderboard 99000 "PlayerB" # PlayerB的分数更新为99000,排名上升
案例2:新闻流按时间倒序展示
# 用时间戳作为分数,确保新消息在前
ZADD news_feed 1704537600 "Article1" 1704537601 "Article2" # 获取最新10条新闻
ZRANGEBYSCORE news_feed +inf -inf LIMIT 0 10 REV
案例3:带过期时间的实时排行榜
# 设置排行榜3600秒后过期
EXPIRE game_leaderboard 3600 # 高频更新时确保原子性
ZADD game_leaderboard 99999 "PlayerC" INCR # 分数递增操作(类似计数器)
案例4:去重消息队列(结合Sorted Set与List)
# 新消息用时间戳作为分数,确保唯一
ZADD message_queue 1704537600 "msg_1001" # 定期清理过期消息
ZREMRANGEBYSCORE message_queue -inf 1704537600-3600 # 将消息推入List消费
BLPOP message_list 0
性能与优化技巧
-
范围查询优化:
- 使用
ZRANGEBYSCORE结合WITHSCORES和LIMIT减少数据传输量。 - 预先对分数排序,避免客户端排序。
- 使用
-
批量操作提升效率:
# 一次性添加多个成员 ZADD leaderboard 95 "Alice" 88 "Bob" 92 "Charlie" -
跳跃表层级监控:
OBJECT ENCODING leaderboard # 返回"ziplist"或"skiplist" -
分布式场景扩展:
- 使用
SORTED SET实现分布式计数器(如统计全球用户活跃度)。 - 结合
Lua脚本保证跨键操作的原子性。
- 使用
相关文章:
Redis数据结构深度解析:从String到Stream的奇幻之旅(一)
Redis系列文章 《半小时掌握Redis核心操作:从零开始的实战指南》-CSDN博客 Redis数据结构深度解析:从String到Stream的奇幻之旅(一)-CSDN博客 Redis数据结构深度解析:从String到Stream的奇幻之旅(二&…...
Linux - 网络基础(应用层,传输层)
一、应用层 1)发送接收流程 1. 发送文件 write 函数发送数据到 TCP 套接字时,内容不一定会立即通过网络发送出去。这是因为网络通信涉及多个层次的缓冲和处理,TCP 是一个面向连接的协议,它需要进行一定的排队、确认和重传等处理…...
【Linux-网络】从逻辑寻址到物理传输:解构IP协议与ARP协议的跨层协作
🎬 个人主页:谁在夜里看海. 📖 个人专栏:《C系列》《Linux系列》《算法系列》 ⛰️ 道阻且长,行则将至 目录 📚前言 📖 IP地址的组成 🔖IPv4 🔖IPv6 📚…...
解锁前端表单数据的秘密旅程:从后端到用户选择!✨
😄 解锁前端表单数据的秘密旅程:从后端到用户选择!✨ 嘿,技术爱好者们!👋 你有没有在开发中遇到过这样的困惑:表单里的数据(比如图片附件、识别点 ID)从哪儿来的&#x…...
Kotlin字符串操作在Android开发中的应用示例
Kotlin字符串操作在Android开发中的应用示例 引言 在Android开发中,Kotlin已经成为主流的编程语言,它提供了许多便捷的字符串操作功能。本文将结合一个具体的Kotlin示例程序,详细介绍Kotlin中字符串的创建、格式化和使用方法。 示例代码 以…...
【机械视觉】C#+visionPro联合编程———【一、C# + VisionPro 联合编程详解以及如何将visionPro工具加载到winform】
机械视觉与 C# VisionPro 联合编程详解 目录 机械视觉与 C# VisionPro 联合编程详解 概念 应用场景 1. 工业检测与质量控制缺陷检测 2. 定位与机器人引导 3. 识别与分类 4. 复杂流程控制 将visionPro工具加载到winform 环境准备 一、创建winform项目 二、打开窗体…...
迷你世界脚本自定义UI接口:Customui
自定义UI接口:Customui 彼得兔 更新时间: 2024-11-07 15:12:42 具体函数名及描述如下:(除前两个,其余的目前只能在UI编辑器内部的脚本使用) 序号 函数名 函数描述 1 openUIView(...) 打开一个UI界面(注意…...
江科大51单片机笔记【9】DS1302时钟可调时钟(下)
在写代码前,记得把上一节的跳线帽给插回去,不然LCD无法显示 一.DS1302时钟 1.编写DS1302.c文件 (1)重新对端口定义名字 sbit DS1302_SCLKP3^6; sbit DS1302_IOP3^4; sbit DS1302_CEP3^5;(2)初始化 因为…...
发行思考:全球热销榜的频繁变动
几点杂感: 1、单机游戏销量与在线人数的衰退是剧烈的,有明显的周期性,而在线游戏则稳定很多。 如去年的某明星游戏,最高200多万在线,如今在线人数是48名,3万多。 而近期热门的是MH,在线人数8…...
微信小程序接入deepseek
先上效果 话不多说,直接上代码(本人用的hbuilder Xuniapp) <template><view class"container"><!-- 聊天内容区域 --><scroll-view class"chat-list" scroll-y :scroll-top"scrollTop":…...
为解决局域网IP、DNS切换的Windows BAT脚本
一、背景 为解决公司普通人员需要切换IP、DNS的情况,于是搞了个windows下的bat脚本,可以对有线网络、无线网络进行切换设置。 脚本内容 echo off title 多网络接口IP切换工具:menu cls echo echo 请选择要配置的网络接口: echo echo 1. 有线网络&am…...
VUE3开发-9、axios前后端跨域问题解决方案
VUE前端解决跨域问题 前端页面需要改写 如果无效,记得重启服务器 后端c#解决跨域问题 前端js取值,后端c#跨域_c# js跨域-CSDN博客...
【计算机网络】计算机网络的性能指标——时延、时延带宽积、往返时延、信道利用率
计算机网络的性能指标 导读 大家好,很高兴又和大家见面啦!!! 在上一篇内容中我们介绍了计算机网络的三个性能指标——速率、带宽和吞吐量。用大白话来说就是:网速、最高网速和实时网速。 相信大家看到这三个词应该就…...
Kubermetes 部署mysql pod
步骤 1: 创建 PersistentVolume 和 PersistentVolumeClaim 首先为 MySQL 创建一个 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 来确保数据的持久性。 mysql-pv.yaml: apiVersion: v1 kind: PersistentVolume metadata:name: mysql-pv-volume spec:cap…...
Docker和DockerCompose基础教程及安装教程
Docker的应用场景 Web 应用的自动化打包和发布。自动化测试和持续集成、发布。在服务型环境中部署和调整数据库或其他的后台应用。从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。 CentOS Docker 安装 使用官方安装脚本自动安装 安装命令…...
网络安全等级保护—定级
🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 网络安全等级保护五个保护等级 五个保护等级 受侵害的客体 对客体的侵害程度 一般损害 严重损害 特别严重损害 公民、法人和其他组织的合法权益 第一级 第二级 …...
服务器数据恢复—raid5阵列中硬盘出现坏道的数据恢复流程
服务器故障情况: 某公司一台服务器中有一组多块硬盘组成的磁盘阵列。磁盘阵列中有2块硬盘出现故障离线,服务器崩溃,上层数据丢失。 硬件检测: 硬件工程师对客户服务器内的所有硬盘进行物理故障检测,最终确认这2块硬盘…...
macos 程序 运行
sudo xattr -r -d com.apple.quarantine [/Applications/Name]使用stow 管理配置文件...
UVC for USBCamera in Android
基于UVC 协议,完成USBCamera 开发 文章目录 一、目的:二、USBCamera 技术实现方案难点 三、误区:四、基础补充、资源参考架构图了解Camera相关专栏零散知识了解部分相机源码参考,学习API使用,梳理流程,偏应…...
Docker 安装 Nacos 2.1.1(单机版)
一、拉取镜像 docker pull nacos/nacos-server:v2.1.1 二、新建数据库 官网上下载 对应版本的 nacos zip 包,在 nacos\conf 目录下有 mysql脚本: 新建一个数据库 nacos_config,在数据库中依次执行 nacos-mysql.sql、1.4.0-ipv6_support-up…...
网络安全数据富化 网络数据安全处理规范
本文件规定了网络运营者开展网络数据收集、存储、使用、加工、传输、提供、公开等数据处理的安全 技术与管理要求。 本文件适用于网络运营者规范网络数据处理,以及监管部门、第三方评估机构对网络数据处理进行 监督管理和评估。 部分术语和定义 数据(data&#x…...
深入理解 C 语言函数的定义
在 C 语言的编程世界里,函数是构建复杂程序的基石。理解函数的定义与运用,对于编写高效、可维护的代码至关重要。 函数定义的基本概念 函数是一组执行特定任务的代码块。它将一个复杂的问题分解为一个个小的、可管理的部分,提高了代码的…...
SQL注入练习场:PHPStudy+SQLI-LABS靶场搭建教程(零基础友好版)
注意:文中涉及演示均为模拟测试,切勿用于真实环境,任何未授权测试都是违法行为! 一、环境准备 下载PHPStudy 官网下载地址:https://www.xp.cn/php-study(选择Windows版) 安装时建议选择自定…...
最好用的多语言插件Google Language Translator
Google Language Translator 是一款用于 WordPress 的翻译插件,可以帮助网站快速实现多语言支持。以下是关于该插件的详细介绍: 功能特点 免费与付费版本 免费版本使用 Google Translate 的短语基础机器翻译,能够快速将网站内容翻译成多种…...
计算机组成原理笔记(一)——1.1电子计算机与存储程序控制
电子计算机是一种不需要人工直接干预,能够自动、高速、准确地对各种信息进行处理和存储的电子设备。电子计算机从总体上来说可以分为两大类:电子模拟计算机和电子数字计算机。电子模拟计算机中处理的信息是连续变化的物理量,运算的过程也是连续的;而电子…...
5人3小时复刻Manus?开源OpenManus项目全解剖,我的DeepSeek股票报告这样诞生
大家好,我是大 F,深耕AI算法十余年,互联网大厂技术岗。分享AI算法干货、技术心得。 更多文章可关注《大模型理论和实战》、《DeepSeek技术解析和实战》,一起探索技术的无限可能! OpenManus是什么 1. 项目背景 OpenManus 是由 MetaGPT 核心团队仅用 3 小时复刻而成的开源…...
WordPress报502错误问题解决-php-fpm-84.service loaded failed failed LSB: starts php-fpm
文章目录 问题描述问题排查问题解决 问题描述 服务器环境: php:8.4MySQL:8.0Nginx:1.26.2 在访问站点时,一直报502,而两天前还能正常访问。 问题排查 导致502的问题很多,比如站点访问量太大…...
「Selenium+Python自动化从0到1②|2025浏览器操控7大核心API实战(附高效避坑模板))」
Python 自动化操作浏览器基础方法 在进行 Web 自动化测试时,操作浏览器是必不可少的环节。Python 结合 Selenium 提供了强大的浏览器操作功能,让我们能够轻松地控制浏览器执行各种任务。本文将详细介绍如何使用 Python 和 Selenium 操作浏览器的基本方法…...
uniapp 微信小程序 升级 uniad插件版本号
问题描述: 每次提交代码升级的时候会弹窗提示:uniad插件版本太低… 解决办法 一、使用微信小程序开发工具点击右上角 查看到最新版本:1.3.4 二、在app.json中改为最新的版本即可 "uni-ad": {"version": "1.3.4&q…...
使用Dockerfile打包java项目生成镜像部署到Linux_java项目打docker镜像的dockerfile
比起容器、镜像来说,Dockerfile 非常普通,它就是一个纯文本,里面记录了一系列的构建指令,比如选择基础镜像、拷贝文件、运行脚本等等,每个指令都会生成一个 Layer,而 Docker 顺序执行这个文件里的所有步骤&…...
