01-Redis 核心:数据结构、持久化与内存治理
📝 笔记摘要:Redis 的数据结构、持久化机制与内存治理
Redis 是把数据放在内存里的键值服务器,键是字符串,值是它自己定义的那几种数据结构。这篇讲三件事:它到底提供了哪些类型、数据怎么落盘、一份 redis.conf 里真正需要动的项在哪。
还有件事得放在最前面:这个项目在 2024 到 2026 年间换过一次许可,也留了几个还在修补中的安全漏洞。选型之前先看第 8 节。
Redis 系列 共 4 篇,这是第 1 篇
01-Redis 核心 数据结构、持久化与内存治理 ← 本篇
02 Redis 部署与高可用 容器化部署、主从复制、哨兵切换
03 Spring Boot 整合 Redis 连接配置、序列化、工具类
04 Redis 缓存实战 穿透击穿雪崩、双写一致、分布式锁
这篇的结构
1. Redis 是什么,不是什么
2. 数据库为什么要拆
3. 五大基础类型
4. 三个特殊类型
5. 单线程模型与真正的瓶颈
6. RDB 与 AOF
7. redis.conf 里真正要动的项
8. 2026 年的 Redis:许可与那个必须知道的 CVE
1. Redis 是什么,不是什么
一句话说清:Redis 是把数据放在内存里的键值服务器,键是字符串,值是它自己定义的那几种数据结构。
它是 内存数据结构服务器、缓存层、计数器、排行榜、发布订阅的通道、轻量级锁
它不是 关系型数据库的替代品、完整的消息队列方案、磁盘数据库
默认端口 6379
默认数据库 16 个(databases 16),用SELECT n切换
常见用途大致这几类:
缓存 数据库前面加一层,热点数据直接返回,不落到 MySQL
计数器 浏览量、点赞数这类高频自增,INCR一次往返就够
排行榜 有序集合按分值排序,取前 N 名用ZREVRANGE
会话存储 带 TTL 的键值,天然契合
分布式锁SET key val NX PX 毫秒加一段释放脚本
发布订阅SUBSCRIBE/PUBLISH,但它不保证消息不丢也不保证顺序,要靠谱的投递用 RabbitMQ
只想要一份命令清单的话,站内那篇 Redis 入门概念 够用,它列了各类型的常用命令。这里往下走的是这些命令背后的机制和坑。
概念看明白了,接下来是把它跑起来。容器部署、配置文件挂载、主从复制和哨兵切换各有各的坑,单独写了一篇 Redis 部署与高可用。
2. 数据库为什么要拆
关系型数据库不是不好,是它有一套明确的假设:数据有结构、关系有约束、事务要一致。这套假设在「用户少、数据少」的时候是优势,规模上来之后就成了负担。
拆的过程分四步,每一步拆的都是同一个瓶颈:

单体模式 应用、连接池、数据库挤在一台机器上,数据量一台放不下,内存也放不下 B+ 树
读写分离 读多写少时把读分到从库,但写仍然单点,主库挂了还是挂
分库分表 单表按主键或用户维度切开,跨库查询和跨库事务变复杂
现代分布式 一份数据一份用途,Redis 扛热点读,MySQL 只接写和复杂查询
NoSQL 这个词被用得很滥,它其实只说明「不是关系型」。真正有意义的分类是按数据模型分的四类:
键值型 Redis、Memcached、Tair。查全靠 key,值当黑盒
文档型 MongoDB。最接近关系型,查询能力最丰富,schema 也最松
列存储型 HBase、ClickHouse。按列存,适合宽表和聚合分析
图数据库型 Neo4j。关系本身就是数据,查多跳路径用它
Redis 属于第一类,但它的值不是黑盒——它自己定义了几种结构,这正是它比 Memcached 强的地方。
3. 五大基础类型
五种基础类型,加三个特殊类型。先看基础的五种:
String 字符串,底层是二进制安全的字节序列,最大 512 MB。能当整数用,也能当 JSON 存
List 双向链表,元素可重复,两头都能出入队
Set 无序集合,元素不可重复
Hash 键值对集合,一个 key 下挂多个 field
ZSet 有序集合,带分值,底层就是 Set 加跳表
String
读写
SET key value、GET key、APPEND key value(不存在等价于 SET)、STRLEN key
计数INCR/DECR步长 1,INCRBY/DECRBY指定步长
片段GETRANGE key 起 止,取全部写GETRANGE key 0 -1;SETRANGE key 偏移 内容局部覆盖
带过期时间SETEX key 秒数 value一步到位,比SET加EXPIRE少一次往返
只在不存在时写SET key value NX,成功返回 OK,失败返回 nil。老的SETNX还能用,但官方推荐用SET NX,因为后者能顺带设过期时间
批量MSET k1 v1 k2 v2、MGET k1 k2、MSETNX k1 v1 k2 v2(有一条已存在就整批不写)
提示:
MSETNX是原子的——要么整批都写,要么整批都不写。这条命令在批量初始化场景里比循环SETNX可靠得多,后者在中途失败会留下半截数据。
List
出入队
LPUSH从左压、RPUSH从右压,LPOP/RPOP从两头取
取一段LRANGE key 起 止,LRANGE key 0 -1取全部
定位LINDEX key 序号,序号从 0 开始
长度LLEN key
删指定值LREM key 数量 值,数量为正从头删、为负从尾删、为 0 全删
截断LTRIM key 起 止
搬运RPOPLPUSH 源 目标,从源尾部弹一个压到目标头部
改值LSET key 序号 value
插入LINSERT key BEFORE 锚点值 AFTER 锚点值 要插入的值
提示:
LPUSH压进去的元素,LRANGE key 0 -1读出来是逆序的。这是链表结构的必然结果,不是 bug。取「最新 N 条」用LRANGE key 0 -1之后自己反一下,或者干脆用LPUSH加LRANGE key 0 N-1。
提示:
LSET写不存在的 key 报no such key,写越界的序号报index out of range。两个错误含义不同,排查时别混。
Set
出入
SADD key 成员...、SMEMBERS key、SISMEMBER key 成员(返回 1 或 0)
计数SCARD key
删SREM key 成员、SPOP key(随机弹一个出来)
随机取SRANDMEMBER key 数量,不传数量返回随机一个
搬运SMOVE 源 目标 成员,失败返回 0
集合运算SDIFF k1 k2差集、SINTER k1 k2交集、SUNION k1 k2并集
提示:
SUNION是并集不是补集。真正的补集(差集)是SDIFF。这两个搞混的结果是「求交集」和「求补集」的返回集完全不对。
Hash
读写
HSET key field value、HGET key field、HGETALL key、HDEL key field
字段HKEYS key、HVALS key、HLEN key、HEXISTS key field
自增HINCRBY key field 增量、HINCRBYFLOAT key field 增量
提示:hash 类型的自增命令是
HINCRBY和HINCRBYFLOAT,没有HINCR和HDECR。要减就传负数增量给HINCRBY。Redis 7.4 起 hash 还支持字段级过期,用HEXPIRE给单个 field 设 TTL,不用再把整个对象拆开。
Hash 的价值在于「一个对象存一个 key」。把 user:1 的 name、age、city 放进同一个 hash,读一次 HGETALL 就拿到整个对象,比拆成三个字符串 key 省往返。
ZSet
写入
ZADD key 分值 成员,同分值再写会更新分值
取ZRANGE key 起 止按分值升序、ZREVRANGE key 起 止降序、ZRANGEBYSCORE key 最小 最大
排名ZRANK key 成员、ZREVRANK key 成员
分值ZSCORE key 成员
4. 三个特殊类型
Geo
地理位置。底层就是一个 ZSet,分值是 geohash 编码,所以 zset 的所有命令对 geo 一样有用。
写入
GEOADD key 经度 纬度 名称,经度在前纬度在后,写反了位置就跑到地球另一边
查坐标GEOPOS key 名称
算距离GEODIST key 名称1 名称2 单位,单位支持m/km/mi/ft
找附近GEOSEARCH key FROMMEMBER 名称 BYRADIUS 半径 单位
取 hashGEOHASH key 名称,返回 11 位字符串
提示:
GEORADIUS和GEORADIUSBYMEMBER从 Redis 6.2.0 起已经废弃,新代码一律用GEOSEARCH和GEOSEARCHSTORE(后者带BYRADIUS参数)。老的代码还能跑,但会一直吃废弃警告。
提示:合法的经度范围是 -180 到 180,纬度是 -85.05112878 到 85.05112878。超出范围的值会被直接拒绝。
HyperLogLog
基数统计:估算一个集合里有几个不重复的元素。占内存极小,代价是有误差。
写入
PFADD key 元素...
统计PFCOUNT key,可以一次传多个 key 算并集的基数
合并PFMERGE 目标 源...
用不上的场景就别用:它算的是估算值,标准误差约 0.81%,而且合并两个已经算过的 HyperLogLog 只能得到并集,不能算交集。要做精确统计就直接用 Set。
Bitmap
位图。当数据只有两种状态时,一个 bit 就够。
设值
SETBIT key 位号 0或1,位号从 0 开始
取值GETBIT key 位号
统计BITCOUNT key,也可以带start end和BYTE/BIT限定范围
典型用法是签到:用户 ID 减一当位号,一年 365 位,一个用户一年只用 45 字节。100 万用户一年 45 MB。
5. 单线程模型与真正的瓶颈
「Redis 是单线程的」这句话对了一半:命令执行始终单线程,但 Redis 6.0 起把 socket 读写搬到了可配的 I/O 线程里。

为什么命令执行不并行走多线程 多线程改共享数据结构要加锁,锁的开销和死锁风险会把省下来的时间吃掉。Redis 干脆把复杂度固定住:数据结构全用单线程改
I/O 线程默认不开 要显式配io-threads才有。它省的是网络往返等待,不是执行时间
真正的瓶颈 不是 CPU 核数,是内存容量和网络带宽。单实例顶不住时横向加实例,不是纵向加线程
慢命令会堵住全部请求 因为只有一条线程在执行命令
提示:线上巡检千万别用
KEYS *,它会遍历整个键空间并阻塞主线程,键一多就是一次假死。用SCAN cursor [MATCH pattern] [COUNT n],它每次只返回一小批,用游标反复调用直到返回 0。生产环境通常还会把KEYS直接从配置里禁掉。
6. RDB 与 AOF
两种持久化方式,一个存快照,一个存操作流水。
RDB 某一时刻的二进制快照。恢复时直接把这个文件读进内存
AOF 把每条写命令追加到文件里。恢复时从头执行一遍
默认开哪个 两个都开。RDB 负责快速恢复,AOF 负责尽量不丢数据
RDB
触发方式有三种:配置里的定时规则、命令行 SAVE、以及关服时自动存一份。
# redis.conf 里的默认规则:3600 秒内至少 1 次修改、300 秒内至少 100 次、60 秒内至少 10000 次
save 3600 1 300 100 60 10000
# 手动存一份快照
SAVE
# 看 RDB 文件在哪
CONFIG GET dir
优点 文件小,恢复快,适合做备份和全量迁移
缺点 两次快照之间的数据会丢。写save时会 fork 一个子进程做快照,这个瞬间内存会翻倍,内存不够就 OOM;子进程写盘期间主进程还能服务,但写的是内存里的旧页(Copy-on-Write)
大内存机器上用 CONFIG SET save "" 关掉自动 RDB,改由外部定时任务(比如 BGSAVE 配合 cron)触发,避开业务高峰期的 fork。
AOF
把写命令按顺序追加进文件,重启时从头执行一遍还原数据。逻辑上像 MySQL 的 binlog 回放,但粒度是单条命令。
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
# 运行时打开
redis-cli CONFIG SET appendonly yes
# 关掉 RDB 只留 AOF,避免两套机制同时 fork
redis-cli CONFIG SET save ""
appendfsync always 每次写都同步落盘,最安全,最慢
appendfsync everysec 每秒同步一次,这是默认档,也是绝大多数场景的正解。最坏情况丢一秒的数据
appendfsync no 交给操作系统自己刷,性能最好,宕机可能丢更多
Redis 7.0 起 AOF 不再是单个文件,而是一个目录,里面三类文件各司其职:

appendonly.aof.N.base.rdb 基线,一个 RDB 格式的全量快照
appendonly.aof.N.incr.aof 基线之后的增量命令
appendonly.aof.manifest 清单,记着当前该用哪几个文件、多大、什么时候截断
文件被写坏了可以用官方工具修:redis-check-aof --fix 文件名。
怎么选 数据丢了不能接受就
appendfsync always;能接受丢一秒就everysec;纯粹当缓存用、不在乎重启后的数据,两个都关掉最省事
7. redis.conf 里真正要动的项
redis.conf 有上千行,但生产上真正要改的就这么几类。
内存上限
maxmemory 1gb,不设就是用满物理内存再加 swap,swap 一开延迟就废了
淘汰策略maxmemory-policy,内存满了踢 key 的规则
持久化save、appendonly、appendfsync、dir
网络bind、protected-mode、port、timeout
认证requirepass或 ACL
连接数maxclients
内存与淘汰策略
maxmemory 到了上限之后,Redis 按 maxmemory-policy 决定踢谁:
noeviction 不踢,写命令直接报错。数据库类用途用这个,别让 Redis 偷偷丢你的数据
allkeys-lru 所有 key 里按最近最少使用踢。纯缓存用这个
allkeys-lfu 所有 key 里按访问频次踢,比 LRU 更抗突发流量,热点 key 不会被一次刷屏打掉
volatile-lru 只在设了 TTL 的 key 里按 LRU 踢
volatile-lfu 只在设了 TTL 的 key 里按 LFU 踢
allkeys-random / volatile-random 随机踢,性能比 LRU 差但开销小
volatile-ttl 踢快过期的那个
提示:带
volatile的策略有个陷阱——如果所有 key 都没设 TTL,内存满了以后 Redis 会直接返回错误让写不进去,而不是帮你腾地方。用 volatile 策略就必须给每个 key 都设过期时间。
网络与认证
# 只监听本机回环,外部访问一律拒绝
bind 127.0.0.1 -::1
protected-mode yes
port 6379
# 认证(Redis 6 之后的推荐写法是 ACL,这里是兼容写法)
requirepass <你的密码>
# 连接数上限,默认 10000
maxclients 10000
# 空闲连接超时,单位秒,0 表示不超时
timeout 0
提示:不要为了「图形化工具连不上」就把
protected-mode改成no,更不要把bind那一行删光。那等于把一个没有任何认证的 Redis 敞在公网上,Redis 出名的未授权访问漏洞就是这么来的。正确做法是:给 Redis 设密码,客户端填密码,防火墙只放行来源 IP。
Redis 6 之后推荐用 ACL 做细粒度权限,比一个全局密码安全得多:
# 给某个应用开一个只读账号,限定只能碰某个前缀的 key
ACL SETUSER app_ro on >密码 ~product:* +get +mget +hget +hmget +list
# 看看当前有哪些用户
ACL LIST
# 删掉某个账号
ACL DELUSER app_ro
本机没有 redis-cli 的话可以先在容器里跑一个:
docker exec -it redis redis-cli -a <你的密码> ACL LIST
8. 2026 年的 Redis:许可与那个必须知道的 CVE
选型之前必须知道的两件事。
许可换过三段
1.0 到 7.2 BSD 许可,宽松的开源协议
7.4(2024-07) 改成 RSALv2 + SSPLv1 双许可,这两个都不是 OSI 认可的开源许可,云厂商不能直接拿去卖托管服务
8.0(2025-05-02 GA)起 AGPLv3 作为第三个选项加回来,现在是 RSALv2 / SSPLv1 / AGPLv3 三选一,可以按 AGPLv3 光明正大当开源用
顺带一提,8.0 把当年要另装的 Redis Stack 模块并进了核心:JSON、Time Series、Bloom / Cuckoo / Count-min sketch / Top-K / t-digest、向量集(预览版)、查询引擎。另外项目名也从 Redis Community Edition 改成了 Redis Open Source。
当前稳定版是 8.10(2026 年 7 月)。
有一个 CVE 打在 8.0.x 上
2026 年 7 月 23 日的协调安全发布修了 CVE-2026-66373:RESTORE 命令处理构造数据时存在释放后重用(use-after-free),可能远程执行代码,CVSS 评分 7.5(高危)。
修复发到了 6.2、7.2、7.4、8.2、8.4、8.6 这几条线
没发到 8.0 8.0.x 的最后一个版本是 8.0.6(2026-02-23),这个漏洞在 8.0.x 上依然存在
怎么办 钉在 8.0.x 的赶紧升到 8.2.8 或更高(一直到 8.10 都可以),别等 8.0.7——它不会来
8.0 当年修过的那批漏洞也值得记一下:CVE-2024-46981(Lua RCE)、CVE-2024-51741(ACL 拒绝服务)、CVE-2024-31449 / 31227 / 31228(Lua 与 ACL 的拒绝服务)。
💡 补充:还有两个命令层面的版本陷阱。SLAVEOF 在 Redis 7.0 已经被彻底删除,改用 REPLICAOF,老运维脚本里写死的命令会直接报错。GEORADIUS 系列从 6.2.0 起废弃,新代码用 GEOSEARCH。这两条都会在故障切换时咬人——命令不存在,自动化脚本就停在半路。
🔥 一句话总结:Redis 的强项是内存里的那五种数据结构和毫秒级读写,弱项一是从命令执行到落盘全是单点逻辑,弱项二是 8.0.x 这条线停在了未修 CVE-2026-66373 的 8.0.6 上——要投产就把版本钉在 8.2.8 以上。
