一、Redis 基础

1. Redis 是什么?

- 开源的内存数据存储系统
- 支持多种数据结构:String, Hash, List, Set, Sorted Set, Bitmaps等
- 可用作数据库、缓存、消息中间件
- 单线程模型(处理命令),多线程处理网络IO(6.0+)
- 高性能:10万+ QPS

2. Redis 数据类型及应用场景

数据类型

底层结构

应用场景

示例命令

String

SDS

缓存、计数器、分布式锁

SET/GET/INCR

Hash

哈希表

对象存储、购物车

HSET/HGET/HGETALL

List

双向链表/压缩列表

消息队列、最新列表

LPUSH/RPOP/LRANGE

Set

哈希表/整数集合

标签、共同好友、抽奖

SADD/SMEMBERS/SINTER

Sorted Set

跳跃表+哈希表

排行榜、延时队列

ZADD/ZRANGE/ZREVRANGE

Bitmap

String

签到、用户在线状态

SETBIT/GETBIT/BITCOUNT

HyperLogLog

特殊结构

UV统计

PFADD/PFCOUNT

GEO

Sorted Set

地理位置

GEOADD/GEODIST

Stream

列表

消息队列

XADD/XREAD

二、数据结构与内部实现

1. Redis 对象系统

// RedisObject
typedef struct redisObject {
    unsigned type:4;      // 类型:string, hash, list等
    unsigned encoding:4;  // 编码:int, embstr, raw, hashtable等
    unsigned lru:LRU_BITS; // LRU时间或LFU频率
    int refcount;         // 引用计数
    void *ptr;           // 指向实际数据的指针
} robj;

2. String 类型实现

# 三种编码:
1. int: 保存整数值,直接存储在ptr
2. embstr: 长度<=44字节的字符串,连续内存
3. raw: 长度>44字节的字符串,SDS动态字符串

# SDS优点:
1. O(1)获取长度
2. 二进制安全
3. 减少内存分配次数
4. 兼容C字符串

3. Hash 类型实现

# 两种编码:
1. ziplist(压缩列表):元素少且值小
   - 元素数量<512
   - 所有值<64字节
2. hashtable(哈希表):不满足ziplist条件时转换

4. List 类型实现

# 两种编码:
1. ziplist(压缩列表):元素少且值小
   - 元素数量<512
   - 所有值<64字节
2. linkedlist(双向链表):3.2版本后废弃
3. quicklist(快速列表):3.2版本后默认
   - ziplist + 双向链表组合

5. Set 类型实现

# 两种编码:
1. intset(整数集合):元素少且都是整数
   - 元素数量<512
   - 元素都是整数
2. hashtable(哈希表):value为NULL的哈希表

6. Sorted Set 类型实现

# 两种编码:
1. ziplist(压缩列表):元素少
   - 元素数量<128
   - 所有成员<64字节
2. skiplist + hashtable(跳跃表+哈希表)
   - 跳跃表:按score排序
   - 哈希表:O(1)查找成员

三、持久化机制

1. RDB(Redis Database)

# 原理:
- 在指定时间间隔生成数据快照
- 二进制格式,紧凑
- 主进程fork子进程进行持久化

# 配置:
save 900 1      # 900秒内至少1个key变化
save 300 10     # 300秒内至少10个key变化
save 60 10000   # 60秒内至少10000个key变化

# 优点:
- 适合备份和灾难恢复
- 恢复速度快
- 最大化Redis性能

# 缺点:
- 可能丢失最后几分钟数据
- fork过程可能阻塞

2. AOF(Append Only File)

# 原理:
- 记录每个写操作命令
- 先执行命令再记录日志
- 通过重写压缩AOF文件

# 同步策略:
appendfsync always    # 每个命令都同步,最安全,性能差
appendfsync everysec  # 每秒同步,推荐
appendfsync no        # 操作系统决定,性能最好,可能丢失数据

# 重写机制:
- 自动触发:AOF文件超过阈值
- 手动触发:BGREWRITEAOF
- 原理:分析现有数据,生成新的AOF

# 优点:
- 数据更安全,最多丢失1秒数据
- 可读性高

# 缺点:
- 文件体积大
- 恢复速度慢

3. RDB vs AOF

# 对比:
| 特性 | RDB | AOF |
|------|-----|-----|
| 持久化方式 | 快照 | 日志 |
| 数据安全 | 可能丢失数据 | 相对安全 |
| 恢复速度 | 快 | 慢 |
| 文件大小 | 小 | 大 |
| 对性能影响 | fork时阻塞 | 写AOF时影响 |
| 默认开启 | 是 | 否 |

# 混合持久化(4.0+):
- 开启:aof-use-rdb-preamble yes
- RDB快照 + AOF增量
- 兼具两者优点

四、内存管理与淘汰策略

1. 内存分配

# Jemalloc分配器:
- 减少内存碎片
- 多线程优化
- 自动内存管理

2. 内存优化

# 配置优化:
maxmemory 1gb           # 最大内存限制
maxmemory-policy allkeys-lru  # 淘汰策略
hash-max-ziplist-entries 512  # Hash使用ziplist的最大元素数
hash-max-ziplist-value 64     # Hash使用ziplist的最大值长度

# 使用建议:
1. 使用合适的数据类型
2. 控制key长度
3. 使用批量操作
4. 开启内存碎片整理

3. 淘汰策略

# 8种策略:
1. noeviction: 不淘汰,写操作报错(默认)
2. allkeys-lru: 从所有key中淘汰最近最少使用
3. volatile-lru: 从设置过期时间的key中淘汰LRU
4. allkeys-random: 随机淘汰所有key
5. volatile-random: 随机淘汰有过期时间的key
6. volatile-ttl: 淘汰即将过期的key
7. allkeys-lfu: 从所有key中淘汰最不经常使用(4.0+)
8. volatile-lfu: 从有过期时间的key中淘汰LFU(4.0+)

# LRU vs LFU:
- LRU: 最近最少使用,适合热点数据变化
- LFU: 最不经常使用,适合长期热点数据

五、主从复制

1. 复制原理

# 同步过程:
1. 从库发送SYNC命令
2. 主库执行BGSAVE生成RDB
3. 主库发送RDB给从库
4. 从库加载RDB
5. 主库发送缓冲区命令
6. 命令传播阶段

# 全量同步 vs 增量同步:
- 全量同步:初次连接或断开太久
- 增量同步:断线重连,通过复制偏移量

2. 配置示例

# 主库配置
bind 0.0.0.0
port 6379
requirepass masterpass

# 从库配置
slaveof 127.0.0.1 6379
masterauth masterpass
slave-read-only yes

3. 复制问题

# 常见问题:
1. 数据不一致:网络延迟、主从延迟
2. 复制风暴:多个从库同时全量同步
3. 脑裂:主库假死,多个主库

# 解决方案:
1. 合理设置repl-backlog-size
2. 使用树状复制结构
3. 合理设置min-slaves-to-write

六、哨兵模式

1. 哨兵功能

# 核心功能:
1. 监控:检查主从是否正常
2. 通知:API通知故障
3. 自动故障转移:主库故障时选举新主
4. 配置提供者:客户端获取主库地址

2. 配置示例

# sentinel.conf
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

3. 故障转移流程

1. 主观下线:单个哨兵认为主库下线
2. 客观下线:多个哨兵确认主库下线
3. 选举Leader哨兵
4. Leader哨兵选择新主库
5. 切换主从关系
6. 通知其他从库和新主库

七、集群模式

1. 集群架构

# 数据分片:
- 16384个哈希槽
- 每个节点负责部分槽
- 客户端路由:CRC16(key) % 16384

# 节点类型:
- 主节点:处理读写,故障转移
- 从节点:复制主节点,故障时升级

2. 集群搭建

# 创建集群
redis-cli --cluster create \
  127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
  127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
  --cluster-replicas 1

# 查看集群信息
redis-cli -c -p 7001 cluster nodes
redis-cli -c -p 7001 cluster info

3. 集群特性

# 优点:
- 自动数据分片
- 主从自动切换
- 可线性扩展
- 高可用性

# 限制:
- 不支持多数据库,只有db0
- 批量操作受限(需在同一节点)
- 事务受限
- 不支持多键操作(除非在相同节点)

八、事务与Lua脚本

1. 事务

# 事务命令
MULTI          # 开启事务
SET key1 value1
SET key2 value2
EXEC           # 执行事务
DISCARD        # 取消事务
WATCH key      # 监视key,乐观锁

2. 事务特性

# 特点:
1. 原子性:要么全部执行,要么全部不执行
2. 隔离性:事务中的命令顺序执行
3. 不支持回滚:执行失败后继续执行
4. 不满足一致性:中途失败不保证数据一致

3. Lua脚本

-- 示例:限流脚本
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
    return 0
else
    redis.call("INCRBY", key, 1)
    redis.call("EXPIRE", key, 60)
    return 1
end
# 执行Lua脚本
redis-cli --eval script.lua key1 key2 , arg1 arg2
# 或
EVAL "script" numkeys key1 key2 arg1 arg2

九、缓存设计与问题

1. 缓存穿透

# 问题:查询不存在的数据,请求直达数据库
# 解决方案:
1. 缓存空值:SET key null 5
2. 布隆过滤器:判断key是否存在
3. 参数校验:过滤非法请求

2. 缓存雪崩

# 问题:大量key同时过期,请求直达数据库
# 解决方案:
1. 设置随机过期时间
2. 热点数据永不过期
3. 集群部署,主从复制
4. 限流降级

3. 缓存击穿

# 问题:热点key过期,大量请求直达数据库
# 解决方案:
1. 互斥锁:SETNX锁
2. 永不过期+异步更新
3. 逻辑过期:设置业务过期时间

4. 缓存更新策略

# 1. Cache Aside(旁路缓存)
- 读:先读缓存,无则读DB,再写缓存
- 写:先写DB,再删缓存

# 2. Read/Write Through
- 应用只操作缓存
- 缓存同步写DB

# 3. Write Behind
- 应用只操作缓存
- 缓存异步批量写DB

十、性能优化

1. 性能调优

# 配置优化:
1. 设置最大内存和淘汰策略
2. 合理设置持久化策略
3. 优化网络配置
4. 调整内核参数

# 使用优化:
1. 使用Pipeline批量操作
2. 避免大key(>10KB)
3. 使用合适的数据结构
4. 合理设置过期时间

2. Pipeline vs 事务

# Pipeline:
- 批量发送命令,减少RTT
- 不保证原子性
- 性能提升明显

# 事务:
- 命令打包执行
- 保证原子性
- 性能低于Pipeline

3. 大key优化

# 查找大key
redis-cli --bigkeys
redis-cli --memkeys

# 分析内存
redis-cli -p 6379 --memkeys --largest 10
INFO memory

十一、监控与运维

1. 监控命令

# 实时监控
redis-cli --stat        # 统计信息
redis-cli --bigkeys     # 大key分析
redis-cli monitor       # 监控命令
redis-cli --latency     # 延迟测试
redis-cli --intrinsic-latency 100  # 内部延迟

# 状态信息
INFO                   # 全部信息
INFO memory           # 内存信息
INFO clients          # 客户端信息
INFO stats            # 统计信息
INFO replication      # 复制信息
INFO cpu              # CPU信息

2. 性能指标

# 关键指标:
1. 内存使用率:used_memory/maxmemory
2. 命中率:keyspace_hits/(keyspace_hits+keyspace_misses)
3. 连接数:connected_clients
4. 命令处理:instantaneous_ops_per_sec
5. 网络流量:total_net_input_bytes/total_net_output_bytes
6. 延迟:响应时间

3. 运维命令

# 慢查询
CONFIG SET slowlog-log-slower-than 10000
CONFIG SET slowlog-max-len 128
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET

# 客户端管理
CLIENT LIST
CLIENT SETNAME client1
CLIENT GETNAME
CLIENT KILL ip:port
CLIENT PAUSE 1000  # 暂停客户端1000ms

# 键管理
KEYS pattern      # 慎用,阻塞
SCAN cursor [MATCH pattern] [COUNT count]  # 非阻塞遍历
OBJECT encoding key
OBJECT refcount key
OBJECT idletime key

十二、应用场景

1. 分布式锁

-- 加锁
SET lock_key unique_value NX PX 30000

-- 解锁Lua脚本
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

2. 限流

-- 令牌桶算法
local key = KEYS[1]
local rate = tonumber(ARGV[1])  -- 令牌生成速率
local capacity = tonumber(ARGV[2])  -- 桶容量
local now = tonumber(ARGV[3])  -- 当前时间
local requested = tonumber(ARGV[4])  -- 请求令牌数

local fill_time = capacity / rate
local ttl = math.floor(fill_time * 2)
local last_tokens = tonumber(redis.call("get", key .. ":tokens"))
if last_tokens == nil then
    last_tokens = capacity
end

local last_refreshed = tonumber(redis.call("get", key .. ":timestamp"))
if last_refreshed == nil then
    last_refreshed = 0
end

local delta = math.max(0, now - last_refreshed)
local filled_tokens = math.min(capacity, last_tokens + (delta * rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
    new_tokens = filled_tokens - requested
end

redis.call("setex", key .. ":tokens", ttl, new_tokens)
redis.call("setex", key .. ":timestamp", ttl, now)

return allowed and 1 or 0

3. 排行榜

# 添加分数
ZADD leaderboard 1000 "user1"
ZADD leaderboard 800 "user2"

# 获取排名
ZREVRANK leaderboard "user1"  # 从高到低排名
ZRANK leaderboard "user1"     # 从低到高排名

# 获取前10名
ZREVRANGE leaderboard 0 9 WITHSCORES

十三、常见面试题

1. 基础问题

Q1: Redis为什么快?
A:
1. 基于内存操作
2. 单线程避免上下文切换
3. IO多路复用
4. 高效的数据结构
5. 合理的编码方式

Q2: Redis单线程为什么能处理高并发?
A:
1. IO多路复用:epoll/kqueue
2. 内存操作速度快
3. 避免锁竞争
4. 6.0+多线程处理网络IO

Q3: Redis过期键删除策略?
A:
1. 定期删除:定期随机检查删除
2. 惰性删除:获取时检查是否过期

2. 进阶问题

Q4: Redis持久化时数据修改怎么办?
A:
1. RDB:fork子进程,写时复制
2. AOF:AOF缓冲区,子进程重写不影响主进程

Q5: Redis集群数据如何分布?
A:
- 16384个哈希槽
- CRC16(key) % 16384计算槽位
- 每个节点负责部分槽
- 客户端重定向

Q6: Redis事务为什么不支持回滚?
A:
1. 设计哲学:简单快速
2. 错误通常由编程错误导致
3. 回滚会增加复杂性
4. 开发者应保证命令正确性

3. 实战问题

Q7: 如何实现分布式锁?
A:
1. SET key value NX PX timeout
2. value使用唯一标识
3. 解锁时用Lua脚本保证原子性
4. 设置锁续期

Q8: 如何实现延迟队列?
A:
1. 使用Sorted Set,score为执行时间
2. 定时任务获取到期的任务
3. 使用Stream数据结构

Q9: 大key有什么问题?如何解决?
A:
问题:
1. 内存不均
2. 阻塞操作
3. 网络拥塞
4. 迁移困难

解决:
1. 拆分大key
2. 压缩value
3. 使用合适的数据结构
4. 定期清理

十四、Redis 6.0+ 新特性

1. 多线程IO

# 配置:
io-threads 4
io-threads-do-reads yes

# 特点:
- 多线程处理网络IO
- 单线程处理命令
- 默认关闭,需手动开启

2. 客户端缓存

# 服务端辅助的客户端缓存
CLIENT TRACKING ON|OFF
RESP3协议支持

3. ACL访问控制

# ACL配置
ACL SETUSER alice on >password ~cached:* +get
ACL LIST
ACL GETUSER alice

4. SSL/TLS支持

# 加密连接
tls-port 6379
tls-cert-file redis.crt
tls-key-file redis.key

面试准备建议

  1. 深入理解数据结构和内部实现

  2. 掌握持久化、复制、集群原理

  3. 熟悉缓存问题和解决方案

  4. 了解性能优化和监控

  5. 准备实际应用场景案例

  6. 关注新版本特性

  7. 动手实践常见功能实现

高频考点

  • Redis数据结构与适用场景

  • 持久化机制对比

  • 缓存穿透/雪崩/击穿解决方案

  • 主从复制和哨兵原理

  • 集群数据分片原理

  • 事务和Lua脚本

  • 分布式锁实现

  • 性能优化策略

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐