Redis 面试必问,项目必用,但很多人的理解停在「缓存、快、单线程」六个字上。真到了线上排查大 key 拖垮集群、缓存雪崩打穿数据库的时候,才发现基础没打牢。
这篇分两部分:上篇从零开始,把 Redis 是什么、为什么快、五种数据结构怎么用讲清楚,读完能上手写业务;下篇进入 Pipeline、Lua 脚本、缓存三兄弟、集群架构和生产踩坑,读完能做选型、能排查问题。
Part 1:快速上手
Redis 凭什么快:单线程 + 内存 + IO 多路复用
Redis 是一个基于内存的键值数据库。三个设计决策让它快到可以做缓存层:
纯内存操作。 数据在内存里读写,不走磁盘 IO。一次内存随机读约 100ns,一次磁盘随机读约 10ms——差了 5 个数量级。
单线程执行命令。 Redis 用一个主线程串行执行所有命令。没有锁竞争、没有上下文切换、没有并发安全问题,CPU 缓存命中率也高。听起来反直觉——单线程怎么扛得住高并发?因为瓶颈不在 CPU,在网络 IO。内存操作本身就是纳秒级的,一个核跑满每秒能处理十几万条命令,绝大部分场景 CPU 根本不是瓶颈。
IO 多路复用。 Redis 用 epoll(Linux)/ kqueue(macOS)在一个线程里同时监听成千上万个客户端连接。哪个连接的数据到了就处理哪个,不用为每个连接开一个线程。
一个常见误解:Redis 不是完全单线程的。 Redis 4.0 开始,UNLINK(异步删大 key)、FLUSHDB ASYNC 等操作由后台线程执行;Redis 6.0 引入多线程 IO,让网络读写(解析请求、发送响应)可以由多个线程并行处理。但命令执行本身——读写数据的那一步——始终是单线程的,这保证了操作的原子性。
五种核心数据结构和典型场景
Redis 不只是 SET key value。五种数据结构各有适用场景,选对结构是写好 Redis 代码的第一步。
String:缓存和计数器
最基础的类型,值可以是字符串、整数或二进制数据,最大 512MB。
# 缓存:设置带过期时间的值SET user:1001:name "Alice" EX 3600 # 1 小时过期
# 计数器:原子递增SET article:2048:views 0INCR article:2048:views # 返回 1INCR article:2048:views # 返回 2INCRBY article:2048:views 10 # 返回 12
# 分布式锁的基础SET lock:order:1001 "uuid-abc" NX EX 30# NX = 不存在才设置,EX = 过期秒数INCR 是原子操作——单线程执行,不存在两个请求同时读到旧值再各自加 1 的问题。这是 Redis 做计数器比数据库 UPDATE views = views + 1 轻量得多的原因。
分布式锁用 SET NX EX 只是起点,完整的坑和方案见 分布式锁详解:数据库与 Redis 两条路线的实现、坑点与选型。
List:队列和最新列表
双向链表,两端操作 O(1),按索引访问 O(n)。
# 消息队列:左进右出LPUSH queue:tasks "task-1"LPUSH queue:tasks "task-2"RPOP queue:tasks # 返回 "task-1"(FIFO)
# 阻塞弹出:队列为空时等待,最多 30 秒BRPOP queue:tasks 30
# 最新列表:只保留最近 100 条LPUSH feed:user:1001 "article-xxx"LTRIM feed:user:1001 0 99 # 截断,只留前 100 个LRANGE feed:user:1001 0 9 # 取最新 10 条BRPOP 让消费者不需要轮询,队列空了就阻塞等待,有数据就立刻返回。比 while True: RPOP + sleep 省 CPU 也少延迟。
Hash:对象存储
一个 key 下挂多个 field-value 对,适合存结构化对象。
# 存储用户信息HSET user:1001 name "Alice" age 28 city "Shanghai"
# 读单个字段HGET user:1001 name # 返回 "Alice"
# 读所有字段HGETALL user:1001# 返回:name Alice age 28 city Shanghai
# 修改单个字段,不用覆盖整个对象HINCRBY user:1001 age 1 # age 变成 29和用 String 存 JSON 相比,Hash 的优势是可以读写单个字段,不用每次序列化/反序列化整个对象。对象有 20 个字段但每次只改 1 个的场景,Hash 明显更合适。
Hash 的内存优化当 field 数量少且值短时(默认 128 个 field 以内,每个值不超过 64 字节),Redis 底层用 listpack(旧版本是 ziplist)而不是哈希表存储,内存占用显著更小。这也是为什么「把大对象拆成小 Hash」是常见的内存优化手段。
Set:去重和集合运算
无序集合,元素唯一,支持交集、并集、差集。
# 文章标签SADD article:2048:tags "Redis" "NoSQL" "缓存"SMEMBERS article:2048:tags # 返回所有标签
# 去重:记录今天访问过的用户SADD uv:2026-09-01 "user:1001" "user:1002" "user:1001"SCARD uv:2026-09-01 # 返回 2(自动去重)
# 共同关注SADD follow:alice "bob" "charlie" "dave"SADD follow:bob "alice" "charlie" "eve"SINTER follow:alice follow:bob # 返回 "charlie"(交集)SINTER、SUNION、SDIFF 是 Redis 在集合运算上的独特优势。社交场景的「共同好友」「可能认识的人」,用 Set 一条命令搞定,自己写代码要拉两个列表再循环比对。
Sorted Set(ZSet):排行榜和延迟队列
和 Set 一样元素唯一,但每个元素关联一个 score(浮点数),按 score 排序。底层是跳表 + 哈希表,插入和查询都是 O(log N)。
# 排行榜ZADD leaderboard 95.5 "alice" 88.0 "bob" 92.3 "charlie"
# 取 Top 3(分数从高到低)ZREVRANGE leaderboard 0 2 WITHSCORES# 返回:alice 95.5, charlie 92.3, bob 88.0
# 查排名(从 0 开始,降序)ZREVRANK leaderboard "bob" # 返回 2(第三名)
# 延迟队列:score 存执行时间戳ZADD delay:queue 1725148800 "order:timeout:1001"ZADD delay:queue 1725148830 "order:timeout:1002"# 消费者轮询:取出已到期的任务ZRANGEBYSCORE delay:queue 0 1725148810# 返回 score ≤ 当前时间的元素延迟队列是 ZSet 的经典用法:订单超时关闭、定时通知,都可以把执行时间戳当 score 塞进去,消费者定时用 ZRANGEBYSCORE 捞到期的任务。比起 Timer 或 ScheduledExecutorService,好处是重启不丢、多实例共享。
不止五种:三个实用扩展类型
HyperLogLog:用 12KB 估算上亿 UV
统计页面独立访客数(UV),用 Set 存用户 ID 准确但吃内存。HyperLogLog 是概率数据结构,固定占 12KB 内存,标准误差 0.81%,可以统计 2^64 个不同元素。
PFADD uv:page:home "user:1001" "user:1002" "user:1001"PFCOUNT uv:page:home # 返回 2
# 合并多天的统计PFMERGE uv:page:home:week uv:page:home:mon uv:page:home:tue代价是只能近似计数,不能取出具体元素。适合对精度要求不高但数据量巨大的统计场景。
Bitmap:签到和状态标记
把 String 的每一位当布尔值用,一个 bit 表示一个状态。
# 用户签到:bit 偏移量 = 今天是今年的第几天SETBIT sign:user:1001:2026 244 1 # 第 244 天签到GETBIT sign:user:1001:2026 244 # 返回 1
# 统计全年签到天数BITCOUNT sign:user:1001:2026 # 返回签到总天数一个用户一年的签到记录只需要 365 bit ≈ 46 字节。100 万用户一年的签到数据约 44MB,用 Set 存每天的日期字符串,内存消耗是这个数字的几十倍。
Stream:带消费者组的消息队列
Redis 5.0 引入,弥补了 List 做队列和 Pub/Sub 的缺陷:支持消费者组、消息确认(ACK)、持久化、历史回溯。
# 生产者:追加消息XADD mystream * name "task-1" data "payload"# * 让 Redis 自动生成 ID(时间戳-序号)
# 创建消费者组,从头开始消费XGROUP CREATE mystream grp1 0
# 消费者读取(阻塞模式)XREADGROUP GROUP grp1 consumer-1 COUNT 1 BLOCK 5000 STREAMS mystream ># > 表示只读新消息
# 处理完后确认XACK mystream grp1 "1725148800000-0"Part 2 会展开 Stream 和 Pub/Sub 的对比。
持久化:RDB 快照 vs AOF 日志
Redis 数据在内存里,进程一挂数据就没了。持久化解决的就是这个问题,有两种机制。
RDB:定时快照
把某一时刻的全量数据序列化成二进制文件(dump.rdb)。
# redis.conf 配置save 900 1 # 900 秒内至少 1 次写入,触发快照save 300 10 # 300 秒内至少 10 次写入save 60 10000 # 60 秒内至少 10000 次写入优点: 文件紧凑,恢复速度快(直接加载二进制),适合做备份。
缺点: 两次快照之间的数据会丢失。save 60 10000 意味着最多丢 60 秒数据。而且 BGSAVE 需要 fork 子进程,内存大时 fork 本身就慢(后面踩坑部分会细说)。
AOF:追加写日志
每条写命令追加到日志文件(appendonly.aof),重启时重放命令恢复数据。
# redis.conf 配置appendonly yesappendfsync everysec # 每秒刷盘(默认,折中方案)# appendfsync always # 每条命令都刷盘,最安全但最慢# appendfsync no # 交给操作系统,最快但可能丢几秒优点: 最多丢 1 秒数据(everysec 模式),数据安全性高。
缺点: 文件比 RDB 大,恢复慢(要重放所有命令)。虽然有 AOF 重写(BGREWRITEAOF)来压缩文件,但日常体积仍然比 RDB 大。
混合持久化(推荐)
Redis 4.0 引入,AOF 重写时不再纯追加命令,而是先写一份 RDB 快照,再追加快照之后的增量命令。恢复时先加载 RDB 部分(快),再重放增量(少),兼顾了速度和安全性。
aof-use-rdb-preamble yes # 默认开启(Redis 4.0+)选型建议: 生产环境开混合持久化。不在乎丢几分钟数据的(比如纯缓存场景)可以只开 RDB。两种都不开,Redis 就是纯内存数据库,重启即清空。
过期与内存淘汰策略
过期删除:惰性 + 定期
给 key 设了 TTL 之后,Redis 不会在到期的瞬间立刻删除——那样需要一个定时器盯着每个 key,开销太大。实际是两种策略配合:
- 惰性删除: 访问一个 key 时检查是否过期,过期了才删。不访问就不删,可能有过期 key 占着内存。
- 定期删除: 每秒 10 次(默认),随机抽查一批设了 TTL 的 key,删掉已过期的。如果过期比例超过 25%,立刻再抽一批,直到比例降下来。
两者结合基本够用,但如果过期 key 积累速度超过定期清理速度(比如大量 key 同时到期),内存还是会涨。这时候靠内存淘汰策略兜底。
八种淘汰策略
当 Redis 内存达到 maxmemory 限制时,新写入会触发淘汰。maxmemory-policy 决定淘汰谁:
| 策略 | 淘汰范围 | 规则 |
|---|---|---|
noeviction | 不淘汰 | 写入直接报错(默认) |
allkeys-lru | 所有 key | 淘汰最近最少使用的 |
allkeys-lfu | 所有 key | 淘汰使用频率最低的 |
allkeys-random | 所有 key | 随机淘汰 |
volatile-lru | 设了 TTL 的 key | 淘汰最近最少使用的 |
volatile-lfu | 设了 TTL 的 key | 淘汰使用频率最低的 |
volatile-random | 设了 TTL 的 key | 随机淘汰 |
volatile-ttl | 设了 TTL 的 key | 淘汰剩余 TTL 最短的 |
怎么选:
- 纯缓存场景(数据丢了能回源),用
allkeys-lru或allkeys-lfu。LFU 更适合有热点数据的场景——热 key 即使短时间没被访问,频率计数器也不会马上归零。 - 缓存和持久数据混存(部分 key 不能丢),用
volatile-lru,只淘汰设了 TTL 的缓存 key,没设 TTL 的持久数据不会被动。 noeviction适合数据不能丢且宁可拒绝写入也不丢的场景,比如用 Redis 做消息队列。
Part 2:进阶实战
Pipeline:一次网络往返做一批事
Redis 命令执行本身是微秒级的,但每条命令都要走一次网络往返(RTT)。本地局域网 RTT 约 0.1ms,看着不多,但 1 万条命令串行发就是 1 秒。
Pipeline 的思路很简单:客户端把一批命令打包发过去,Redis 顺序执行后把结果一次性返回。省掉了中间的 N-1 次网络往返。
import redis
r = redis.Redis()
# 不用 Pipeline:10000 次网络往返for i in range(10000): r.set(f"key:{i}", i)
# 用 Pipeline:1 次网络往返pipe = r.pipeline()for i in range(10000): pipe.set(f"key:{i}", i)pipe.execute() # 一次性发送,一次性返回实测差异通常在 5-10 倍以上,RTT 越大差距越明显。
Pipeline 不是事务。 它只是批量发送,命令之间没有原子性保证——中间可能穿插其他客户端的命令。需要原子性,用事务或 Lua 脚本。
Pipeline 也不要一次塞太多命令(几万条以上),否则 Redis 的输出缓冲区会暴涨。建议按批次分片,每批几百到几千条。
事务与 Lua 脚本:原子操作的两条路
MULTI/EXEC 事务
MULTI # 开启事务SET balance:alice 100SET balance:bob 200EXEC # 原子执行MULTI 到 EXEC 之间的命令会被排队,EXEC 时一次性串行执行,中间不会被其他命令插入。
但 Redis 事务有两个关键限制:
- 不支持回滚。 如果中间某条命令执行失败(比如对 String 执行
INCR但值不是数字),其他命令照样执行。这和 MySQL 的事务完全不同。 - 命令入队时不执行,拿不到中间结果。 你没法在事务里读一个值、根据值做判断、再写入。
WATCH+MULTI/EXEC可以实现乐观锁,但用起来比较绕。
Lua 脚本:更灵活的原子操作
Redis 保证 Lua 脚本的执行是原子的——整个脚本跑完之前不会执行其他命令。而且脚本里可以读值、做判断、再写入,比事务灵活得多。
# 库存扣减:检查库存 → 扣减,原子完成EVAL "local stock = tonumber(redis.call('GET', KEYS[1]) or '0')if stock < tonumber(ARGV[1]) then return -1endreturn redis.call('DECRBY', KEYS[1], ARGV[1])" 1 stock:sku:1001 1这个脚本在 分布式锁详解:数据库与 Redis 两条路线的实现、坑点与选型 里的秒杀场景中出现过:把库存检查和扣减焊成一个原子操作,不需要分布式锁。
EVALSHA 优化: 每次发完整脚本浪费带宽。先用 SCRIPT LOAD 把脚本缓存到 Redis 拿到 SHA1,之后用 EVALSHA sha1 ... 调用,只传 40 字节的哈希值。
注意: Lua 脚本执行期间 Redis 不处理任何其他命令。脚本跑太久(比如里面有大循环)会阻塞整个 Redis。生产环境建议设 lua-time-limit(默认 5 秒),超时后其他客户端的命令会返回 BUSY 错误(但脚本不会被自动杀掉,需要 SCRIPT KILL 或等它跑完)。
发布订阅 vs Stream:消息队列两代方案
Pub/Sub:轻量但脆弱
# 订阅者SUBSCRIBE channel:order
# 发布者(另一个客户端)PUBLISH channel:order '{"orderId": 1001, "action": "created"}'Pub/Sub 是实时推送模型:消息发了就推给在线的订阅者,不存储。
三个致命缺陷:
- 无持久化。 订阅者不在线时的消息直接丢失,没有重试。
- 无 ACK 机制。 发布者不知道消息有没有被处理成功。
- 无消费者组。 同一条消息会推给所有订阅者(广播),做不了负载均衡。
适合做实时通知(比如配置变更广播、WebSocket 消息分发),不适合做可靠消息队列。
Stream:Redis 版的消息队列
Stream 解决了上面三个问题:
# 生产者XADD orders * orderId 1001 action "created"XADD orders * orderId 1002 action "created"
# 创建两个消费者组,模拟不同的下游服务XGROUP CREATE orders payment-group 0XGROUP CREATE orders notify-group 0
# 支付服务的消费者(负载均衡:同组内多个消费者分摊消息)XREADGROUP GROUP payment-group pay-worker-1 COUNT 1 BLOCK 5000 STREAMS orders >
# 处理完确认XACK orders payment-group "1725148800000-0"
# 查看未确认的消息(排查卡住的消费者)XPENDING orders payment-group| 能力 | Pub/Sub | Stream |
|---|---|---|
| 消息持久化 | 无 | 有,存在 Redis 里 |
| 消费者组 | 无 | 有,同组内负载均衡 |
| ACK 机制 | 无 | 有,未确认可重消费 |
| 历史回溯 | 无 | 有,XRANGE 读历史 |
| 阻塞读取 | 有 | 有 |
Stream 和 Kafka 的定位差异: Stream 适合中小规模、对延迟敏感、不想引入额外组件的场景。Kafka 在吞吐量(百万级 TPS)、多副本持久化、生态集成上是另一个量级。如果系统里已经有 Kafka,没必要用 Stream 替代;如果只是想在 Redis 里做一个轻量队列,Stream 够用。
缓存三兄弟:穿透、击穿、雪崩
这三个问题的共性是:请求没被缓存挡住,直接打到数据库。
缓存穿透:查的数据根本不存在
恶意请求或代码 bug 导致大量查询一个数据库里也没有的 key。缓存查不到 → 穿透到数据库 → 数据库也没有 → 不写缓存 → 下次还是穿透。
解法:
- 缓存空值。 数据库查不到时,缓存一个空标记(比如空字符串),设短 TTL(如 60 秒)。简单有效,但如果攻击者遍历大量不存在的 key,缓存会被垃圾数据撑满。
- 布隆过滤器。 在缓存前面加一层布隆过滤器(Redis 的
RedisBloom模块,或客户端侧实现),请求先过布隆过滤器——不存在的 key 直接拦截,存在的才查缓存和数据库。误判率可控(通常设 1% 以内),不会漏掉真实数据。
缓存击穿:热 key 过期瞬间被打穿
一个高频访问的 key 过期的那一刻,大量并发请求同时发现缓存没了,全部涌向数据库。
解法:
- 互斥锁。 缓存未命中时,先用
SET lock NX EX抢锁,抢到的那个请求去查数据库并回写缓存,其他请求等待或重试。 - 逻辑过期。 缓存不设真实 TTL(永不过期),而是在值里存一个逻辑过期时间。发现逻辑过期后,异步开一个线程去更新缓存,当前请求直接返回旧数据。用户看到的数据可能晚几秒,但不会打穿数据库。
两种方案取舍:互斥锁保证数据一致但有短暂延迟,逻辑过期保证可用性但数据可能短暂不一致。
缓存雪崩:大面积 key 同时过期
不是一个 key 的问题,而是大批 key 在同一时刻过期(比如缓存预热时统一设了相同的 TTL),或者 Redis 节点宕机。
解法:
- TTL 加随机偏移。 不要给同类 key 设一样的过期时间,加一个随机值打散:
TTL = base + random(0, 300)。 - 多级缓存。 本地缓存(如 Caffeine)+ Redis,Redis 挂了本地缓存还能顶一阵。
- 熔断降级。 数据库压力达到阈值时,直接返回默认值或错误提示,不再继续查询。保护数据库不被打死,等缓存恢复后再放开。
主从复制 → 哨兵 → Cluster:高可用三级跳
单节点 Redis 有两个硬伤:挂了就没服务,容量上限就是单机内存。三种架构分别解决不同层面的问题。
主从复制:读写分离
一个主节点负责写,多个从节点复制主节点的数据,负责读。
Master (读写) ──复制──> Slave 1 (只读) ──复制──> Slave 2 (只读)复制过程: 从节点第一次连接主节点时,主节点执行 BGSAVE 生成 RDB 快照发过去(全量同步);之后主节点的每条写命令实时发给从节点(增量同步)。
局限: 主节点挂了,没有自动切换机制——需要人工把从节点提升为主节点,然后改所有客户端的连接地址。凌晨三点告警的话,这个手动操作的时间就是业务中断的时间。
哨兵(Sentinel):自动故障转移
哨兵是一组独立的监控进程,盯着主节点:
- 主节点宕机,多个哨兵投票确认(避免误判)
- 从节点中选一个提升为新主节点
- 通知其他从节点改跟新主节点
- 通知客户端连接新主节点
Sentinel 1 ──监控──> Master ──复制──> Slave 1Sentinel 2 ──监控──> ──复制──> Slave 2Sentinel 3 ──监控──>哨兵解决了高可用问题,但没解决容量问题——数据还是在一台机器的内存里。
Redis Cluster:分片 + 高可用
Cluster 把数据分散到多个节点上,总容量 = 各节点内存之和。
核心机制: 16384 个哈希槽(slot),每个 key 通过 CRC16(key) % 16384 算出属于哪个槽,每个主节点负责一部分槽。
客户端路由: 客户端发命令到任意节点,如果 key 不在该节点的槽上,节点返回 MOVED 重定向告诉客户端去找谁。智能客户端(如 Jedis、Lettuce)会缓存槽映射关系,大部分请求一次就到位。
多 key 操作的限制: MGET、Lua 脚本里涉及多个 key 时,这些 key 必须在同一个槽上,否则报错。可以用 Hash Tag 强制路由:{user:1001}:name 和 {user:1001}:age 中大括号里的部分相同,一定落在同一个槽。
选型:
| 场景 | 方案 |
|---|---|
| 数据量小,需要高可用 | 主从 + 哨兵 |
| 数据量超过单机内存 | Cluster |
| 纯缓存,丢了能回源 | 单节点也行,加哨兵更稳 |
生产环境的五个高频坑
1. 大 key 拖垮集群
一个 Hash 里塞了 100 万个 field,一个 List 存了几十万条记录——读写它的时候,序列化、网络传输、内存分配都是放大的。DEL 一个大 key 甚至会阻塞 Redis 几秒(单线程,删除期间其他命令排队)。
怎么发现:
redis-cli --bigkeys # 扫描各类型的最大 keyredis-cli --memkeys # 按内存占用排序怎么治:
- 拆分:把一个大 Hash 按前缀拆成多个小 Hash。
- 删除用
UNLINK代替DEL,异步释放内存,不阻塞主线程。 - 预防:代码审查时就控制集合类型的元素数量上限。
2. 热 key 打穿单片
Cluster 模式下,一个被高频访问的 key 只在一个节点上。该节点的网络带宽和 CPU 被打满,其他节点闲着。
解法:
- 本地缓存:热 key 在应用侧缓存一份(Caffeine / Guava Cache),TTL 设短(比如 1 秒),大部分请求不到 Redis。
- 读写分离:热 key 的读请求分散到从节点。
- key 拆分:
hot_key拆成hot_key:1、hot_key:2…hot_key:N,随机读其中一个,请求打散到不同槽。
3. 慢查询:KEYS 和全量遍历
KEYS * 遍历整个键空间,O(N),key 多了直接卡死 Redis。生产环境绝对不要用。
替代: SCAN 命令,游标式分批遍历,每次只返回一小批,不阻塞。
SCAN 0 MATCH user:* COUNT 100 # 从游标 0 开始,每批约 100 个# 返回下一个游标和匹配的 key,游标为 0 表示遍历完成同理,SMEMBERS(返回集合所有元素)在大 Set 上也是灾难,用 SSCAN 替代。HGETALL 同理,用 HSCAN。
4. 持久化 fork 卡顿
BGSAVE(RDB 快照)和 BGREWRITEAOF 都需要 fork 子进程。fork 的耗时和 Redis 占用的内存量成正比——内存 10GB 时 fork 可能要几十毫秒,内存 50GB 时可能到几百毫秒。fork 期间主线程完全阻塞,所有请求排队。
缓解:
- 控制单个 Redis 实例的内存上限(建议不超过 10-15GB),数据量大时用 Cluster 分片。
- 在从节点上做 BGSAVE,不影响主节点服务。
- 关闭 THP(Transparent Huge Pages),它会让 fork 后的 copy-on-write 内存开销放大。
echo never > /sys/kernel/mm/transparent_hugepage/enabled5. 连接池耗尽
Redis 处理快,但 TCP 连接的建立和销毁不快。高并发时如果每个请求都新建连接,连接数打满 Redis 的 maxclients(默认 10000),后续请求直接被拒绝。
做法: 用连接池。Java 用 JedisPool / Lettuce(Lettuce 默认连接复用),Python 用 redis.ConnectionPool,Go 用 go-redis 的内置池。
import redis
# 连接池:最多维持 20 个连接pool = redis.ConnectionPool(host='localhost', port=6379, max_connections=20)r = redis.Redis(connection_pool=pool)连接池大小不是越大越好。Redis 单线程处理命令,连接再多也是排队。经验值:连接数 ≈ 业务线程数 + 少量冗余,通常 20-50 足够。
收尾:Redis 选型决策树
Redis 功能多,但不是所有场景都适合。
不该用 Redis 的场景:
- 数据量大但访问频率低——内存贵,这种数据放磁盘数据库更合适。
- 需要复杂查询(JOIN、聚合、全文搜索)——Redis 的查询能力有限,硬搞不如用 MySQL / Elasticsearch。
- 对数据一致性要求极高——Redis 的主从复制是异步的,故障切换时可能丢几秒数据。金融交易的资金流水不要只放 Redis 里。
一句话总结:Redis 是「快」和「灵活数据结构」的交汇点。用对了是利器,用错了是内存黑洞。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时