Skip to content

Redis核心

基础

Redis是什么?有什么特点?

答案: Redis (Remote Dictionary Server) 是一个开源的内存数据库,支持多种数据结构。

特点

  • 高性能:基于内存,读写速度快(10万+QPS)
  • 丰富的数据类型:String、Hash、List、Set、ZSet等
  • 持久化:RDB、AOF
  • 高可用:主从复制、哨兵、集群
  • 原子性:所有操作都是原子性的
  • 支持事务:MULTI、EXEC
  • 发布订阅:消息队列

Redis的数据类型及应用场景?

答案:

数据类型底层实现应用场景
StringSDS缓存、计数器、分布式锁
Hash哈希表/压缩列表对象存储、购物车
List双向链表/压缩列表消息队列、文章列表
Set哈希表/整数集合标签、共同好友
ZSet跳表+哈希表排行榜、延时队列

代码示例

bash
# String
SET key value
GET key
INCR counter  # 计数器

# Hash
HSET user:1 name Tom age 20
HGET user:1 name
HGETALL user:1

# List
LPUSH list 1 2 3  # 左插入
RPUSH list 4 5 6  # 右插入
LRANGE list 0 -1  # 获取所有

# Set
SADD tags java redis mysql
SMEMBERS tags
SINTER tags1 tags2  # 交集

# ZSet
ZADD rank 100 user1 200 user2
ZRANGE rank 0 -1 WITHSCORES  # 排行榜

Redis为什么这么快?

答案:

  1. 基于内存:内存读写速度远超磁盘
  2. 单线程模型:避免线程切换和锁竞争(Redis 6.0后网络I/O多线程)
  3. I/O多路复用:epoll模型,高效处理并发连接
  4. 高效的数据结构:SDS、跳表、压缩列表等
  5. 优化的内存管理:jemalloc内存分配器
客户端请求

I/O多路复用(epoll)

事件分发器

单线程处理

返回结果

持久化

Redis的持久化方式?

答案:

1. RDB (Redis Database)

  • 原理:快照,保存某个时间点的完整数据
  • 触发:SAVE、BGSAVE、自动触发
  • 优点:文件小,恢复快
  • 缺点:可能丢失最后一次快照后的数据
bash
# 配置
save 900 1      # 900秒内至少1个key变化
save 300 10     # 300秒内至少10个key变化
save 60 10000   # 60秒内至少10000个key变化

# 手动触发
SAVE      # 阻塞
BGSAVE    # 后台执行(fork子进程)

2. AOF (Append Only File)

  • 原理:记录每个写操作命令
  • 优点:数据更完整,最多丢失1秒数据
  • 缺点:文件大,恢复慢
bash
# 配置
appendonly yes
appendfsync always    # 每次写入都同步(慢但安全)
appendfsync everysec  # 每秒同步(推荐)
appendfsync no        # 由OS决定(快但不安全)

AOF重写

bash
# 自动触发
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 手动触发
BGREWRITEAOF

混合持久化(Redis 4.0+)

  • RDB快照 + AOF增量
  • 兼顾恢复速度和数据完整性

RDB和AOF如何选择?

答案:

场景推荐方案
可以容忍分钟级数据丢失RDB
数据完整性要求高AOF
生产环境RDB + AOF混合

缓存

缓存穿透、缓存击穿、缓存雪崩?

答案:

1. 缓存穿透

  • 问题:查询不存在的数据,缓存和数据库都没有,每次都打到数据库
  • 解决
    • 布隆过滤器:判断数据是否存在
    • 缓存空值:设置短过期时间
java
// 布隆过滤器
if (!bloomFilter.contains(key)) {
    return null;  // 数据不存在
}

// 缓存空值
String value = redis.get(key);
if (value == null) {
    value = db.query(key);
    if (value == null) {
        redis.set(key, "", 60);  // 缓存空值60秒
    } else {
        redis.set(key, value, 3600);
    }
}

2. 缓存击穿

  • 问题:热点key过期,大量请求同时打到数据库
  • 解决
    • 互斥锁:只让一个线程查数据库
    • 热点数据永不过期
java
// 互斥锁
String value = redis.get(key);
if (value == null) {
    if (redis.setnx(lock_key, "1", 10)) {  // 获取锁
        value = db.query(key);
        redis.set(key, value, 3600);
        redis.del(lock_key);  // 释放锁
    } else {
        Thread.sleep(100);
        return get(key);  // 重试
    }
}

3. 缓存雪崩

  • 问题:大量key同时过期,请求全部打到数据库
  • 解决
    • 过期时间加随机值
    • 多级缓存
    • 限流降级
java
// 过期时间加随机值
int expire = 3600 + new Random().nextInt(300);  // 3600~3900秒
redis.set(key, value, expire);

如何保证缓存和数据库一致性?

答案:

常见方案

1. Cache Aside(旁路缓存)

  • :先读缓存,miss则读数据库并更新缓存
  • :先更新数据库,再删除缓存
java
// 读
public User get(Long id) {
    User user = redis.get(id);
    if (user == null) {
        user = db.query(id);
        redis.set(id, user, 3600);
    }
    return user;
}

// 写
public void update(User user) {
    db.update(user);
    redis.del(user.getId());  // 删除缓存
}

为什么删除而不是更新缓存?

  • 更新缓存可能浪费(数据可能不会被读取)
  • 删除缓存更简单,下次读取时再加载

2. 延迟双删

java
public void update(User user) {
    redis.del(user.getId());  // 第一次删除
    db.update(user);
    Thread.sleep(500);        // 延迟
    redis.del(user.getId());  // 第二次删除
}

3. 订阅MySQL Binlog

  • 使用Canal监听binlog
  • 异步更新缓存

分布式锁

如何实现Redis分布式锁?

答案:

基础版本

java
// 加锁
SET lock_key unique_value NX EX 30

// 解锁(Lua脚本保证原子性)
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

Java实现

java
public boolean tryLock(String key, String value, int expireTime) {
    return redis.set(key, value, "NX", "EX", expireTime);
}

public void unlock(String key, String value) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del', KEYS[1]) else return 0 end";
    redis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
}

问题和优化

1. 锁过期问题

  • 问题:业务未执行完,锁就过期了
  • 解决:Redisson的看门狗机制,自动续期

2. 主从切换问题

  • 问题:主节点宕机,从节点未同步锁数据
  • 解决:RedLock算法(多个独立Redis实例)

Redisson使用

java
RLock lock = redisson.getLock("myLock");
try {
    lock.lock(30, TimeUnit.SECONDS);  // 自动续期
    // 业务逻辑
} finally {
    lock.unlock();
}

高可用

Redis的主从复制原理?

答案:

复制流程

1. Slave发送PSYNC命令
2. Master执行BGSAVE生成RDB
3. Master发送RDB给Slave
4. Slave加载RDB
5. Master发送缓冲区的写命令
6. 后续增量同步

全量复制 vs 增量复制

  • 全量复制:第一次同步,传输RDB
  • 增量复制:后续同步,传输写命令

Redis哨兵(Sentinel)的作用?

答案:

功能

  1. 监控:检测Master和Slave是否正常
  2. 通知:故障时通知管理员
  3. 自动故障转移:Master宕机时选举新Master
  4. 配置提供:客户端连接哨兵获取Master地址

故障转移流程

1. 哨兵检测到Master下线
2. 多数哨兵确认(主观下线→客观下线)
3. 选举Leader哨兵
4. Leader选择一个Slave升级为Master
5. 其他Slave指向新Master
6. 通知客户端

Redis集群(Cluster)的原理?

答案:

特点

  • 无中心架构
  • 数据分片存储(16384个槽)
  • 支持主从复制
  • 自动故障转移

槽分配

CRkey) % 16384 = slot

数据迁移

  • 节点间通过Gossip协议通信
  • 槽可以在线迁移

练习题

  1. Redis的过期策略和内存淘汰策略?
  2. Redis的单线程模型为什么还能支持高 如何用Redis实现延时队列?
  3. Redis的跳表是什么?为什么用跳表而不是红黑树?

Released under the MIT License.