目录

Redis-01 Redis简介、安装与基础运维

1. Redis 是什么

Redis(REmote DIctionary Server,远程字典服务)是一个用 ANSI C 编写的开源、基于内存的键值型数据库,由意大利人 Salvatore Sanfilippo(网名 antirez)于 2009 年发布。

它最初的诞生动机非常朴素:antirez 在做一个网站实时统计产品 LLOOGG 时,MySQL 顶不住频繁的写入和统计查询,于是他干脆自己写了一个内存里的数据结构服务器。

一句话概括 Redis 的本质:

Redis 是一个把「数据结构」当作服务对外提供的内存数据库。

这句话是理解 Redis 一切设计的钥匙。别的 KV 存储(如 Memcached)里 value 只是一坨字节,你想改其中一部分只能全量取出、改完再全量写回。而 Redis 的 value 是有类型的数据结构——列表、哈希、集合、有序集合,服务端直接提供 LPUSHHINCRBYZRANGE 这样的原语,让你在服务端就地操作,省掉了网络往返和并发冲突。

1.1 核心特性

特性 说明
内存存储 数据主要放在内存,读写延迟在微秒级(通常 0.1ms 内)
丰富的数据类型 string、list、hash、set、zset,以及 Bitmap、HyperLogLog、GEO、Stream
单线程命令执行 命令串行执行,天然无竞态,所有单命令都是原子的
持久化 RDB 快照 + AOF 日志,支持混合持久化,重启不丢数据
高可用 主从复制、Sentinel 哨兵自动故障转移
可扩展 Cluster 集群分片,支持在线扩缩容
原子性扩展 Lua 脚本、Function、事务(MULTI/EXEC)让多命令组合成原子操作
过期机制 每个 key 可设 TTL,天然适合做缓存
发布订阅 / 消息流 pub/sub、Stream(支持消费组,可做轻量 MQ)

1.2 Redis 的典型使用场景

很多人把 Redis 简单等同于「缓存」,其实它的场景远不止于此:

  1. 缓存:最经典的场景,挡在数据库前面减少压力。
  2. 计数器:文章阅读数、点赞数、接口 QPS 统计——INCR 原子自增。
  3. 分布式锁SET key val NX EX 30,配合 Lua 释放。
  4. 限流:固定窗口(INCR + EXPIRE)、滑动窗口(zset)、令牌桶(Lua)。
  5. 排行榜:zset 天然按 score 排序,ZREVRANGE 直接取 Top N。
  6. 会话共享(Session):多台无状态应用服务器共享登录态。
  7. 消息队列:list 做简单队列,Stream 做带消费组的可靠队列。
  8. 社交关系:set 的 SINTER/SDIFF 算共同关注、可能认识的人。
  9. 地理位置:GEO 实现附近的人、附近的店。
  10. UV 统计:HyperLogLog 用 12KB 统计上亿基数。
  11. 布尔状态位图:Bitmap 做签到、活跃用户统计。
  12. 延迟队列:zset 以时间戳为 score,轮询到期任务。

1.3 Redis 与 Memcached 的对比

这是面试高频对比题:

维度 Redis Memcached
数据类型 丰富(9+ 种) 只有字符串
持久化 支持 RDB/AOF 不支持,重启数据全丢
线程模型 单线程命令执行(6.0 后 IO 多线程) 原生多线程
集群 官方 Cluster,支持数据分片 + 高可用 客户端分片,无原生集群
内存管理 自己封装 zmalloc,jemalloc 分配器 Slab 分配 + LRU,无内存碎片但有空间浪费
value 上限 512MB 默认 1MB
事务/脚本 支持 MULTI/Lua 不支持(仅有 CAS)
适用场景 通用缓存 + 数据结构服务 纯粹的大 value 简单缓存

结论:除了「纯大 value、超高并发简单读」这种极端场景,绝大多数情况选 Redis。


2. Redis 为什么这么快

单机 Redis 的 QPS 轻松到 10 万级别(redis-benchmark 在普通云主机上能跑到 10w+ ops/s)。原因是四点合力,不是单一原因:

2.1 纯内存操作

这是最主要的原因。内存的随机访问延迟约 100ns,而 SSD 是 100μs 级别、机械磁盘是 10ms 级别,差了 3~5 个数量级。数据库的性能瓶颈通常在磁盘 IO,Redis 直接绕开了这一点。

2.2 单线程避免了上下文切换与锁竞争

Redis 的命令处理是单线程的(注意:只是命令处理单线程,持久化 fork、AOF 刷盘、惰性删除、6.0 的 IO 读写都有其他线程)。

单线程带来的好处:

  • 无锁:不需要加锁保护共享数据,省掉了加锁/解锁开销和死锁风险。
  • 无上下文切换:一个线程一直跑,没有线程切换(一次上下文切换大约几微秒)。
  • 代码简单可靠:并发 bug 的一大来源被从设计上消除。

那单线程为什么不慢?因为 Redis 的瓶颈从来不是 CPU,而是内存和网络带宽。既然 CPU 不是瓶颈,多线程带来的收益就有限,反而引入复杂度。

2.3 IO 多路复用(Reactor 模型)

单线程要同时服务上万个客户端连接,靠的是 IO 多路复用:用 epoll(Linux)/ kqueue(BSD/macOS)/ evport / select 让一个线程同时监听大量 socket,只处理真正就绪的连接。

Redis 自己封装了一个极简的事件库 ae(约 1000 行代码),根据编译平台自动选择最优的多路复用实现。这部分第 5 篇会详细拆解。

2.4 高效的数据结构

Redis 对每一种数据类型都针对不同规模做了多种底层编码,小数据用紧凑结构(省内存、CPU 缓存友好),大数据用高效结构(保证时间复杂度):

  • 短字符串用 embstr(SDS 和 robj 一次分配、内存连续);
  • 小 hash/zset 用 listpack(连续内存,无指针开销);
  • 大 hash 用 hashtable(O(1) 查找);
  • 大 zset 用 skiplist(O(logN) 范围查询,实现比红黑树简单);
  • 全整数小 set 用 intset(有序数组 + 二分查找)。

这部分第 4 篇会详细讲。

小结成一句话:内存 + 单线程无锁 + IO 多路复用 + 精心设计的数据结构。回答面试题时四点都要提,并强调「瓶颈是内存和网络,不是 CPU」。


3. 版本演进史

了解版本演进对面试和实际选型都很有用,很多"Redis 不支持 XX"的说法其实是老版本的信息。

版本 时间 里程碑特性
1.0 2009 首个版本,string/list/set
2.0 2010 hash 类型、pub/sub、VM(后废弃)
2.6 2012 Lua 脚本、毫秒级过期精度、连接数上限提升
2.8 2013 部分重同步 PSYNC、keyspace notification、SCAN 命令
3.0 2015 Cluster 集群正式发布、新的 LRU 算法
3.2 2016 GEO 地理位置、quicklist 编码、SDS 优化、Lua 脚本改进
4.0 2017 模块系统(Module)混合持久化惰性删除 UNLINK、PSYNC2、MEMORY 命令
5.0 2018 Stream 数据类型、新的 ZPOP 命令、RESP3 雏形、Cluster 管理迁到 redis-cli
6.0 2020 多线程 IOACL 权限RESP3 协议、客户端缓存(Client-side caching)、TLS 支持
6.2 2021 大量命令补全(GETDELCOPYZRANGESTORESINTERCARD),过期语义修正
7.0 2022 Function(持久化脚本)多部分 AOF(Multi-part AOF)、Cluster 分片 pub/sub、listpack 替换 ziplist、命令级 ACL
7.2 2023 listpack 作为 list 的默认编码之一、复制优化、Function 增强
7.4 2024 哈希字段级过期(HEXPIRE)、许可证改为 RSALv2/SSPLv1
8.0 2025 性能大幅优化(部分场景 2x)、向量集合(Vector Set)、许可证增加 AGPLv3 选项,重新回归开源

关于许可证:2024 年 3 月 Redis 从 BSD 改为 RSALv2/SSPLv1(不再是 OSI 认可的开源),引发社区分裂,Linux 基金会牵头 fork 出 Valkey(由 AWS、Google、Oracle 等支持)。2025 年 5 月 Redis 8.0 增加 AGPLv3 选项部分挽回局面。生产选型时如果对协议敏感,可以关注 Valkey。

生产版本建议:新项目直接上 7.x(7.0/7.2 最成熟稳定),云厂商托管版通常提供 6.x/7.x。


4. 安装

4.1 Docker 安装(最推荐,开发环境首选)

# 拉取镜像并启动
docker run -d --name redis \
  -p 6379:6379 \
  -v /data/redis/data:/data \
  -v /data/redis/redis.conf:/usr/local/etc/redis/redis.conf \
  redis:7.2 \
  redis-server /usr/local/etc/redis/redis.conf --appendonly yes

# 进入容器用 cli
docker exec -it redis redis-cli

# 查看日志
docker logs -f redis

用 docker-compose 更清晰:

version: '3'
services:
  redis:
    image: redis:7.2
    container_name: redis
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - ./data:/data
      - ./redis.conf:/etc/redis/redis.conf
    command: redis-server /etc/redis/redis.conf

4.2 源码编译安装(生产环境推荐)

生产环境建议源码编译,可以自己控制版本和编译选项。

# 1. 安装依赖
yum install -y gcc gcc-c++ make tcl systemd-devel
# Ubuntu: apt install -y build-essential tcl pkg-config

# 2. 下载解压
cd /usr/local/src
wget https://download.redis.io/releases/redis-7.2.5.tar.gz
tar -zxvf redis-7.2.5.tar.gz
cd redis-7.2.5

# 3. 编译(USE_SYSTEMD=yes 让 systemd 能正确管理)
make USE_SYSTEMD=yes -j4

# 4. 跑一遍测试(可选,比较慢)
make test

# 5. 安装到 /usr/local/bin
make install PREFIX=/usr/local/redis

# 6. 准备目录和配置
mkdir -p /usr/local/redis/{conf,data,logs}
cp redis.conf /usr/local/redis/conf/

# 7. 启动
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf

编译后 src 目录下的主要可执行文件:

文件 作用
redis-server 服务端主程序
redis-cli 命令行客户端(也是集群管理工具)
redis-sentinel 哨兵(其实是 redis-server 的软链,用 sentinel 模式启动)
redis-benchmark 性能压测工具
redis-check-aof AOF 文件检查/修复
redis-check-rdb RDB 文件检查

4.3 配置 systemd 托管

cat > /etc/systemd/system/redis.service <<'EOF'
[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
Type=notify
User=redis
Group=redis
ExecStart=/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
ExecStop=/usr/local/redis/bin/redis-cli shutdown
Restart=always
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
EOF

# 注意:使用 Type=notify 时,配置文件里必须设置 supervised systemd
# 并且 daemonize 必须为 no

systemctl daemon-reload
systemctl enable redis
systemctl start redis
systemctl status redis

4.4 macOS

brew install redis
brew services start redis   # 后台常驻
redis-cli ping              # 应该返回 PONG

4.5 安装后必做的系统优化

这几项不做,生产环境一定踩坑:

# 1. 开启内存过量分配(否则 fork 做 RDB/AOF 重写时可能失败)
echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf

# 2. 提高 TCP 全连接队列(默认 128 太小,高并发建连会丢)
echo 'net.core.somaxconn = 1024' >> /etc/sysctl.conf
sysctl -p

# 3. 关闭透明大页 THP(会导致 fork 后写时复制的延迟毛刺)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 持久化:写进 /etc/rc.local 或用 tuned

# 4. 提高文件描述符上限
echo '* soft nofile 65535' >> /etc/security/limits.conf
echo '* hard nofile 65535' >> /etc/security/limits.conf

启动时如果 Redis 日志里出现 WARNING 开头的警告,基本都是这几项没做。


5. redis.conf 核心配置详解

配置文件是 Redis 运维的核心。下面按模块拆解生产环境最需要关注的配置项。

5.1 网络

# 监听地址。默认只监听本机,生产要么绑内网 IP,要么配合防火墙 + 密码
# 千万不要在公网无密码地 bind 0.0.0.0(会被挖矿脚本秒扫)
bind 127.0.0.1 10.0.0.5
port 6379

# 保护模式:bind 为空且无密码时,只允许本机访问。别随意关
protected-mode yes

# TCP 全连接队列长度,需要配合内核 net.core.somaxconn
tcp-backlog 511

# 客户端空闲多久断开(秒),0 = 永不断开。有连接池的应用建议 0 或较大值
timeout 0

# TCP keepalive 探活间隔(秒),用于及时发现半开连接
tcp-keepalive 300

5.2 通用

# 是否后台运行。用 systemd 托管(Type=notify)时必须为 no
daemonize no
supervised systemd

pidfile /var/run/redis_6379.pid

# 日志级别:debug / verbose / notice / warning
loglevel notice
logfile "/usr/local/redis/logs/redis.log"

# 数据库数量,默认 16(编号 0~15)。Cluster 模式只能用 db0
databases 16

5.3 持久化 - RDB

# 触发快照的条件:<秒> <变更 key 数>,满足任一即触发 bgsave
save 3600 1
save 300 100
save 60 10000
# 想完全关闭 RDB:save ""

# bgsave 出错后是否停止写入(默认 yes,是一种保护但会造成服务不可写)
stop-writes-on-bgsave-error yes

rdbcompression yes    # 用 LZF 压缩字符串对象
rdbchecksum yes       # CRC64 校验,损坏可发现,代价约 10% 性能

dbfilename dump.rdb
dir /usr/local/redis/data

5.4 持久化 - AOF

appendonly yes
appendfilename "appendonly.aof"
appenddirname "appendonlydir"   # 7.0+ 多部分 AOF 目录

# 刷盘策略(重点):
#   always   每条命令都 fsync,最安全最慢
#   everysec 每秒 fsync,默认,最多丢 1 秒数据
#   no       交给操作系统,最快最不安全
appendfsync everysec

# AOF 重写期间是否禁止 fsync(避免和重写抢磁盘 IO,但可能丢更多数据)
no-appendfsync-on-rewrite no

# 自动重写条件:文件比上次重写后大了 100% 且超过 64MB
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 混合持久化:重写时前半段用 RDB 格式,后半段追加 AOF 命令(4.0+,推荐 yes)
aof-use-rdb-preamble yes

5.5 内存与淘汰

# 最大内存。务必设置!不设的话内存会一直涨到被 OOM Killer 杀掉
# 建议设为物理内存的 60%~70%(留出 fork 时 COW 和缓冲区的空间)
maxmemory 4gb

# 淘汰策略(详见第 6 篇):
#   noeviction      不淘汰,写入时报错(默认)
#   allkeys-lru     所有 key 中淘汰最近最少使用
#   allkeys-lfu     所有 key 中淘汰最少频繁使用(4.0+,缓存场景推荐)
#   allkeys-random  随机淘汰
#   volatile-lru    仅在设了过期时间的 key 中用 LRU
#   volatile-lfu    仅在设了过期时间的 key 中用 LFU
#   volatile-random 仅在设了过期时间的 key 中随机
#   volatile-ttl    优先淘汰剩余 TTL 最短的
maxmemory-policy allkeys-lru

# LRU/TTL 淘汰的采样精度,越大越准但越耗 CPU
maxmemory-samples 5

5.6 客户端与安全

# 密码。生产必须设,且要足够长(Redis 每秒能试几十万次密码)
requirepass your_very_long_random_password

# 最大客户端连接数
maxclients 10000

# 客户端输出缓冲区限制:<class> <hard limit> <soft limit> <soft seconds>
# 普通客户端不限(0 0 0),从节点 256MB,pubsub 订阅者 32MB
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

# 重命名/禁用危险命令(生产强烈建议)
rename-command FLUSHALL ""
rename-command FLUSHDB  ""
rename-command KEYS     ""
rename-command CONFIG   CONFIG_9f2b7c

5.7 慢查询与延迟监控

# 超过多少微秒记入慢查询日志(默认 10000 = 10ms)
slowlog-log-slower-than 10000
# 慢日志最多保留条数
slowlog-max-len 128

# 延迟监控阈值(毫秒),超过就记录事件,0 = 关闭
latency-monitor-threshold 100

5.8 底层编码阈值

这些决定了数据类型用紧凑结构还是标准结构,直接影响内存占用(第 4 篇详解):

hash-max-listpack-entries 128     # 旧名 hash-max-ziplist-entries
hash-max-listpack-value 64
list-max-listpack-size 128
set-max-intset-entries 512
set-max-listpack-entries 128
set-max-listpack-value 64
zset-max-listpack-entries 128
zset-max-listpack-value 64

5.9 惰性删除(4.0+)

删除大 key 时如果同步释放内存,会阻塞主线程几百毫秒。开启惰性删除后,内存释放交给后台线程:

lazyfree-lazy-eviction yes      # 淘汰时异步释放
lazyfree-lazy-expire yes        # 过期删除时异步释放
lazyfree-lazy-server-del yes    # 隐式删除(如 rename 覆盖)时异步
replica-lazy-flush yes          # 从节点全量同步前清空数据时异步
lazyfree-lazy-user-del yes      # 6.0+,让 DEL 也表现得像 UNLINK

生产建议全部开 yes。


6. redis-cli 使用

6.1 连接

# 基本连接
redis-cli -h 127.0.0.1 -p 6379 -a password -n 0

# 密码写在命令行会有警告且泄露到 history,推荐用环境变量
export REDISCLI_AUTH=password
redis-cli -h 127.0.0.1

# 连接后再认证
redis-cli
127.0.0.1:6379> AUTH password
127.0.0.1:6379> PING
PONG

# 6.0+ ACL 多用户
redis-cli --user alice --pass secret

# 连接集群(-c 开启集群模式,会自动跟随重定向)
redis-cli -c -h 10.0.0.5 -p 7000

6.2 直接执行命令与管道

# 单条命令后即退出
redis-cli set name tom
redis-cli get name

# 从 stdin 批量执行(--pipe 是高性能批量导入,走 RESP 协议)
cat commands.txt | redis-cli --pipe

# 生成测试数据的经典写法
for i in $(seq 1 10000); do echo "SET key:$i val:$i"; done | redis-cli --pipe

6.3 高频运维参数

这几个参数是排查线上问题的利器:

# 每秒刷新一次核心统计(QPS、内存、连接数),像 top 一样
redis-cli --stat

# 采样内存中最大的 key(对每种类型分别找 Top 1),线上可用(走 SCAN 不阻塞)
redis-cli --bigkeys

# 采样每个 key 的实际内存占用(更精确但更慢)
redis-cli --memkeys

# 找热点 key(需要 maxmemory-policy 为 lfu 系列)
redis-cli --hotkeys

# 实时打印所有执行的命令(调试神器,但会有性能开销,生产慎用且要及时 Ctrl+C)
redis-cli monitor

# 测量延迟(持续 ping 并统计)
redis-cli --latency
redis-cli --latency-history      # 每 15 秒一个区间
redis-cli --latency-dist         # 延迟分布彩色图

# 测量系统固有延迟(跑 100 秒,判断是机器问题还是 Redis 问题)
redis-cli --intrinsic-latency 100

# 扫描并输出匹配的 key(内部用 SCAN,安全)
redis-cli --scan --pattern 'user:*' | head

# 从主节点实时同步 RDB + 命令流(可用来做数据迁移或备份)
redis-cli --rdb /tmp/dump.rdb

# 执行 Lua 脚本
redis-cli --eval script.lua key1 key2 , arg1 arg2

MONITOR 的开销:它会把所有命令都推给客户端,官方压测显示能让吞吐下降 50% 以上。线上排查建议改用 SLOWLOG 或短时间抓包。


7. 基础运维命令

7.1 服务状态

# 全量信息(最重要的运维命令,输出分很多 section)
INFO
INFO server         # 版本、启动时间、进程 ID、配置文件路径
INFO clients        # 连接数、阻塞客户端数、最大输入输出缓冲
INFO memory         # 内存使用、峰值、碎片率、分配器
INFO persistence    # RDB/AOF 状态、上次保存时间、是否正在 bgsave
INFO stats          # 总连接数、总命令数、QPS、命中率、过期/淘汰 key 数
INFO replication    # 主从角色、偏移量、从节点列表、复制延迟
INFO cpu            # CPU 使用
INFO commandstats   # 每个命令的调用次数、总耗时、平均耗时(性能分析必看)
INFO latencystats   # 6.2+ 每个命令的延迟百分位分布
INFO keyspace       # 每个 db 的 key 数量、带过期时间的 key 数量

几个必须会看的指标:

# 内存
used_memory_human            # Redis 认为自己用了多少
used_memory_rss_human        # 操作系统看到的物理内存占用
mem_fragmentation_ratio      # rss/used,>1.5 说明碎片严重,<1 说明用了 swap(危险!)

# 命中率(自己算)
keyspace_hits / (keyspace_hits + keyspace_misses)

# 是否有阻塞
latest_fork_usec             # 最近一次 fork 耗时(微秒),大于 100ms 要警惕
rdb_bgsave_in_progress       # 是否正在做 RDB
aof_rewrite_in_progress      # 是否正在重写 AOF

# 淘汰与拒绝
evicted_keys                 # 被淘汰的 key 数,持续增长说明内存不够
rejected_connections         # 因超过 maxclients 被拒绝的连接数

7.2 配置管理

# 查看配置(支持通配)
CONFIG GET maxmemory
CONFIG GET 'max*'
CONFIG GET '*'

# 动态修改(立即生效,但不写入配置文件,重启后丢失)
CONFIG SET maxmemory 8gb
CONFIG SET maxmemory-policy allkeys-lfu

# 把当前运行时配置写回配置文件(持久化修改)
CONFIG REWRITE

# 重置 INFO stats 里的统计计数器
CONFIG RESETSTAT

实战经验:动态改完配置一定要 CONFIG REWRITE,否则某次重启后配置回退,问题会以极其诡异的方式复现。

7.3 客户端管理

CLIENT LIST                     # 列出所有连接(addr、age、idle、cmd、name)
CLIENT INFO                     # 当前连接的信息
CLIENT ID                       # 当前连接 ID
CLIENT SETNAME myapp-worker-1   # 给连接起名,排查时能一眼看出是谁
CLIENT GETNAME

CLIENT KILL ID 12345            # 按 ID 杀连接
CLIENT KILL ADDR 10.0.0.9:52133 # 按地址杀
CLIENT KILL LADDR 10.0.0.5:6379 # 按本地地址杀(杀掉某个监听地址的所有连接)
CLIENT KILL TYPE normal         # 按类型杀(normal/master/replica/pubsub)

CLIENT NO-EVICT on              # 6.2+ 保护该连接不被内存压力踢掉
CLIENT UNPAUSE                  # 解除暂停
CLIENT PAUSE 5000               # 暂停所有客户端 5 秒(故障切换时用来"静默")

7.4 键空间管理

DBSIZE                # 当前 db 的 key 总数(O(1),直接读计数器)
SELECT 1              # 切换 db
SWAPDB 0 1            # 交换两个 db(原子,瞬间完成)

FLUSHDB               # 清空当前 db(危险!)
FLUSHALL              # 清空所有 db(更危险!)
FLUSHALL ASYNC        # 4.0+ 后台异步清空,不阻塞主线程

# 遍历 key:永远用 SCAN,不要用 KEYS
KEYS *                # 禁用!O(N) 会阻塞整个实例
SCAN 0 MATCH user:* COUNT 100

SCAN 的三个要点(面试常问):

  1. 游标式遍历,每次返回一个新游标,游标为 0 表示遍历结束;
  2. COUNT 是"建议值",不是精确返回条数,返回数量可能是 0(但只要游标不为 0 就要继续);
  3. 保证"完整性"但不保证"不重复":遍历过程中一直存在的 key 一定会被返回至少一次,但可能返回多次;遍历中新增/删除的 key 是否返回不确定。业务上要自己去重。

对应的类型内遍历命令:HSCANSSCANZSCAN

7.5 持久化与关闭

SAVE                  # 同步生成 RDB,阻塞主线程,禁止在线上直接用
BGSAVE                # fork 子进程生成 RDB
BGREWRITEAOF          # 触发 AOF 重写
LASTSAVE              # 上次成功保存的 Unix 时间戳

SHUTDOWN              # 关闭(若配置了 save 则先存 RDB)
SHUTDOWN NOSAVE       # 直接关闭不保存
SHUTDOWN SAVE         # 强制保存后关闭

DEBUG SLEEP 5         # 让主线程睡 5 秒(测试阻塞影响用)
DEBUG OBJECT key      # 查看 key 的内部编码、引用计数、序列化长度
OBJECT ENCODING key   # 查看编码(生产常用,比 DEBUG OBJECT 安全)
OBJECT FREQ key       # LFU 模式下查看访问频率
OBJECT IDLETIME key   # LRU 模式下查看空闲时间(秒)
OBJECT REFCOUNT key   # 引用计数(共享整数对象会大于 1)
MEMORY USAGE key      # 该 key 占用的字节数(含 key 本身和开销)

7.6 慢查询日志

SLOWLOG GET 10        # 获取最近 10 条慢查询
SLOWLOG LEN           # 慢查询条数
SLOWLOG RESET         # 清空

# 输出格式:
# 1) 1) (integer) 14          <- 唯一 ID
#    2) (integer) 1721890000  <- 发生时间戳
#    3) (integer) 15230       <- 耗时(微秒)
#    4) 1) "KEYS"             <- 命令及参数
#       2) "*"
#    5) "10.0.0.9:52133"      <- 客户端地址
#    6) "worker-1"            <- 客户端名字

注意:慢查询记录的时间不包括网络传输和排队等待时间,只是命令实际执行的时间。所以客户端观察到 100ms 超时但慢日志里什么都没有,说明问题在排队(前面有慢命令)或网络。


8. 一个完整的上手例子

用一个"文章点赞 + 阅读量 + 排行榜"的小场景把基础串起来:

# 文章基本信息用 hash 存
HSET article:1001 title "Redis 入门" author "tom" content "..."
HINCRBY article:1001 views 1              # 阅读量 +1

# 点赞用 set(天然去重,还能算交集)
SADD article:1001:likes user:2001 user:2002
SCARD article:1001:likes                  # 点赞数
SISMEMBER article:1001:likes user:2001    # 判断某人是否点过赞

# 热度排行榜用 zset
ZINCRBY hot:articles 1 article:1001       # 每次阅读热度 +1
ZREVRANGE hot:articles 0 9 WITHSCORES     # Top 10

# 用户最近浏览记录用 list(保留最新 20 条)
LPUSH user:2001:history article:1001
LTRIM user:2001:history 0 19

# 防重复提交/接口限流用 string + NX + EX
SET lock:submit:user:2001 1 NX EX 10      # 10 秒内不许重复提交

# 缓存文章详情,设 1 小时过期
SET cache:article:1001 '{"title":"..."}' EX 3600

# 看看这些 key 的编码
OBJECT ENCODING article:1001              # listpack(字段少)
OBJECT ENCODING article:1001:likes        # listpack(元素少且非整数)
OBJECT ENCODING hot:articles              # listpack

9. 高频面试题

Q1:Redis 为什么快?

四个原因合力:

  1. 纯内存操作,避开磁盘 IO(内存随机访问 ~100ns,SSD ~100μs,差 3 个数量级);
  2. 单线程执行命令,无锁、无线程上下文切换开销,也没有并发 bug;
  3. IO 多路复用(epoll/kqueue),单线程用 Reactor 模型高效管理上万连接;
  4. 精心设计的数据结构与多编码机制,小数据用紧凑内存结构(listpack/intset/embstr),大数据用高效结构(hashtable/skiplist)。

关键补充:Redis 的瓶颈是内存和网络带宽,不是 CPU,所以单线程不但不慢,反而是最优权衡。

Q2:Redis 是单线程的吗?

要分清"命令执行"和"整个进程"

  • 命令执行是单线程:一个主线程负责接收命令、执行命令、返回结果,这保证了单命令的原子性。
  • 但进程从来不是只有一个线程
    • 4.0 引入后台线程处理 UNLINK 大 key 释放、FLUSHALL ASYNC、AOF 刷盘(bio 线程);
    • 6.0 引入 IO 多线程,把 socket 的读取/解析和结果写回交给多个 IO 线程并行做,但命令执行仍然在主线程串行完成
    • RDB/AOF 重写是 fork 出的子进程(不是线程)。

所以标准回答:「Redis 的命令处理是单线程的,但 Redis 服务是多线程的。6.0 的多线程只是把网络 IO 并行化,命令执行依旧单线程串行。」

Q3:Redis 和 Memcached 的区别?

  • 数据类型:Redis 有 9+ 种(string/list/hash/set/zset/Bitmap/HLL/GEO/Stream),Memcached 只有字符串;
  • 持久化:Redis 有 RDB/AOF,Memcached 重启数据全丢;
  • 线程模型:Redis 命令单线程,Memcached 原生多线程;
  • 集群:Redis 有官方 Cluster 支持分片和高可用,Memcached 靠客户端一致性哈希分片;
  • value 大小:Redis 512MB,Memcached 默认 1MB;
  • 高级能力:Redis 有事务、Lua、pub/sub、过期通知、主从复制,Memcached 都没有。

Q4:Redis 有哪些数据类型?

5 种基本类型:string、list、hash、set、zset(sorted set)

扩展类型:Bitmap(基于 string 的位操作)、HyperLogLog(基数统计,12KB 存上亿)、GEO(基于 zset 的地理位置)、Stream(5.0 的消息流,支持消费组)、Bitfield(位域操作),8.0 还引入了 Vector Set(向量集合)。

注意 Bitmap/HLL 底层就是 string,GEO 底层就是 zset,它们是"命令集"而不是新的底层类型。

Q5:为什么线上禁用 KEYS 命令?用什么替代?

KEYS pattern 的时间复杂度是 O(N),N 是整个实例的 key 总数。因为 Redis 单线程执行命令,一次 KEYS * 在百万级 key 的实例上可能阻塞几百毫秒到几秒,期间所有其他请求全部排队,表现为整个服务雪崩式超时。

替代方案是 SCAN:游标式增量遍历,每次只返回少量结果,把一次长阻塞拆成多次短操作。要注意 SCAN 保证「遍历期间始终存在的 key 一定被返回」,但可能重复返回,业务侧要去重。同理 HGETALL(大 hash)、SMEMBERS(大 set)、LRANGE 0 -1(大 list)都要用对应的 HSCAN/SSCAN/分段 LRANGE 替代。

Q6:maxmemory 应该设多大?

建议物理内存的 60%~70%,原因:

  1. fork 时的写时复制(COW):做 RDB 或 AOF 重写时 fork 子进程,父进程继续写入会触发页面复制,极端情况(全量写)内存翻倍。虽然实际很少翻倍,但要留足余量;
  2. 额外开销:客户端输入输出缓冲区、复制积压缓冲区、AOF 缓冲区都不算在 used_memory 的数据部分里;
  3. 内存碎片mem_fragmentation_ratio 通常 1.0~1.5,实际物理占用比逻辑用量高。

如果不设 maxmemory,Redis 会一直申请内存直到被系统 OOM Killer 杀掉,比触发淘汰糟糕得多。

Q7:mem_fragmentation_ratio 小于 1 说明什么?

说明 Redis 的一部分内存被换出到了 swap 分区(操作系统看到的物理内存 RSS 小于 Redis 自认为用的内存)。这是非常危险的信号:内存访问变成磁盘访问,延迟会从微秒级恶化到毫秒甚至十毫秒级,实例基本等于不可用。

处理:立刻检查 free -m/proc/<pid>/smaps 确认 swap 用量,减少数据量或扩容内存,生产环境建议干脆关闭 swap(或至少把 vm.swappiness 设为 1)。

Q8:Redis 默认有多少个数据库?生产该怎么用?

默认 16 个(databases 16,编号 0~15),用 SELECT n 切换。

生产环境不建议用多 db 做业务隔离,原因:

  1. Cluster 模式只支持 db0,用了多 db 以后没法平滑迁到集群;
  2. 多个 db 共享同一个线程和内存,隔离是假的——一个 db 的慢命令照样阻塞全部;
  3. FLUSHDB 之类的操作容易误伤,运维和监控(很多工具默认只看 db0)都更麻烦。

正确做法是用 key 前缀做命名空间(如 order:user:),或者干脆部署多个实例做物理隔离。

Q9:CONFIG SET 修改的配置重启还在吗?

不在。CONFIG SET 只改运行时内存里的配置,重启后从配置文件重新加载,修改丢失。要持久化必须执行 CONFIG REWRITE(把当前运行配置回写到启动时用的那个配置文件),或者手动改配置文件。

注意 CONFIG REWRITE 要求启动时是指定了配置文件的,如果 Redis 是无配置文件裸启动的,这个命令会报错。

Q10:vm.overcommit_memory = 1 是干什么的?为什么必须设?

Linux 默认 overcommit_memory = 0,内核会用启发式算法判断内存申请是否"合理",不合理就直接拒绝。

Redis 在做 BGSAVE / BGREWRITEAOF 时要 fork()。虽然 fork 用的是写时复制、并不真的立刻复制全部内存,但内核在 overcommit_memory=0 下会按最坏情况估算(认为需要和父进程一样多的内存),当 Redis 已用内存超过物理内存一半时,fork 就可能直接失败,日志报 Can't save in background: fork: Cannot allocate memory,导致持久化失效。

设为 1 表示"总是允许过量分配",让 fork 成功,实际内存按需分配。这是 Redis 官方明确要求的配置。

Q11:THP(透明大页)为什么要关闭?

THP 把内存页从 4KB 变成 2MB,本意是减少 TLB miss 提升性能。但对 Redis 有害:

fork 后父进程写入会触发写时复制,COW 的粒度是页。4KB 页时改一个 key 只复制 4KB,2MB 页时要复制 2MB——复制量放大 512 倍。结果是 RDB/AOF 重写期间内存暴涨、延迟出现明显毛刺(可能几十毫秒),甚至 fork 内存不足。

所以启动时 Redis 会检测并打印警告,生产必须 echo never > /sys/kernel/mm/transparent_hugepage/enabled

Q12:SLOWLOG 记录的时间包含哪些部分?

只包含命令实际执行的时间,不包含:

  • 客户端到服务端的网络传输时间;
  • 命令在队列里等待前面命令执行完的排队时间;
  • 结果写回客户端的时间。

这解释了一个常见困惑:客户端大量超时报警,但慢日志里干干净净。这时问题通常是「有个别慢命令(或 fork、AOF 刷盘)造成主线程阻塞,后面的正常命令全在排队」或者「网络抖动 / 客户端 GC」。排查要结合 --latency--intrinsic-latencyINFO commandstatslatest_fork_usec 一起看。


小结

  • Redis 的本质是把数据结构当作服务提供的内存数据库,这是它区别于 Memcached 等 KV 存储的根本。
  • 快的四个原因:纯内存 + 单线程无锁 + IO 多路复用 + 多编码的高效数据结构;核心前提是瓶颈在内存和网络,不在 CPU
  • “单线程"指的是命令执行单线程;后台有 bio 线程(异步删除、AOF 刷盘)、6.0 有 IO 多线程、持久化用 fork 子进程。
  • 生产安装后必做四件事:vm.overcommit_memory=1net.core.somaxconn 调大、关闭 THP、提高 nofile 上限。
  • 配置重点:maxmemory(物理内存 60%~70%)+ maxmemory-policyappendfsync everyseclazyfree-* 全开、requirepass、危险命令 rename。
  • CONFIG SET 后记得 CONFIG REWRITE,否则重启配置回退。
  • 遍历永远用 SCAN 系列,禁用 KEYS;生产禁用/重命名 FLUSHALLFLUSHDBCONFIG
  • 排查工具箱:INFO(memory/stats/commandstats/persistence)、SLOWLOG--latency--bigkeys--stat--intrinsic-latency
  • 危险信号:mem_fragmentation_ratio < 1(用了 swap)、evicted_keys 持续增长(内存不够)、latest_fork_usec 过大(fork 阻塞)、rejected_connections > 0(连接数超限)。
  • 不要用多 db 做业务隔离,用 key 前缀或多实例。