
文章目录
- 一、引言:Redis为何成为分布式系统核心组件?
- 二、Redis基础核心:概念、安装与核心数据结构
-
- [2.1 Redis核心概念与特性](#2.1 Redis核心概念与特性)
- [2.2 Redis环境安装与基础配置(Linux系统)](#2.2 Redis环境安装与基础配置(Linux系统))
-
- [2.2.1 源码安装步骤](#2.2.1 源码安装步骤)
- [2.2.2 核心配置参数说明](#2.2.2 核心配置参数说明)
- [2.2.3 启动与停止Redis服务](#2.2.3 启动与停止Redis服务)
- [2.3 Redis核心数据结构及操作示例](#2.3 Redis核心数据结构及操作示例)
-
- [2.3.1 字符串(String)](#2.3.1 字符串(String))
-
- [2.3.1.1 核心命令](#2.3.1.1 核心命令)
- [2.3.1.2 Java代码示例(Jedis)](#2.3.1.2 Java代码示例(Jedis))
- [2.3.2 哈希(Hash)](#2.3.2 哈希(Hash))
-
- [2.3.2.1 核心命令](#2.3.2.1 核心命令)
- [2.3.2.2 Java代码示例](#2.3.2.2 Java代码示例)
- [2.3.3 列表(List)](#2.3.3 列表(List))
-
- [2.3.3.1 核心命令](#2.3.3.1 核心命令)
- [2.3.3.2 Java代码示例(消息队列场景)](#2.3.3.2 Java代码示例(消息队列场景))
- [2.3.4 集合(Set)](#2.3.4 集合(Set))
-
- [2.3.4.1 核心命令](#2.3.4.1 核心命令)
- [2.3.4.2 Java代码示例(好友交集场景)](#2.3.4.2 Java代码示例(好友交集场景))
- [2.3.5 有序集合(Sorted Set)](#2.3.5 有序集合(Sorted Set))
-
- [2.3.5.1 核心命令](#2.3.5.1 核心命令)
- [2.3.5.2 Java代码示例(积分排行榜场景)](#2.3.5.2 Java代码示例(积分排行榜场景))
- 三、Redis持久化机制:保障数据不丢失
-
- [3.1 RDB持久化](#3.1 RDB持久化)
-
- [3.1.1 核心原理](#3.1.1 核心原理)
- [3.1.2 触发方式](#3.1.2 触发方式)
-
- [3.1.2.1 自动触发](#3.1.2.1 自动触发)
- [3.1.2.2 手动触发](#3.1.2.2 手动触发)
- [3.1.3 优缺点](#3.1.3 优缺点)
-
- [3.1.3.1 优点](#3.1.3.1 优点)
- [3.1.3.2 缺点](#3.1.3.2 缺点)
- [3.2 AOF持久化](#3.2 AOF持久化)
-
- [3.2.1 核心原理](#3.2.1 核心原理)
- [3.2.2 核心配置](#3.2.2 核心配置)
- [3.2.3 AOF重写机制](#3.2.3 AOF重写机制)
-
- [3.2.3.1 触发方式](#3.2.3.1 触发方式)
- [3.2.4 优缺点](#3.2.4 优缺点)
-
- [3.2.4.1 优点](#3.2.4.1 优点)
- [3.2.4.2 缺点](#3.2.4.2 缺点)
- [3.3 RDB与AOF混合持久化](#3.3 RDB与AOF混合持久化)
-
- [3.3.1 开启配置](#3.3.1 开启配置)
- 四、Redis高可用架构:主从复制与哨兵机制
-
- [4.1 主从复制](#4.1 主从复制)
-
- [4.1.1 核心原理](#4.1.1 核心原理)
- [4.1.2 配置方式](#4.1.2 配置方式)
-
- [4.1.2.1 主节点配置(无需特殊配置,默认即可)](#4.1.2.1 主节点配置(无需特殊配置,默认即可))
- [4.1.2.2 从节点配置(配置文件方式)](#4.1.2.2 从节点配置(配置文件方式))
- [4.1.2.3 命令行方式(临时配置)](#4.1.2.3 命令行方式(临时配置))
- [4.1.3 验证主从复制](#4.1.3 验证主从复制)
- [4.2 哨兵机制(Sentinel)](#4.2 哨兵机制(Sentinel))
-
- [4.2.1 核心功能](#4.2.1 核心功能)
- [4.2.2 哨兵架构配置](#4.2.2 哨兵架构配置)
-
- [4.2.2.1 哨兵配置文件(sentinel.conf)](#4.2.2.1 哨兵配置文件(sentinel.conf))
- [4.2.2.2 启动哨兵节点](#4.2.2.2 启动哨兵节点)
- [4.2.3 验证哨兵机制](#4.2.3 验证哨兵机制)
- 五、Redis实战场景:核心应用与问题解决
-
- [5.1 典型实战场景实现](#5.1 典型实战场景实现)
-
- [5.1.1 缓存场景:商品详情缓存](#5.1.1 缓存场景:商品详情缓存)
-
- [5.1.1.1 Java代码示例](#5.1.1.1 Java代码示例)
- [5.1.2 分布式锁:基于Redis实现](#5.1.2 分布式锁:基于Redis实现)
-
- [5.1.2.1 核心实现(避免死锁)](#5.1.2.1 核心实现(避免死锁))
- [5.1.2.2 Java代码示例](#5.1.2.2 Java代码示例)
- [5.1.3 实时排行榜:基于Sorted Set实现](#5.1.3 实时排行榜:基于Sorted Set实现)
-
- [5.1.3.1 Java代码示例(游戏积分排行榜)](#5.1.3.1 Java代码示例(游戏积分排行榜))
- [5.2 缓存核心问题解决方案](#5.2 缓存核心问题解决方案)
-
- [5.2.1 缓存穿透](#5.2.1 缓存穿透)
-
- [5.2.1.1 问题描述](#5.2.1.1 问题描述)
- [5.2.1.2 解决方案](#5.2.1.2 解决方案)
- [5.2.1.3 空值缓存代码示例](#5.2.1.3 空值缓存代码示例)
- [5.2.2 缓存击穿](#5.2.2 缓存击穿)
-
- [5.2.2.1 问题描述](#5.2.2.1 问题描述)
- [5.2.2.2 解决方案](#5.2.2.2 解决方案)
- [5.2.2.3 互斥锁解决方案代码示例](#5.2.2.3 互斥锁解决方案代码示例)
- [5.2.3 缓存雪崩](#5.2.3 缓存雪崩)
-
- [5.2.3.1 问题描述](#5.2.3.1 问题描述)
- [5.2.3.2 解决方案](#5.2.3.2 解决方案)
- 六、总结
一、引言:Redis为何成为分布式系统核心组件?
在分布式系统架构中,缓存、会话存储、实时数据处理等场景对性能的要求日益严苛,传统关系型数据库难以满足高并发、低延迟的访问需求。Redis(Remote Dictionary Server)作为一款开源的高性能键值对存储数据库,凭借其内存存储、多数据结构支持、高吞吐量等特性,成为解决上述问题的首选方案。无论是电商平台的商品缓存、社交应用的消息推送,还是分布式锁的实现、实时排行榜的构建,都能看到Redis的身影。
本文将采用总分总结构,从Redis基础概念入手,逐步深入核心特性、实战应用、性能优化及高级特性,最后进行知识点总结与扩展,并推荐优质阅读资料。全文逻辑清晰,结合理论知识与代码示例,帮助读者从入门到进阶,全面掌握Redis核心技术。
二、Redis基础核心:概念、安装与核心数据结构
2.1 Redis核心概念与特性
Redis是一款基于内存的键值对存储数据库,采用C语言开发,支持多种数据结构,具有高性能、高可用、可持久化等特点。其核心特性主要包括以下几点:
-
内存存储:Redis将数据存储在内存中,读写速度极快,单机并发量可轻松达到10万+ QPS,远超传统关系型数据库。
-
多数据结构支持:除了基础的字符串(String)类型,Redis还支持哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等多种复杂数据结构,满足不同业务场景需求。
-
可持久化:Redis支持RDB和AOF两种持久化机制,可将内存中的数据定期或实时写入磁盘,避免数据丢失。
-
高可用:通过主从复制(Master-Slave Replication)和哨兵(Sentinel)机制,实现Redis集群的故障自动转移,保障服务稳定性。
-
分布式支持:Redis Cluster提供分布式存储能力,支持数据分片,可横向扩展,满足大规模数据存储与访问需求。
2.2 Redis环境安装与基础配置(Linux系统)
2.2.1 源码安装步骤
Redis官方推荐源码安装方式,可获取最新稳定版本,步骤如下:
bash
# 1. 安装依赖包
yum install -y gcc gcc-c++ make
# 2. 下载Redis源码包(以6.2.6版本为例)
wget https://download.redis.io/releases/redis-6.2.6.tar.gz
# 3. 解压源码包
tar -zxvf redis-6.2.6.tar.gz
# 4. 进入解压目录并编译
cd redis-6.2.6
make
# 5. 安装到指定目录(如/usr/local/redis)
make PREFIX=/usr/local/redis install
# 6. 复制配置文件到安装目录的conf文件夹
mkdir -p /usr/local/redis/conf
cp redis.conf /usr/local/redis/conf/
2.2.2 核心配置参数说明
修改Redis配置文件(/usr/local/redis/conf/redis.conf),调整核心参数以适应生产环境:
properties
# 1. 允许远程访问(注释bind 127.0.0.1,或设置为0.0.0.0)
bind 0.0.0.0
# 2. 开启守护进程模式(后台运行)
daemonize yes
# 3. 设置密码(增强安全性)
requirepass your_password
# 4. 配置持久化方式(RDB+AOF混合模式)
save 60 1000 # 60秒内有1000次修改则触发RDB持久化
appendonly yes # 开启AOF持久化
appendfsync everysec # 每秒同步一次AOF文件
# 5. 设置内存限制(避免内存溢出)
maxmemory 4gb
maxmemory-policy allkeys-lru # 内存满时,淘汰最近最少使用的key
2.2.3 启动与停止Redis服务
bash
# 启动Redis(指定配置文件)
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
# 停止Redis(通过redis-cli)
/usr/local/redis/bin/redis-cli -a your_password shutdown
# 验证Redis是否启动成功
ps -ef | grep redis
/usr/local/redis/bin/redis-cli -a your_password ping # 返回PONG则表示启动成功
2.3 Redis核心数据结构及操作示例
Redis的核心优势之一是支持多种数据结构,每种数据结构都有其独特的应用场景和操作API。以下将详细介绍常用数据结构及对应的命令操作和代码示例(使用Java的Jedis客户端)。
2.3.1 字符串(String)
String是Redis最基础的数据结构,用于存储字符串、整数或浮点数,最大存储容量为512MB。适用于缓存用户信息、商品详情、计数器等场景。
2.3.1.1 核心命令
bash
SET key value # 设置键值对
GET key # 获取键对应的值
INCR key # 整数自增1
DECR key # 整数自减1
INCRBY key n # 整数自增n
DECRBY key n # 整数自减n
EXPIRE key sec # 设置键的过期时间(秒)
SETNX key value # 仅当键不存在时设置值(分布式锁常用)
2.3.1.2 Java代码示例(Jedis)
java
import redis.clients.jedis.Jedis;
public class StringDemo {
public static void main(String[] args) {
// 连接Redis
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");
// 操作String
jedis.set("user:name", "zhangsan");
String userName = jedis.get("user:name");
System.out.println("用户名:" + userName);
jedis.set("product:stock:1001", "100");
jedis.incr("product:stock:1001"); // 库存+1
jedis.decrBy("product:stock:1001", 5); // 库存-5
String stock = jedis.get("product:stock:1001");
System.out.println("商品1001库存:" + stock);
// 设置过期时间(1小时)
jedis.expire("user:name", 3600);
// 关闭连接
jedis.close();
}
}
2.3.2 哈希(Hash)
Hash是键值对的集合,适合存储对象类型的数据(如用户信息、商品属性),可单独对对象的某个字段进行修改,无需修改整个对象,节省内存和带宽。
2.3.2.1 核心命令
bash
HSET key field value # 设置哈希表中指定字段的值
HGET key field # 获取哈希表中指定字段的值
HMSET key field1 value1 field2 value2 # 批量设置哈希表字段值
HMGET key field1 field2 # 批量获取哈希表字段值
HGETALL key # 获取哈希表中所有字段和值
HDEL key field # 删除哈希表中指定字段
HLEN key # 获取哈希表中字段的数量
2.3.2.2 Java代码示例
java
import redis.clients.jedis.Jedis;
import java.util.Map;
public class HashDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");
// 存储用户信息
jedis.hset("user:1001", "name", "zhangsan");
jedis.hset("user:1001", "age", "25");
jedis.hset("user:1001", "email", "zhangsan@example.com");
// 批量获取用户信息
Map<String, String> userInfo = jedis.hgetAll("user:1001");
System.out.println("用户1001信息:" + userInfo);
// 获取单个字段
String userAge = jedis.hget("user:1001", "age");
System.out.println("用户1001年龄:" + userAge);
// 删除字段
jedis.hdel("user:1001", "email");
jedis.close();
}
}
2.3.3 列表(List)
List是有序的字符串列表,基于双向链表实现,支持从两端插入和删除元素,适用于消息队列、最新消息排行、历史记录等场景。
2.3.3.1 核心命令
bash
LPUSH key value1 value2 # 从列表左侧插入一个或多个值
RPUSH key value1 value2 # 从列表右侧插入一个或多个值
LPOP key # 从列表左侧弹出一个值
RPOP key # 从列表右侧弹出一个值
LRANGE key start end # 获取列表中指定范围的元素(0表示第一个,-1表示最后一个)
LLEN key # 获取列表长度
LREM key count value # 删除列表中count个值为value的元素
2.3.3.2 Java代码示例(消息队列场景)
java
import redis.clients.jedis.Jedis;
public class ListDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");
String queueKey = "message:queue";
// 生产者:向队列右侧插入消息
jedis.rpush(queueKey, "消息1:用户下单成功");
jedis.rpush(queueKey, "消息2:用户支付成功");
jedis.rpush(queueKey, "消息3:订单已发货");
// 消费者:从队列左侧弹出消息(非阻塞)
String message1 = jedis.lpop(queueKey);
System.out.println("消费消息:" + message1);
// 获取队列中剩余消息
System.out.println("剩余消息:" + jedis.lrange(queueKey, 0, -1));
jedis.close();
}
}
2.3.4 集合(Set)
Set是无序的字符串集合,不允许重复元素,支持交集、并集、差集等集合运算,适用于好友关系、标签、去重等场景。
2.3.4.1 核心命令
bash
SADD key member1 member2 # 向集合中添加一个或多个元素
SMEMBERS key # 获取集合中所有元素
SISMEMBER key member # 判断元素是否在集合中
SREM key member1 member2 # 从集合中删除一个或多个元素
SCARD key # 获取集合大小
SINTER key1 key2 # 求两个集合的交集
SUNION key1 key2 # 求两个集合的并集
SDIFF key1 key2 # 求两个集合的差集(key1中有而key2中没有的元素)
2.3.4.2 Java代码示例(好友交集场景)
java
import redis.clients.jedis.Jedis;
import java.util.Set;
public class SetDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");
// 存储用户A和用户B的好友
jedis.sadd("friend:1001", "2001", "2002", "2003");
jedis.sadd("friend:1002", "2002", "2003", "2004");
// 求共同好友(交集)
Set<String> commonFriends = jedis.sinter("friend:1001", "friend:1002");
System.out.println("用户1001和1002的共同好友:" + commonFriends);
// 判断用户2001是否是用户1001的好友
boolean isFriend = jedis.sismember("friend:1001", "2001");
System.out.println("2001是1001的好友:" + isFriend);
jedis.close();
}
}
2.3.5 有序集合(Sorted Set)
Sorted Set与Set类似,不允许重复元素,但每个元素会关联一个分数(score),Redis会根据分数对元素进行排序。适用于排行榜、积分排名、范围查询等场景。
2.3.5.1 核心命令
bash
ZADD key score1 member1 score2 member2 # 向有序集合中添加元素及对应的分数
ZRANGE key start end [WITHSCORES] # 按分数升序获取指定范围的元素(带WITHSCORES显示分数)
ZREVRANGE key start end [WITHSCORES] # 按分数降序获取指定范围的元素
ZSCORE key member # 获取指定元素的分数
ZINCRBY key increment member # 增加指定元素的分数
ZCOUNT key min max # 统计分数在[min, max]范围内的元素个数
ZRANK key member # 获取元素按分数升序的排名(从0开始)
2.3.5.2 Java代码示例(积分排行榜场景)
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;
public class SortedSetDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");
String rankKey = "user:score:rank";
// 添加用户积分
jedis.zadd(rankKey, 95, "user1001");
jedis.zadd(rankKey, 88, "user1002");
jedis.zadd(rankKey, 98, "user1003");
jedis.zadd(rankKey, 90, "user1004");
// 增加用户1002的积分(+5)
jedis.zincrby(rankKey, 5, "user1002");
// 获取积分排行榜前3名(降序)
Set<Tuple> top3 = jedis.zrevrangeWithScores(rankKey, 0, 2);
System.out.println("积分排行榜前3名:");
for (Tuple tuple : top3) {
System.out.println("用户名:" + tuple.getElement() + ",积分:" + tuple.getScore());
}
// 获取用户1001的排名
long rank = jedis.zrevrank(rankKey, "user1001");
System.out.println("user1001的排名:第" + (rank + 1) + "名");
jedis.close();
}
}
三、Redis持久化机制:保障数据不丢失
Redis是内存数据库,若不进行持久化,一旦Redis服务崩溃或服务器重启,内存中的数据将全部丢失。Redis提供了两种核心持久化机制:RDB(Redis DataBase)和AOF(Append Only File),实际生产环境中通常采用两种机制结合的方式,兼顾性能与数据安全性。
3.1 RDB持久化
3.1.1 核心原理
RDB持久化是通过快照(snapshot)的方式,将Redis在某个时间点的内存数据完整保存到磁盘的二进制文件(dump.rdb)中。当Redis重启时,会读取该文件并将数据恢复到内存中。RDB的触发方式分为自动触发和手动触发两种。
3.1.2 触发方式
3.1.2.1 自动触发
通过Redis配置文件中的save指令设置触发条件,格式为:save ,表示在seconds秒内,若数据发生了changes次修改,则自动触发RDB快照。例如:
properties
save 900 1 # 900秒内有1次修改
save 300 10 # 300秒内有10次修改
save 60 10000 # 60秒内有10000次修改
若需要禁用自动RDB触发,可将所有save指令注释,或添加save ""。
3.1.2.2 手动触发
通过Redis客户端执行以下两个命令手动触发RDB快照:
-
SAVE:同步执行快照操作,期间Redis会阻塞所有客户端请求,直到快照完成。适用于数据量较小、对阻塞不敏感的场景。
-
BGSAVE:异步执行快照操作,Redis会fork一个子进程来完成快照生成,主进程继续处理客户端请求,不会阻塞服务。是生产环境中推荐使用的手动触发方式。
bash
127.0.0.1:6379> BGSAVE # 异步触发RDB
Background saving started
3.1.3 优缺点
3.1.3.1 优点
-
RDB文件是二进制文件,体积小,备份和恢复速度快,适合用于数据备份和灾难恢复。
-
RDB持久化对Redis服务性能影响较小,因为快照生成由子进程完成,主进程无需参与繁重的IO操作。
3.1.3.2 缺点
-
数据安全性较低:RDB是周期性快照,若在两次快照之间Redis服务崩溃,期间修改的数据将全部丢失。
-
fork子进程时会产生内存开销:当数据量较大时,fork操作可能会阻塞主进程短暂时间,影响服务可用性。
3.2 AOF持久化
3.2.1 核心原理
AOF持久化是通过记录Redis的所有写操作命令(如SET、HSET、LPUSH等)到文本文件(appendonly.aof)中,当Redis重启时,会重新执行AOF文件中的所有命令,从而恢复数据。AOF默认不开启,需要手动在配置文件中启用。
3.2.2 核心配置
properties
appendonly yes # 开启AOF持久化(默认no)
appendfilename "appendonly.aof" # AOF文件名
appendfsync everysec # AOF同步策略
# appendfsync always # 每次写操作都同步到磁盘(安全性最高,性能最差)
# appendfsync no # 由操作系统决定何时同步(性能最好,安全性最低)
appendfsync everysec是生产环境的推荐配置,兼顾性能与安全性:每秒同步一次AOF文件,即使服务崩溃,最多丢失1秒内的数据。
3.2.3 AOF重写机制
随着Redis运行时间增长,AOF文件会记录大量重复或无效的命令(如多次修改同一个key的命令),导致文件体积过大,影响恢复速度。Redis提供AOF重写机制,通过删除无效命令、合并重复命令,生成一个精简的AOF文件,替代原来的大文件。
3.2.3.1 触发方式
-
自动触发 :通过配置文件设置重写条件:
auto-aof-rewrite-percentage 100 # AOF文件体积比上次重写后增长100% auto-aof-rewrite-min-size 64mb # AOF文件最小体积达到64MB才触发重写 -
手动触发 :执行BGREWRITEAOF命令,异步重写AOF文件,不阻塞主进程:
127.0.0.1:6379> BGREWRITEAOF Background append only file rewriting started
3.2.4 优缺点
3.2.4.1 优点
-
数据安全性高:支持多种同步策略,最多仅丢失1秒内的数据(everysec策略),甚至不丢失数据(always策略)。
-
AOF文件是文本文件,命令可读性强,若发生数据错误,可手动编辑文件修复。
3.2.4.2 缺点
-
AOF文件体积通常比RDB文件大,备份和恢复速度较慢。
-
写操作性能开销比RDB大:每次写操作都需要记录命令到文件,即使是everysec策略,也会有一定的IO开销。
3.3 RDB与AOF混合持久化
Redis 4.0及以上版本支持RDB与AOF混合持久化机制。开启混合持久化后,AOF重写时会将当前内存数据以RDB格式写入AOF文件开头,后续的写操作命令以AOF格式追加到文件末尾。这种方式结合了RDB和AOF的优点:恢复速度快(开头是RDB格式)、数据安全性高(后续是AOF命令)。
3.3.1 开启配置
properties
aof-use-rdb-preamble yes # 开启混合持久化(默认yes,Redis 4.0+)
混合持久化的AOF文件结构如下:[RDB二进制数据][AOF命令文本数据],重启时Redis会先加载RDB部分恢复数据,再执行AOF命令部分补充数据。
四、Redis高可用架构:主从复制与哨兵机制

单节点Redis存在单点故障风险:一旦节点崩溃,服务将不可用。为保障服务高可用,Redis提供了主从复制(Master-Slave Replication)和哨兵(Sentinel)机制。主从复制实现数据备份,哨兵机制实现故障自动转移,两者结合构建高可用架构。
4.1 主从复制
4.1.1 核心原理
主从复制是指将一个Redis节点(主节点,Master)的数据复制到其他Redis节点(从节点,Slave)。主节点负责处理写操作和读操作,从节点仅负责处理读操作(默认),实现读写分离,提高系统并发处理能力;同时,从节点存储主节点数据副本,实现数据备份。
主从复制的核心流程分为三个阶段:
-
连接建立阶段:从节点通过配置或命令连接主节点,建立复制关系。
-
数据同步阶段:主节点将当前所有数据生成RDB快照,发送给从节点;从节点接收RDB快照并加载,完成初始数据同步。
-
命令传播阶段:初始同步完成后,主节点将后续的写操作命令实时发送给从节点;从节点执行这些命令,保持与主节点数据一致。
4.1.2 配置方式
主从复制有两种配置方式:配置文件方式和命令行方式(临时生效,重启后失效)。以下以一主两从(Master:192.168.1.101,Slave1:192.168.1.102,Slave2:192.168.1.103)为例进行配置。
4.1.2.1 主节点配置(无需特殊配置,默认即可)
properties
bind 0.0.0.0 # 允许远程连接
daemonize yes
requirepass your_password
appendonly yes # 开启AOF持久化
4.1.2.2 从节点配置(配置文件方式)
修改从节点的redis.conf文件:
properties
bind 0.0.0.0
daemonize yes
requirepass your_password
# 配置主节点信息
replicaof 192.168.1.101 6379 # Redis 5.0+使用replicaof,旧版本使用slaveof
masterauth your_password # 主节点的密码(若主节点设置了密码)
replica-read-only yes # 从节点设为只读模式(默认yes,推荐开启)
4.1.2.3 命令行方式(临时配置)
在从节点的Redis客户端执行以下命令:
bash
127.0.0.1:6379> REPLICAOF 192.168.1.101 6379 # 建立主从关系
OK
127.0.0.1:6379> CONFIG SET masterauth "your_password" # 设置主节点密码
OK
127.0.0.1:6379> CONFIG SET replica-read-only yes # 设置为只读
OK
4.1.3 验证主从复制
在主节点客户端执行命令查看从节点信息:
bash
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.1.102,port=6379,state=online,offset=100,lag=1
slave1:ip=192.168.1.103,port=6379,state=online,offset=100,lag=1
...
在主节点执行写操作,从节点执行读操作,验证数据是否同步:
bash
# 主节点设置key
127.0.0.1:6379> SET test:replica "hello redis"
OK
# 从节点获取key
127.0.0.1:6379> GET test:replica
"hello redis" # 数据同步成功
4.2 哨兵机制(Sentinel)
主从复制解决了数据备份和读写分离问题,但无法解决主节点故障后的自动恢复问题。当主节点崩溃时,需要手动将一个从节点晋升为新的主节点,修改其他从节点的主节点配置,操作繁琐且耗时。哨兵机制通过监控主从节点状态,实现主节点故障后的自动故障转移,无需人工干预。
4.2.1 核心功能
-
监控:哨兵节点定期检查主节点和从节点是否正常运行。
-
通知:当某个节点出现故障时,哨兵节点通过配置的通知方式(如邮件、短信)向管理员或其他应用程序发送告警信息。
-
自动故障转移:当主节点故障时,哨兵节点会自动选举一个从节点晋升为新的主节点,将其他从节点重新指向新的主节点,并更新应用程序的Redis连接信息。
4.2.2 哨兵架构配置
哨兵机制通常采用多个哨兵节点(推荐3个)组成哨兵集群,避免哨兵节点单点故障。以下是一主两从三哨兵的架构配置(主节点:192.168.1.101,从节点:192.168.1.102、192.168.1.103,哨兵节点:192.168.1.104、192.168.1.105、192.168.1.106)。
4.2.2.1 哨兵配置文件(sentinel.conf)
每个哨兵节点使用相同的sentinel.conf配置文件:
properties
daemonize yes # 后台运行
port 26379 # 哨兵节点端口(默认26379)
sentinel monitor mymaster 192.168.1.101 6379 2 # 监控主节点,mymaster是主节点名称,2表示至少2个哨兵节点认为主节点故障才触发故障转移
sentinel auth-pass mymaster your_password # 主节点的密码
sentinel down-after-milliseconds mymaster 30000 # 30秒内未收到主节点响应,则认为主节点故障
sentinel parallel-syncs mymaster 1 # 故障转移后,从节点同步新主节点数据的并发数(1表示串行同步)
sentinel failover-timeout mymaster 180000 # 故障转移超时时间(180秒)
4.2.2.2 启动哨兵节点
bash
redis-sentinel /usr/local/redis/conf/sentinel.conf # 启动哨兵,指定配置文件
4.2.3 验证哨兵机制
- 查看哨兵信息,确认监控的主从节点状态:
bash
redis-cli -p 26379 INFO sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.1.101:6379,slaves=2,sentinels=3
- 模拟主节点故障:关闭主节点Redis服务:
bash
redis-cli -a your_password shutdown
- 观察哨兵日志和节点状态,哨兵会自动选举新的主节点(例如192.168.1.102),并将其他从节点(192.168.1.103)指向新主节点。重启原主节点(192.168.1.101)后,它会自动成为新主节点的从节点。
五、Redis实战场景:核心应用与问题解决
Redis凭借其高性能和丰富的数据结构,在实际开发中有诸多典型应用场景。本节将介绍常见实战场景的实现方案,同时解决实际应用中遇到的缓存穿透、缓存击穿、缓存雪崩等核心问题。
5.1 典型实战场景实现
5.1.1 缓存场景:商品详情缓存
电商平台的商品详情页访问量巨大,直接查询数据库会导致性能瓶颈。使用Redis缓存商品详情,可大幅提高查询效率。实现思路如下:
-
用户访问商品详情页时,先从Redis查询商品数据。
-
若Redis中存在数据(缓存命中),直接返回数据给用户。
-
若Redis中不存在数据(缓存未命中),从数据库查询数据,将数据写入Redis并设置过期时间,再返回数据给用户。
-
商品信息更新时,同步更新数据库和Redis中的数据(或删除Redis缓存,让下次查询重新加载)。
5.1.1.1 Java代码示例
java
import redis.clients.jedis.Jedis;
import com.alibaba.fastjson.JSON;
import java.util.HashMap;
import java.util.Map;
public class ProductCacheDemo {
// 模拟数据库查询商品信息
private static Map<String, Map<String, String>> productDB = new HashMap<>();
static {
Map<String, String> product = new HashMap<>();
product.put("id", "1001");
product.put("name", "iPhone 14 Pro");
product.put("price", "7999");
product.put("stock", "500");
productDB.put("1001", product);
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");
String productId = "1001";
String cacheKey = "product:info:" + productId;
// 1. 从Redis查询缓存
String productJson = jedis.get(cacheKey);
if (productJson != null) {
Map<String, String> product = JSON.parseObject(productJson, Map.class);
System.out.println("从缓存获取商品信息:" + product);
jedis.close();
return;
}
// 2. 缓存未命中,从数据库查询
Map<String, String> product = productDB.get(productId);
if (product == null) {
System.out.println("商品不存在");
jedis.close();
return;
}
// 3. 将数据写入缓存,设置过期时间(30分钟)
jedis.setex(cacheKey, 1800, JSON.toJSONString(product));
System.out.println("从数据库获取商品信息并写入缓存:" + product);
jedis.close();
}
}
5.1.2 分布式锁:基于Redis实现
在分布式系统中,多个服务实例需要竞争同一资源(如库存扣减、订单创建),需通过分布式锁保证操作的原子性。基于Redis的SETNX命令可实现分布式锁,核心思路:利用SETNX命令的原子性,若键不存在则设置值(获取锁),键存在则设置失败(获取锁失败);释放锁时删除键。
5.1.2.1 核心实现(避免死锁)
为避免获取锁的服务实例崩溃导致锁无法释放(死锁),需给锁设置过期时间。同时,释放锁时需验证锁的归属(避免误删其他服务的锁),可通过设置锁的值为唯一标识(如UUID)实现。
5.1.2.2 Java代码示例
java
import redis.clients.jedis.Jedis;
import java.util.UUID;
public class RedisDistributedLock {
private static final String LOCK_KEY = "distributed:lock:product:1001";
private static final int LOCK_EXPIRE = 30; // 锁过期时间(秒)
// 获取锁
public static String acquireLock(Jedis jedis) {
String lockValue = UUID.randomUUID().toString();
// SETNX + EXPIRE 原子操作(Redis 2.6.12+支持)
String result = jedis.set(LOCK_KEY, lockValue, "NX", "EX", LOCK_EXPIRE);
if ("OK".equals(result)) {
System.out.println("获取锁成功,锁标识:" + lockValue);
return lockValue;
}
System.out.println("获取锁失败");
return null;
}
// 释放锁
public static boolean releaseLock(Jedis jedis, String lockValue) {
if (lockValue == null) {
return false;
}
// 验证锁归属并删除(原子操作,避免误删)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, 1, LOCK_KEY, lockValue);
return Integer.parseInt(result.toString()) == 1;
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");
// 获取锁
String lockValue = acquireLock(jedis);
if (lockValue != null) {
try {
// 执行核心业务逻辑(如扣减库存)
System.out.println("执行扣减库存操作...");
Thread.sleep(2000); // 模拟业务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
boolean releaseResult = releaseLock(jedis, lockValue);
System.out.println("释放锁结果:" + releaseResult);
}
}
jedis.close();
}
}
5.1.3 实时排行榜:基于Sorted Set实现
游戏积分排行榜、电商销量排行榜等场景需要实时更新和查询排名,基于Redis的Sorted Set可轻松实现。核心思路:以用户ID为member,积分/销量为score,通过ZADD、ZREVRANGE等命令实现排名更新和查询。
5.1.3.1 Java代码示例(游戏积分排行榜)
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;
public class GameRankDemo {
private static final String RANK_KEY = "game:score:rank";
// 更新用户积分
public static void updateScore(Jedis jedis, String userId, double score) {
jedis.zadd(RANK_KEY, score, userId);
System.out.println("用户" + userId + "积分更新为:" + score);
}
// 获取排行榜前N名
public static Set<Tuple> getTopRank(Jedis jedis, int topN) {
return jedis.zrevrangeWithScores(RANK_KEY, 0, topN - 1);
}
// 获取用户排名和积分
public static void getUserRank(Jedis jedis, String userId) {
Long rank = jedis.zrevrank(RANK_KEY, userId);
Double score = jedis.zscore(RANK_KEY, userId);
if (rank != null && score != null) {
System.out.println("用户" + userId + "积分:" + score + ",排名:第" + (rank + 1) + "名");
} else {
System.out.println("用户" + userId + "未上榜");
}
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");
// 更新多个用户积分
updateScore(jedis, "user1001", 95);
updateScore(jedis, "user1002", 88);
updateScore(jedis, "user1003", 98);
updateScore(jedis, "user1004", 90);
updateScore(jedis, "user1005", 85);
// 获取排行榜前3名
Set<Tuple> top3 = getTopRank(jedis, 3);
System.out.println("游戏积分排行榜前3名:");
int index = 1;
for (Tuple tuple : top3) {
System.out.println(index + "名:用户" + tuple.getElement() + ",积分:" + tuple.getScore());
index++;
}
// 获取用户1001的排名
getUserRank(jedis, "user1001");
jedis.close();
}
}
5.2 缓存核心问题解决方案
5.2.1 缓存穿透
5.2.1.1 问题描述
缓存穿透是指查询一个不存在的数据,由于缓存中没有该数据,所有请求都会穿透到数据库,导致数据库压力过大,甚至崩溃。例如:恶意攻击时,大量查询不存在的用户ID或商品ID。
5.2.1.2 解决方案
-
空值缓存:查询数据库发现数据不存在时,将空值写入Redis并设置较短的过期时间(如5分钟),避免后续相同请求穿透到数据库。
-
布隆过滤器(Bloom Filter):将数据库中所有存在的key提前存入布隆过滤器,查询前先通过布隆过滤器判断key是否存在,若不存在则直接返回,无需查询缓存和数据库。布隆过滤器具有高效的空间利用率和查询速度,但存在一定的误判率(需合理设置参数)。
5.2.1.3 空值缓存代码示例
java
// 优化商品详情缓存,解决缓存穿透
public static Map<String, String> getProductInfo(Jedis jedis, String productId) {
String cacheKey = "product:info:" + productId;
String productJson = jedis.get(cacheKey);
// 缓存命中(包括空值)
if (productJson != null) {
if ("null".equals(productJson)) {
return null; // 空值,返回不存在
}
return JSON.parseObject(productJson, Map.class);
}
// 缓存未命中,查询数据库
Map<String, String> product = productDB.get(productId);
if (product == null) {
// 空值写入缓存,设置5分钟过期
jedis.setex(cacheKey, 300, "null");
return null;
}
// 正常数据写入缓存
jedis.setex(cacheKey, 1800, JSON.toJSONString(product));
return product;
}
5.2.2 缓存击穿
5.2.2.1 问题描述
缓存击穿是指一个热点key(如热门商品、热门活动)的缓存过期时,大量并发请求同时访问该key,导致所有请求穿透到数据库,数据库瞬间压力剧增。
5.2.2.2 解决方案
-
互斥锁(分布式锁):缓存过期时,只有一个请求能获取分布式锁,该请求查询数据库并更新缓存,其他请求等待锁释放后直接查询缓存。
-
热点key永不过期:对于热点key,不设置过期时间,通过后台线程定期更新缓存数据,避免缓存过期导致的击穿。
-
缓存预热:系统启动时,提前将热点key的数据加载到缓存中,避免用户请求时缓存未命中。
5.2.2.3 互斥锁解决方案代码示例
Plain
// 基于分布式锁解决缓存击穿问题
public static Map<String, String> getHotProductInfo(Jedis jedis, String productId) {
String cacheKey = "product:info:" + productId;
String productJson = jedis.get(cacheKey);
// 缓存命中,直接返回
if (productJson != null && !"null".equals(productJson)) {
return JSON.parseObject(productJson, Map.class);
}
// 缓存未命中(过期或不存在),获取分布式锁
String lockKey = "distributed:lock:product:" + productId;
String lockValue = UUID.randomUUID().toString();
try {
// 尝试获取锁,过期时间30秒
String lockResult = jedis.set(lockKey, lockValue, "NX", "EX", 30);
if ("OK".equals(lockResult)) {
// 成功获取锁,查询数据库
Map<String, String> product = productDB.get(productId);
if (product != null) {
// 写入缓存,设置过期时间(30分钟)
jedis.setex(cacheKey, 1800, JSON.toJSONString(product));
} else {
// 空值缓存,避免穿透
jedis.setex(cacheKey, 300, "null");
}
return product;
} else {
// 未获取到锁,等待100毫秒后重试
Thread.sleep(100);
return getHotProductInfo(jedis, productId); // 递归重试
}
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} finally {
// 释放锁
String releaseScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(releaseScript, 1, lockKey, lockValue);
}
}
5.2.3 缓存雪崩
5.2.3.1 问题描述
缓存雪崩是指在某一时间段内,大量缓存key同时过期,或Redis集群整体故障,导致海量请求瞬间穿透到数据库,数据库因压力过大而崩溃,进而引发整个系统服务不可用的连锁反应。与缓存击穿针对单个热点key不同,缓存雪崩是大规模key失效或服务故障导致的全局性问题。
5.2.3.2 解决方案
-
过期时间随机化:为不同缓存key设置随机的过期时间,避免大量key在同一时间点过期。例如,在基础过期时间(如30分钟)的基础上,增加0-10分钟的随机偏移量,分散过期时间点。
-
多级缓存架构:引入本地缓存(如Caffeine、Guava Cache)+ 分布式缓存(Redis)的多级缓存模式。当分布式缓存失效时,请求先访问本地缓存,减少对数据库的直接冲击,为分布式缓存恢复或重建争取时间。
-
Redis集群高可用:部署Redis主从复制+哨兵机制,或Redis Cluster集群,确保单个节点故障时,其他节点能快速接管服务,避免Redis集群整体不可用。
-
缓存降级与熔断:通过服务降级机制,当Redis或数据库压力达到阈值时,暂时关闭非核心业务的缓存查询,直接返回默认数据(如"服务繁忙,请稍后重试");利用熔断组件(如Sentinel、Hystrix),当数据库异常时,快速熔断请求,避免数据库进一步恶化。
-
缓存预热与应急储备:系统启动前,提前将热点数据加载到缓存中;针对核心业务的缓存数据,准备应急缓存副本,当主缓存失效时,快速切换到副本缓存。
六、总结
本文围绕Redis从基础到进阶的核心知识展开,系统梳理了Redis的核心特性、环境配置、核心数据结构、持久化机制、高可用架构及实战场景与问题解决方案,形成了完整的Redis技术知识体系。
在基础层面,Redis作为基于内存的高性能键值对数据库,其多数据结构支持(String、Hash、List、Set、Sorted Set)是适配不同业务场景的核心优势,掌握各数据结构的特性与操作命令是Redis应用的基础。环境配置方面,需重点关注远程访问、守护进程、密码安全、持久化及内存限制等核心参数,确保Redis服务稳定运行。
在数据安全层面,RDB与AOF两种持久化机制各有优劣,生产环境中推荐采用混合持久化模式,兼顾数据恢复速度与安全性。高可用架构是Redis服务稳定的保障,主从复制实现数据备份与读写分离,哨兵机制则解决了主节点故障后的自动转移问题,两者结合可有效避免单点故障风险。
在实战应用层面,Redis在缓存、分布式锁、实时排行榜等场景中发挥着关键作用,但需重点应对缓存穿透、击穿、雪崩等核心问题。通过空值缓存、布隆过滤器、分布式锁、过期时间随机化、多级缓存等解决方案,可有效提升缓存架构的稳定性与可靠性。
Redis的学习与实践需兼顾理论与实操,理解核心原理的同时,结合具体业务场景灵活运用技术方案。未来,随着分布式系统规模的扩大,Redis Cluster的分布式分片与扩容能力、Redis 6.0+的多线程IO等高级特性将成为进一步优化性能的关键方向,值得深入探索与实践。