尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Redis基础知识全解析:从数据结构到持久化与性能优化
Redis 是很多人接触的第一款内存数据库但大多数人一开始对它的理解都停在“把数据放内存所以读写快”这个层面。我第一次把官方文档通读下来时其实挺懵的——它不只是缓存还经常出现在分布式锁、排行榜、消息队列、计数的场景里。后来我陆续做过几个内部项目回头看基础知识这块发现真正理解它的定位、数据结构和底层机制和“会用几个命令”之间差距很大。这篇笔记是系列第一篇写给两类读者一类是刚接触 Redis 想系统梳理基础的人一类是已经在用但主要是“复制粘贴命令、能跑就行”的朋友。我会把 Redis 的核心基础知识拆成六个部分从它到底解决了什么问题开始讲到五种数据类型的选型、性能来源、持久化机制、过期与淘汰策略最后分享初学者第一个月最容易踩的坑。每个部分我都会解释“为什么”而不是只丢几个命令让你背。1. Redis到底是什么一个内存键值仓库但不只是“缓存”1.1 为什么常说它是“数据库之上的数据库”Redis 的官方定位是 in-memory data structure store也就是“内存数据结构存储”。它把数据放在内存里所以读写速度可以做到微秒到亚毫秒级别远超传统磁盘数据库。这个特性决定了它最常见的用法把热点数据放在 Redis 里让请求先打到内存而不是每次都在数据库里查一遍磁盘。但你要注意它和“数据库”的关系更像是互补而不是替代。传统关系型数据库擅长事务、复杂查询、关系建模数据持久放在磁盘上Redis 擅长高速读写、简单数据类型、原子操作数据主要活在内存里。用一个场景类比关系型数据库是仓库Redis 是前台货架。热门商品放货架上随时取冷门商品去仓库搬。如果所有商品都堆在前台货架会爆成本也撑不住。这决定了你在设计系统时要先问一个问题这份数据真的是“热数据”吗如果全量数据都塞进 Redis内存成本会非常夸张命中率也不一定高。正确思路是只把读多写少、访问频繁的数据放进去。1.2 和Memcached、MySQL的定位差异选型时先想清楚这三者很多老项目早期用 Memcached 做缓存后来逐步换到 Redis。两者都是内存键值存储但差异非常明显数据结构Memcached 基本只有简单的 stringRedis 有 string、list、hash、set、zset 五种基础类型还能做位图、HyperLogLog 这类扩展操作。持久化Memcached 挂了数据就没了Redis 可以通过 RDB 和 AOF 把数据恢复到磁盘。主从复制与高可用Redis 有主从复制、哨兵、集群方案Memcached 没有这么完整的体系。原子操作Redis 的 INCR、SETNX 等命令天然支持原子自增、分布式锁Memcached 需要自己拼逻辑。MySQL 和 Redis 的区别更本质MySQL 的核心是“关系模型 事务 SQL 查询”Redis 的核心是“内存 数据结构 原子命令”。你不可能用 Redis 替代 MySQL 存储复杂的订单关系数据但可以用 Redis 缓存订单热点信息、加速计数统计、实现分布式锁。我的建议很直接新的系统里直接用 Redis 就行了Memcached 除了极端简单的缓存场景基本没有新项目选它的理由。而 MySQL 和 Redis 两者必须同时存在Redis 负责扛流量和热点MySQL 负责保底数据。1.3 典型应用场景缓存之外Redis还常出现在这五个地方光说理论容易空我列几个实际项目中高频出现的使用方式缓存缓存用户信息、商品详情、配置项。这是知名度最高的场景也是新手的第一个练手项目。计数器文章阅读量、点赞数、接口调用次数。用 INCR 命令直接原子自增不用先查再改也不会并发覆盖。分布式锁多个实例同时抢资源时需要锁。用 SETNX 过期时间实现一个简单锁保证同一时刻只有一个进程执行关键操作。排行榜游戏积分排行、热销商品榜。ZSet 的分数排序天生适合这类需求ZREVRANGE 一行命令拿到前 N 名。简单的消息队列用 List 的 LPUSH BRPOP 做一个先进先出的任务队列。功能比不上专业消息中间件但轻量、无额外依赖很多小项目就这么跑起来的。看到这里你应该能明白Redis 的定位是“基础设施”不是某个业务的专属工具。这也是为什么基础概念值得认真梳理一遍——数据类型之间的细微差别直接影响你场景设计得好不好。2. 五种基础数据结构先学会给数据“分类”再谈命令2.1 String不只是存字符串那么简单String 是 Redis 里最基础、最通用的类型。你存 JSON 字符串、存图片 base64、存 session 都可以用它但如果你想充分利用 RedisString 还有一些“隐藏能力”。先看最基本的读写SET user:1001 tom GET user:1001真正有意思的是INCR / DECR原子自增。很多语言里做“先读后写”的计数器都有并发问题Redis 把这些操作封装成了原子命令INCR page_view:2025:06:01 INCRBY login_count:1001 10另外一个容易被忽略的是SETNX也就是“不存在才设置”。它常用来实现分布式锁SET lock:order_paid 1001 NX EX 30这条命令的意思是只有当 key 不存在时才写入并且 30 秒后自动过期。这样多个进程同时执行时只有一个能成功拿到锁其余进程直接失败或等一会儿重试。String 的底层有多种编码方式比如整数类型用的就是整数编码而非字符串这一点在后面讲对象编码时细说。实际使用中String 最适合做短小、高频修改的标量数据比如状态、计数、token、标志位。2.2 List从队列到时间线注意“头尾方向”List 底层是双向链表。用 LPUSH 从左边推入RPUSH 从右边推入LPOP / RPOP 分别从两端弹出。它有两个常用姿势第一个是先进先出队列LPUSH task_queue job_1001 RPOP task_queue生产者把待处理任务 LPUSH 进去消费者 RPOP 取出来这就算完成了一个最简单的任务队列。如果消费者处理到一半挂了任务会丢这是它的局限。更可靠的做法是用 RPOPLPUSH 先把任务挪到一个 backup 队列里处理成功后再真正删除相当于带“备份”的队列。第二个是时间线/最新消息列表LPUSH user:1001:newsfeed 10001 LPUSH user:1001:newsfeed 10002 LRANGE user:1001:newsfeed 0 9每次新内容都往左推LRANGE 取前 10 条天然就是“最新的在前”。很多社交信息流、评论列表都是这么设计的。使用 List 时最容易踩的坑是混淆头尾方向。LPUSH 和 RPUSH 不只是名字不同它直接决定了列表顺序。写代码前先画一下你希望“新元素排在前面”还是“排在后面”再决定用哪个 push。否则你取出的顺序会莫名其妙反过来。2.3 Hash结构化的对象比 String 更省内存Hash 是一个 key 下挂多个 field-value非常適合存储“一个对象的多个字段”。比如用户信息HSET user:1001 name tom age 28 city 北京 HGET user:1001 name HGETALL user:1001 HINCRBY user:1001 points 50和把整个用户对象序列化成 JSON 字符串存 String 相比Hash 有几个好处局部读改写只改某个字段时不需要把整个字符串拉出来反序列化再写回去。直接 HSET 或 HINCRBY 就行。省内存部分场景Redis 对小型 hash 做了 ziplist 压缩编码字段少、值小的时候内存占用相当可观。结构清晰业务上就是一个对象代码读起来也自然。Hash 的典型使用场景是购物车、用户资料、文章详情。注意不要滥用如果字段很多且频繁写入压缩编码会转成 hashtable内存优势就没了这时候还是要回到 String 序列化或分片存储。2.4 Set 与 ZSet去重、交并差运算和有序排行榜Set 是无序字符串集合主要特点是自动去重而且提供集合运算。常用命令SADD user:1001:tags java redis SISMEMBER user:1001:tags redis # 判断是否存在 SPOP user:1001:tags # 随机弹出一个 SINTER user:1001:tags user:1002:tags # 交集共同标签 SUNION user:1001:tags user:1002:tags # 并集 SDIFF user:1001:tags user:1002:tags # 差集实际的常见场景包括抽奖活动SPOP 随机抽人、点赞去重、UV 去重、社交关系里“共同好友”这种交并差计算。如果你需要“在大量元素中快速判断某个值是否存在”Set 就非常合适。ZSet有序集合在 Set 基础上给每个成员加了一个分数 score按分数排序。它底层组合了哈希表和跳表既支持 O(1) 查成员又支持按分数排序。ZADD leaderboard 1000 player_a ZADD leaderboard 1500 player_b ZINCRBY leaderboard 200 player_a ZREVRANGE leaderboard 0 9 WITHSCORES # 前十名 ZSCORE leaderboard player_a # 查询某人分数ZSet 的经典场景是排行榜。每次比赛结束 ZINCRBY 加分随时拿 ZREVRANGE 取前 N 名分数还能作为时间戳实现“延迟队列”按执行时间排序定期取最前面的任务判断是否到期。ZSet 要注意的是 score 支持浮点数如果对精度敏感建议统一存成整数比如毫秒时间戳避免浮点误差带来排序错乱。2.5 一次真实的类型选型从 JSON 大字段拆成 Hash 的收益我在一个内部模拟项目里遇到过这种情况业务方要把一整个用户扩展信息存进 Redis最开始图省事直接存 JSON 字符串key 是 user:1001:extvalue 是几百个字段序列化后的 JSON。上线后有两个问题一是每次只要改动其中一个字段就要把整个 JSON 拉出来反序列化再写回二是内存占用比预期高因为大字符串无法走压缩编码。后来改成了 Hash 存储把高频读写的字段单独拆出来低频字段继续放 JSON 子串。结构变成HSET user:1001:ext level 5 vip 1 last_login 2025-06-01结果是单次更新速度明显提升内存占用也降下来一截。这个例子想说明的核心是先判断业务对某个对象的读写模式再选类型而不是一律 SET JSON。做个快速选择表需求推荐类型原因计数器、token、短状态String原子操作、简单直接任务队列、最新列表List天然先进先出方向可控对象多字段读改写Hash局部更新方便小型时更省内存去重、快速判存、交并差Set集合运算原生支持排行榜、按分数排序ZSet分数维度天然排序大 JSON、日志原文String不需要结构化解耦直存最省事3. 高性能的三个魔法单线程、多路复用和高效数据结构3.1 单线程为什么快没有竞争就没有锁的开销Redis 的命令执行是单线程的所有客户端请求最终都在一个主线程里串行处理。很多人第一反应是单线程不是慢吗这里要反过来想。对 Redis 来说数据在内存里一个简单 GET 命令耗时才几十纳秒到微秒级。此时的瓶颈根本不是 CPU 计算能力而是网络 IO 和内存带宽。单线程最大的收益是不需要处理并发竞争没有锁、没有线程切换、没有同步开销。多线程程序为了并发正确性付出的代价往往比并发带来的收益还大尤其当任务本身极快时。打个比方一个窗口办业务每个人只需要 1 秒。如果十个窗口同时开大家还要排队等叫号、协调资源反而乱套一个窗口顺滑地处理吞吐反而稳定。Redis 6.0 之后引入了多线程处理网络 IO 读写但核心命令执行仍然是单线程。这意味着你写 Lua 脚本时也要保持原子性思维脚本内所有操作按顺序执行不会被其他命令插进来。3.2 IO多路复用一个进程如何“盯着”成千上万个连接单线程要应对大量客户端连接靠的是 IO 多路复用机制。通俗解释是主线程只需要一个“接待员”用 epoll 把成千上万个 socket 都交给内核监听。哪个连接有数据来了内核就通知它去处理没有数据就用空闲时间去处理别的。这个设计让 Redis 能够用很低的资源支撑大量连接。你用 redis-benchmark 可以看到它在本机压测下能跑到十万级 QPS和生产环境网络瓶颈下表现完全不同。实际调优时如果你发现 Redis 延迟升高首先要排查的是网络延迟、客户端慢查询、big key 阻塞而不是怀疑 Redis 单线程撑不住。3.3 字符串底层 SDS 和对象编码省下的时间都很可观Redis 没有直接用 C 语言的 char 数组实现字符串而是定义了自己的 SDS简单动态字符串结构。它额外记录了长度、分配空间等信息所以获取字符串长度是 O(1)不用像 C 字符串那样从头扫到尾。修改字符串时还会做预分配减少频繁 realloc 的次数。对象编码对外透明但了解后能帮助你理解内存。常见的字符串编码有int字符串能解析成 64 位整数时直接用整数存储。embstr短字符串和 RedisObject 头一起分配在一块连续内存里少一次内存分配。raw长字符串用独立内存。你可以用 OBJECT ENCODING 查看某个 key 的当前编码SET num 100 OBJECT ENCODING num # int SET name hello OBJECT ENCODING name # embstr SET long_str a very long string worth of raw encoding... OBJECT ENCODING long_str # raw还有一个隐藏的优化是整数共享0 到 9999 这种小整数在 Redis 内部是共享对象不重复分配。所以如果你有很多小状态值内存压力会小很多。这些细节不需要背但排查内存问题时会非常有用。4. 持久化配置文件里最需要提前想明白的部分4.1 RDB 快照的触发机制与“丢数据窗口”Redis 默认的持久化方式是 RDB它生成一个压缩的二进制快照文件 dump.rdb。触发方式有三种手动执行 SAVE / BGSAVE、配置文件里的 save 规则、正常关闭时自动保存。其中 SAVE 是同步执行会阻塞主线程生产环境千万不能用。BGSAVE 是 fork 一个子进程做快照主线程继续服务。配置文件里默认的规则长这样save 900 1 save 300 10 save 60 10000意思是900 秒内有 1 次写操作就生成快照300 秒内有 10 次写操作就生成快照60 秒内有 10000 次写操作就生成快照。RDB 的核心局限是丢数据窗口。比如你 60 秒内没攒够 10000 次写操作最后一次快照之后所有数据都可能丢失。如果你做的项目能容忍几分钟数据丢失RDB 够用如果不行就要开 AOF。4.2 AOF 追加日志三种 fsync 策略怎么选AOFAppend Only File把每一条写命令追加到一个日志文件里。重启时重新执行这些命令来恢复数据所以它最多只丢你最后一次 fsync 之后到宕机之间的数据。关键配置是 appendfsync三种策略策略行为特点always每次写命令都执行 fsync最安全但性能最差everysec每秒执行一次 fsync性能与安全折中默认推荐no由操作系统决定何时落盘性能最好但可能丢大量数据实际生产中 unless 业务对数据一致性有硬性要求比如支付流水我一般用 everysec 就够最坏丢一秒数据。always 每秒要 fsync 一次会明显拖慢写性能适合极端重要且写量不高的场景。AOF 还有一个重写机制文件越来越大时需要压缩成最终状态。手动执行 BGREWRITEAOF或者通过配置自动触发auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb重写时 Redis 会 fork 子进程根据当前内存数据生成最小命令集清掉历史上大量冗余命令。你可以从 INFO 命令里看到 aof_rewrite 相关的统计信息。4.3 混合持久化与我的备份建议Redis 4.0 后支持混合持久化在 AOF 重写时生成的文件开头包含一份 RDB 快照后面继续追加增量写命令。这样重启恢复时先加载 RDB 飞快回放再补增量日志兼顾恢复速度和数据完整性。配置是aof-use-rdb-preamble yes我的建议分两种情况仅当缓存可以丢数据开 RDB 就够恢复快文件小。数据重要不能丢开 AOFappendfsync everysec同时打开混合持久化。两个都开时Redis 会优先用 AOF 恢复因为 AOF 完整度更高。另外提醒一句持久化不只是配置一把锁还要定期做备份演练。把 dump.rdb 或 AOF 文件备份到独立存储并实测一次“从备份恢复数据”的完整过程。如果你从来不知道备份文件能不能用那这个“备份”等于没有。5. 过期删除与内存淘汰Redis 不会无限膨胀靠的就是这套机制5.1 惰性删除 定期删除为什么不用定时器给 key 设置过期时间是日常操作比如验证码存 5 分钟、session 存 30 分钟SETEX captcha:user1001 300 123456 EXPIRE product:1001:seckill_stock 60 TTL product:1001:seckill_stock那过期时间一到Redis 就会立刻把 key 删掉吗不会。删除策略是惰性删除 定期删除的组合。惰性删除的意思是每次访问 key 时先检查是否过期过期才删。好处是省 CPU坏处是过期数据可能长时间占着内存。定期删除则是每 100ms 随机抽一批设置了过期时间的 key删除其中过期的防止大量过期 key 一直没人访问把内存占满。为什么不直接用定时器每秒扫一遍因为 key 量很大时全量扫描会拖垮单线程。所以 Redis 采用“抽查”策略——这是性能和维护成本之间的平衡。5.2 淘汰策略8 种策略怎么选近似 LRU 又是什么意思就算有过期机制内存也有膨胀风险。当内存达到 maxmemory 上限时Redis 会按配置的策略淘汰 key。常见策略如下策略行为noeviction不淘汰写入直接报错allkeys-lru所有 key 中淘汰最久未使用的volatile-lru仅设了过期时间的 key 中淘汰最久未使用的allkeys-random所有 key 随机淘汰volatile-random仅有过期时间的 key 随机淘汰volatile-ttl仅有过期时间按剩余寿命从短到长淘汰最常用的是 allkeys-lru。注意它叫近似 LRU不是严格 LRU。因为 Redis 不会为每个 key 维护完整的一颗全量访问链表而是采样一部分 key从中选出最久没用的。默认采样数是 5想让淘汰更精准可以调高采样数但会消耗更多 CPU。生产环境我会先把 maxmemory 设成物理内存的 60%~70% 左右留出操作系统和持久化子进程的余量。如果空间很充足就把淘汰策略配成 noeviction让直接报错成为信号尽早暴露容量问题而不是靠淘汰静默丢数据。5.3 缓存雪崩、击穿、穿透从“基础知识”到“高频故障”的过渡这三个概念属于“基础知识进阶”但非常值得在这一篇里提一句因为根源都来自过期和未命中。缓存雪崩大量 key 同一时间过期请求全部打到数据库数据库被压垮。对策是给过期时间加随机抖动比如 300 秒 ± 60 秒。缓存击穿一个热点 key 过期瞬间大量请求穿透到数据库。对策是热点 key 不过期或用互斥锁保证只有一个请求去重建。缓存穿透查询一个根本不存在的 key缓存里没有数据库里也没有请求每次都穿透。对策是缓存空值或者用布隆过滤器先挡一层。这些内容放到后面“缓存设计”专题里展开但在基础阶段至少要有这个意识过期策略不只是清理数据它直接影响线上稳定性。6. 初学 Redis 第一个月最容易踩的坑6.1 big key删除一个键拖慢了整个实例big key 指的是某个 key 对应的 value 特别大比如一个 string 有几 MB一个 list 里有几十万条数据一个 hash 有上万个字段。它的危害很隐蔽。表面上看存取都正常但从删除、序列化、迁移时就能感受到问题。最典型的是 DEL 一个几百万成员的集合主线程要花几秒清理内存期间所有命令都被阻塞线上就是断崖式超时。处理方式删除大 key 用 UNLINK 命令它把释放内存放到后台线程去做不阻塞主线程或者用 SSCAN、LRANGE 分批删除。另外可以用 redis-cli 的 --bigkeys 参数扫描实例里最大的 key提前发现风险redis-cli --bigkeys我习惯把 big key 排查纳入周常巡检尤其是业务快速迭代阶段很容易不知不觉长出大 key。6.2 keys * 与 scan别再亲手制造线上事故初学的时候最顺手的就是keys *想看看都有哪些 key。但在生产环境执行它等同于自杀因为 KEYS 会全量扫描所有键单线程阻塞几百万 key 的情况下直接把实例卡死几十秒。正确做法是用 SCAN 命令它每次只返回一部分 key增量迭代SCAN 0 MATCH product:* COUNT 100 SCAN 1000 MATCH product:* COUNT 100SCAN 支持游标方式分批遍历不会阻塞主线程。即使目标是写脚本清理符合某种规则的 key也应该用 SCAN DEL 分批处理而不是 KEYS 拿到全量再删。6.3 学习建议从 redis.conf 注释开始读起最后一个建议可能和你想的不太一样学 Redis 不要先刷命令列表而是把 redis.conf 从头翻一遍。官方配置文件里的注释写得非常清楚基本上把每个参数什么场景用、默认值多少、有什么坑都讲透了。我到现在排查问题时还会经常打开 conf 看某个参数的官方说明。配合 redis-cli 的 INFO 命令你能看到内存、持久化、连接数、命中率等实时统计这些才是真实运维的依据。如果遇到想不通的行为就手动模拟一个场景用 OBJECT ENCODING、TTL、MEMORY USAGE 观察真实状态比背概念有效得多。基础部分的笔记就写到这里。这个系列后续我打算继续整理过期策略实测、持久化恢复演练、主从与哨兵搭建、集群分片原理等内容。如果你在学习和排查中遇到了和这篇笔记相关的问题可以在评论区把现象和配置发出来我们一起看看是哪个基础原理没对上。
RELATED

相关推荐

短剧什么都知道:欲望叙事与高质量进化之路

短剧什么都知道:欲望叙事与高质量进化之路

1. 为什么一本帮短剧"说话"的书,反而让我把它看完了 我承认,点开这本书之前,我带着一点微妙的优越感。作为一个自诩"看剧有品位"的人,短剧在我这里的定位一直是"狗血""粗制滥造"的代名词…

📅 2026/10/16 12:59:23
免费接入Claude级代码生成能力:本地部署与混合路由实战指南

免费接入Claude级代码生成能力:本地部署与混合路由实战指南

1. 这个项目到底在折腾什么第一次看到 "free-claude-code" 这个标题,我脑子里蹦出来的第一个念头是:又是一个想白嫖大模型能力的工具。但仔细琢磨了一下,事情没那么简单。这个标题背后其实藏着一个非常现实的需求——如何在不依赖官…

📅 2026/10/16 12:59:23
DVWA Medium级SQL注入原理与实战突破

DVWA Medium级SQL注入原理与实战突破

1. Medium难度SQL注入的本质:不是绕过过滤,而是重构攻击链 DVWA的Medium级别SQL注入,常被初学者误读为“比Low级多加了几个 mysql_real_escape_string 调用,只要找对绕过方式就能通关”。这种理解从根子上就错了。我带过不少刚接…

📅 2026/10/16 12:54:22
MORE NEWS

更多资讯

📰

逻辑智力题的本质是结构化拆解能力训练

1. 这类题不是考“聪明”,而是考你拆解问题的肌肉记忆“面试准备:逻辑智力题”——这八个字背后藏着太多被误解的真相。很多人一看到“逻辑智力题”,第一反应是去刷《门萨智商测试》或者翻《微软面试100题》,以为答对越多&#xf…

📰

气象数据小波分析实战:MATLAB实现CWT、XWT与WTC周期诊断

简介:这份气象数据小波分析MATLAB代码面向气象、水文及大气科学方向的学生与科研人员,用于处理非平稳、非线性气象信号,帮助理解小波变换在时间-频率局部化分析中的实际应用。资源包共2个文件,包含1个m脚本与1个mat数据文件&#…

📰

C#扩展方法实战:为.NET Framework基础类型赋能

1. 项目概述:为什么给基础类型“加戏”是每个C#开发者迟早要走的路你有没有写过这样的代码:if (str ! null && str.Trim().Length > 0)?或者在处理时间时反复写DateTime.Now.AddHours(24).ToString("yyyy-MM-dd")&#…

📰

揭秘AnimeGen-T2V-A14B-Lightning-int4的双专家MoE架构:高噪声与低噪声模型如何分工

【免费下载链接】AnimeGen-T2V-A14B-Lightning-int4 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/AnimeGen-T2V-A14B-Lightning-int4 点击查看 免费下载 AnimeGen-T2V-A14B-Lightning-int4 是一款专为 Apple Silicon(MLX) 打…

📰

SciPy多重积分实战:从dblquad到nquad的数值积分指南

1. 先搞清楚:为什么学了二重积分却算不出来平时做科研或者写工程代码,最头疼的事情之一就是:公式推导完了,最后卡在一个二重积分上。手算吧,积分区域复杂一点就废了;查表吧,根本没那么巧的函数形…

📰

Django电信资费管理系统:资费模型与出账流程设计实践

前阵子整理旧项目,翻出一个用于内部业务演练的电信资费管理系统,路径名带了一串自动生成的随机后缀,项目代号就不提了,核心内容是典型的"Python Django"。当时做完这个模拟项目X挺感慨的:电信资费管理听起来…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬