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 是有类型的数据结构——列表、哈希、集合、有序集合,服务端直接提供 LPUSH、HINCRBY、ZRANGE 这样的原语,让你在服务端就地操作,省掉了网络往返和并发冲突。
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 简单等同于「缓存」,其实它的场景远不止于此:
- 缓存:最经典的场景,挡在数据库前面减少压力。
- 计数器:文章阅读数、点赞数、接口 QPS 统计——
INCR原子自增。 - 分布式锁:
SET key val NX EX 30,配合 Lua 释放。 - 限流:固定窗口(
INCR+EXPIRE)、滑动窗口(zset)、令牌桶(Lua)。 - 排行榜:zset 天然按 score 排序,
ZREVRANGE直接取 Top N。 - 会话共享(Session):多台无状态应用服务器共享登录态。
- 消息队列:list 做简单队列,Stream 做带消费组的可靠队列。
- 社交关系:set 的
SINTER/SDIFF算共同关注、可能认识的人。 - 地理位置:GEO 实现附近的人、附近的店。
- UV 统计:HyperLogLog 用 12KB 统计上亿基数。
- 布尔状态位图:Bitmap 做签到、活跃用户统计。
- 延迟队列: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 | 多线程 IO、ACL 权限、RESP3 协议、客户端缓存(Client-side caching)、TLS 支持 |
| 6.2 | 2021 | 大量命令补全(GETDEL、COPY、ZRANGESTORE、SINTERCARD),过期语义修正 |
| 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 的三个要点(面试常问):
- 游标式遍历,每次返回一个新游标,游标为 0 表示遍历结束;
- COUNT 是"建议值",不是精确返回条数,返回数量可能是 0(但只要游标不为 0 就要继续);
- 保证"完整性"但不保证"不重复":遍历过程中一直存在的 key 一定会被返回至少一次,但可能返回多次;遍历中新增/删除的 key 是否返回不确定。业务上要自己去重。
对应的类型内遍历命令:HSCAN、SSCAN、ZSCAN。
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 为什么快?
四个原因合力:
- 纯内存操作,避开磁盘 IO(内存随机访问 ~100ns,SSD ~100μs,差 3 个数量级);
- 单线程执行命令,无锁、无线程上下文切换开销,也没有并发 bug;
- IO 多路复用(epoll/kqueue),单线程用 Reactor 模型高效管理上万连接;
- 精心设计的数据结构与多编码机制,小数据用紧凑内存结构(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 出的子进程(不是线程)。
- 4.0 引入后台线程处理
所以标准回答:「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%,原因:
- fork 时的写时复制(COW):做 RDB 或 AOF 重写时 fork 子进程,父进程继续写入会触发页面复制,极端情况(全量写)内存翻倍。虽然实际很少翻倍,但要留足余量;
- 额外开销:客户端输入输出缓冲区、复制积压缓冲区、AOF 缓冲区都不算在
used_memory的数据部分里; - 内存碎片:
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 做业务隔离,原因:
- Cluster 模式只支持 db0,用了多 db 以后没法平滑迁到集群;
- 多个 db 共享同一个线程和内存,隔离是假的——一个 db 的慢命令照样阻塞全部;
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-latency、INFO commandstats 和 latest_fork_usec 一起看。
小结
- Redis 的本质是把数据结构当作服务提供的内存数据库,这是它区别于 Memcached 等 KV 存储的根本。
- 快的四个原因:纯内存 + 单线程无锁 + IO 多路复用 + 多编码的高效数据结构;核心前提是瓶颈在内存和网络,不在 CPU。
- “单线程"指的是命令执行单线程;后台有 bio 线程(异步删除、AOF 刷盘)、6.0 有 IO 多线程、持久化用 fork 子进程。
- 生产安装后必做四件事:
vm.overcommit_memory=1、net.core.somaxconn调大、关闭 THP、提高nofile上限。 - 配置重点:
maxmemory(物理内存 60%~70%)+maxmemory-policy、appendfsync everysec、lazyfree-*全开、requirepass、危险命令 rename。 CONFIG SET后记得CONFIG REWRITE,否则重启配置回退。- 遍历永远用
SCAN系列,禁用KEYS;生产禁用/重命名FLUSHALL、FLUSHDB、CONFIG。 - 排查工具箱:
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 前缀或多实例。
xingliuhua