一、学习目标
- Redis 5种核心数据类型(string/hash/list/set/sorted set)的增删改查全操作,理解每种类型的适用场景
- Redis 单机、多实例、集群的部署与运维(含常见问题排查)
- Redis 在 JavaEE 项目中的实战应用(Jedis 原生调用、Spring 整合、缓存设计)
- Redis 高级特性(持久化、主从复制、集群)的原理与配置,解决数据安全和高可用问题
- 从 NoSQL 基础到 Redis 底层逻辑的完整知识链,能独立设计 Redis 缓存方案
二、NoSQL 基础:为什么需要 Redis?
在学 Redis 之前,我们先搞懂:什么是 NoSQL?它和我们熟悉的 MySQL(关系型数据库)有啥区别?
1. 什么是 NoSQL?
NoSQL = Not Only SQL(不只是 SQL),是一类非关系型数据库的总称。它不是要替代关系型数据库,而是作为补充,解决关系型数据库的"短板":
- 关系型数据库(MySQL):适合结构化数据、复杂查询(联表)、事务一致性,但高并发、大数据量场景下性能拉胯(比如百万用户同时访问的电商网站)
- NoSQL 数据库:适合非结构化/半结构化数据、高并发读写、海量数据存储,特点是高性能、高可用、可扩展
2. NoSQL 四大分类
| 分类 | 核心特点 | 代表产品 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 键值(Key-Value) | 存储键值对,value 是字符串/二进制数据 | Redis、Tokyo Cabinet | 缓存、会话存储、计数器 | 查询速度极快(O(1))、支持高并发 | 数据无结构化,无法做复杂查询 |
| 列存储 | 按列簇存储,同一列数据存在一起 | HBase、Cassandra | 分布式文件系统、大数据分析(如日志存储) | 查找速度快、可扩展性强(横向扩容) | 功能局限,不适合复杂查询 |
| 文档型 | value 是结构化文档(JSON/XML) | MongoDB、CouchDB | Web 应用(存储用户信息、文章内容) | 数据结构灵活(字段可增删)、易读写 | 查询性能一般,无统一查询语法 |
| 图形(Graph) | 存储节点和边的关系(如社交网络) | Neo4j、Infinite Graph | 社交网络、推荐系统(如好友关系、商品关联) | 擅长复杂关系查询(如"好友的好友") | 分布式集群难实现,大数据量下性能下降 |
3. Redis 为什么脱颖而出?
Redis 属于键值存储型 NoSQL,但它不止于此------支持多种数据类型、持久化、主从复制、集群,还能做缓存、消息队列、排行榜等,是"全能型选手"。其核心优势:
- 基于内存操作,读写速度极快(读 11 万次/秒,写 8 万次/秒)
- 支持 5 种核心数据类型,适配多种场景
- 单线程模型(避免线程切换开销)+ IO 多路复用(高效处理并发连接)
- 支持持久化(数据不丢失)、主从复制(高可用)、集群(横向扩容)
- 跨平台、开源、社区活跃,企业级应用广泛(微博、知乎、GitHub 等)
三、Redis 前世今生
- 2008 年:意大利创业公司 Merzia 开发实时统计系统 LLOOGG,创始人 Salvatore Sanfilippo 对 MySQL 性能不满
- 2009 年:Salvatore 亲自开发 Redis(Remote Dictionary Server),专门适配 LLOOGG;同年开源,Pieter Noordhuis 加入共同开发
- 2010 年:VMware 赞助 Redis 开发,两位核心开发者全职投入
- 2012 年:Redis 用户量爆发,Hacker News 调查显示 12% 企业在使用
- 至今:Redis 已成为最流行的 NoSQL 数据库,广泛用于缓存、消息队列、分布式锁等场景
四、Redis 安装与环境配置(CentOS7.5 实战)
Redis 基于 C 语言开发,推荐在 Linux 环境运行(Windows 版本功能不全,不推荐生产使用)。以下是详细安装步骤,含问题排查:
1. 前置环境准备
(1)安装 GCC 编译环境
Redis 源码需要 GCC 编译,先检查是否安装:
bash
gcc --version # 若显示版本号则已安装,否则执行下面命令
yum install -y gcc-c++ # 安装 GCC(CentOS 7 自带 yum 包管理器)
问题排查:若 yum 安装失败(如网络问题),可更换国内源(阿里源、网易源):
bash
# 备份原 yum 源
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
# 下载阿里源(CentOS7)
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/CentOS-7.repo
# 清理缓存并更新
yum clean all && yum makecache
(2)下载 Redis 源码
推荐版本:3.0(企业稳定版)或 4.0(功能更全),官网下载地址:
bash
# 切换到 /usr/local 目录(习惯存放软件)
cd /usr/local
# 下载 Redis 4.0.14(推荐,兼容性好)
wget http://download.redis.io/releases/redis-4.0.14.tar.gz
# 若没有 wget,先安装:yum install -y wget
2. 解压与编译安装
bash
# 解压源码包
tar -zxvf redis-4.0.14.tar.gz
# 进入解压目录
cd redis-4.0.14
# 编译(生成可执行文件)
make
# 安装到指定目录(/usr/local/redis,方便管理)
make PREFIX=/usr/local/redis install
验证安装 :进入 /usr/local/redis/bin 目录,若有以下文件则安装成功:
- redis-server:Redis 服务端程序
- redis-cli:Redis 客户端程序
- redis-benchmark:性能测试工具
- redis-check-aof:AOF 日志修复工具
- redis-check-dump:RDB 快照修复工具
- redis-sentinel:哨兵(集群高可用工具)
3. 配置文件优化(redis.conf)
Redis 配置文件在源码目录(/usr/local/redis-4.0.14/redis.conf),需拷贝到安装目录并修改关键配置:
bash
# 进入安装目录,创建 conf 文件夹存放配置文件
cd /usr/local/redis
mkdir conf
# 拷贝配置文件
cp /usr/local/redis-4.0.14/redis.conf /usr/local/redis/conf/
# 编辑配置文件(用 vim)
vim /usr/local/redis/conf/redis.conf
关键配置项(必改)
| 配置项 | 默认值 | 修改后值 | 说明 |
|---|---|---|---|
| daemonize | no | yes | 后台运行(否则关闭 SSH 窗口,Redis 服务就停了) |
| bind | 127.0.0.1 | 0.0.0.0 | 允许远程访问(默认只允许本机连接,开发/测试环境需修改) |
| port | 6379 | 6379(可改) | 服务端口(默认 6379,若启动多实例需修改) |
| requirepass | 无 | 自定义密码 | (可选)设置访问密码(生产环境必设,避免未授权访问),如:requirepass 123456 |
| dir | ./ | /usr/local/redis/data | 数据存储目录(持久化文件、日志文件存放位置),需提前创建:mkdir /usr/local/redis/data |
| logfile | 无 | /usr/local/redis/logs/redis.log | 日志文件路径,需提前创建:mkdir /usr/local/redis/logs |
vim 操作技巧 :按 / 输入配置项名称(如 daemonize),按 n 查找下一个,修改后按 Esc,输入 :wq 保存退出。
4. Redis 服务启动与停止(核心操作)
(1)启动 Redis 服务
bash
# 方式 1:指定配置文件启动(推荐,明确使用哪个配置)
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
# 方式 2:进入 bin 目录启动(简化路径)
cd /usr/local/redis/bin
./redis-server ../conf/redis.conf
验证启动成功:
bash
# 查看 Redis 进程
ps aux | grep redis
# 若显示类似以下内容,说明启动成功:
# root 1234 0.0 0.1 141848 2048 ? Ssl 10:00 0:00 ./redis-server 0.0.0.0:6379
(2)停止 Redis 服务(安全方式,避免数据丢失)
bash
# 方式 1:通过客户端发送 shutdown 命令(推荐)
/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 6379 -a 123456 shutdown save
# 参数说明:-h 主机IP,-p 端口,-a 密码(若未设密码可省略),save 表示停止前持久化数据
# 方式 2:若未设密码,简化命令
/usr/local/redis/bin/redis-cli shutdown save
禁止强制停止 :不要用 kill -9 进程号,会导致未持久化的数据丢失!
(3)启动多个 Redis 实例(同一服务器)
场景:开发/测试环境需要多个 Redis 服务(如一个用于缓存,一个用于消息队列),通过不同端口区分:
方法 1:启动时指定端口(临时方案)
bash
# 启动第一个实例(端口 6379,用默认配置)
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
# 启动第二个实例(端口 6380,指定配置文件或命令行参数)
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf --port 6380
方法 2:创建多配置目录(推荐,生产级)
bash
# 1. 创建以端口命名的目录(清晰区分)
mkdir -p /usr/local/redis/6379 /usr/local/redis/6380
# 2. 拷贝 bin 和 conf 到对应目录
cp -r /usr/local/redis/bin /usr/local/redis/6379/
cp -r /usr/local/redis/conf /usr/local/redis/6379/
cp -r /usr/local/redis/bin /usr/local/redis/6380/
cp -r /usr/local/redis/conf /usr/local/redis/6380/
# 3. 修改 6380 目录的配置文件(port 改为 6380,dir 改为 /usr/local/redis/6380/data)
vim /usr/local/redis/6380/conf/redis.conf
# 4. 分别启动两个实例
cd /usr/local/redis/6379/bin && ./redis-server ../conf/redis.conf
cd /usr/local/redis/6380/bin && ./redis-server ../conf/redis.conf
5. Redis 客户端连接(操作 Redis 的入口)
Redis 自带客户端 redis-cli,用于发送命令操作 Redis 服务端,支持本地和远程连接。
(1)本地连接(同一服务器)
bash
# 简化命令(默认连接 127.0.0.1:6379)
/usr/local/redis/bin/redis-cli
# 若设了密码,连接后需认证
127.0.0.1:6379> auth 123456 # 输入密码,返回 OK 则认证成功
(2)远程连接(Windows/Mac 连接 Linux 服务器)
bash
# 命令格式:redis-cli -h 服务器IP -p 端口 -a 密码
redis-cli -h 192.168.0.209 -p 6379 -a 123456
Windows 客户端推荐 :除了命令行 redis-cli,还可以用可视化工具(如 Redis Desktop Manager、Another Redis Desktop Manager),操作更直观:
- 下载安装工具(官网或 GitHub 下载)
- 新建连接:输入服务器 IP、端口、密码,点击连接即可
(3)客户端常用测试命令
| 命令 | 功能 | 示例 | 返回值 |
|---|---|---|---|
| ping | 测试连接是否正常 | 192.168.0.209:6379> ping | PONG(正常) |
| set key value | 设置键值对 | 192.168.0.209:6379> set name zhangsan | OK |
| get key | 获取键对应的值 | 192.168.0.209:6379> get name | "zhangsan" |
| keys * | 查看所有键(生产环境慎用,会阻塞服务) | 192.168.0.209:6379> keys * | 所有键列表 |
| exists key | 判断键是否存在 | 192.168.0.209:6379> exists name | 1(存在)/0(不存在) |
| del key | 删除键 | 192.168.0.209:6379> del name | 1(成功)/0(失败) |
五、Redis 多数据库(了解即可,不推荐使用)
Redis 实例默认包含 16 个数据库(下标 0-15),类似 MySQL 的多个数据库,但功能非常简陋,不推荐在实际开发中使用。
1. 切换数据库
bash
192.168.0.209:6379> select 1 # 切换到 1 号数据库(默认 0 号)
OK
192.168.0.209:6379[1]> # 提示符后加 [1],表示当前在 1 号库
2. 数据库特性
- 每个数据库独立存储数据,切换后看不到其他库的键
- 不支持修改数据库名称,只能通过下标切换
- 关键坑点:
flushall命令会清空所有数据库 的数据(不管当前在哪个库),而flushdb只清空当前数据库数据
3. 为什么不推荐使用多数据库?
- 缺乏权限控制:无法给不同数据库设置不同密码
- 数据隔离性差:
flushall容易误删所有数据 - 运维不便:无法单独备份某个数据库,集群环境下不支持多数据库
推荐方案 :不同应用/模块使用不同的 Redis 实例(不同端口),而非同一实例的不同数据库。
六、Redis 核心数据类型(重中之重)
Redis 支持 5 种核心数据类型,每种类型都有特定的底层实现和适用场景,掌握它们是使用 Redis 的基础。
1. 字符串(string):最基础的键值对
(1)特点
- 存储任意类型的二进制数据(字符串、数字、图片二进制等)
- 最大存储容量:512MB
- 底层实现:简单动态字符串(SDS),而非 C 语言原生字符串
(2)SDS 底层结构(为什么 Redis 字符串这么强?)
C 语言字符串的缺点:
- 长度获取需要遍历(O(n) 时间)
- 不支持二进制数据(遇到
\0认为字符串结束) - 拼接/修改容易导致缓冲区溢出
Redis SDS 结构(sds.h 源码):
c
struct sdshdr {
unsigned int len; // 字符串实际长度(O(1) 获取)
unsigned int free; // 空闲空间(减少内存分配次数)
char buf[]; // 存储字符串的缓冲区(二进制安全)
};
SDS 优势:
- 二进制安全:buf 存储二进制数据,
\0只是普通字符,支持存储图片、视频等 - 长度获取 O(1):直接读取 len 字段
- 减少内存分配:free 字段记录空闲空间,拼接时先检查空闲空间,不够再扩容
(3)常用命令(详细示例+说明)
| 命令 | 功能 | 示例 | 返回值 | 注意事项 |
|---|---|---|---|---|
| SET key value | 设置键值对(覆盖已有键) | SET name zhangsan | OK | 若键已存在,会覆盖原 value |
| SETNX key value | 仅当键不存在时设置(分布式锁常用) | SETNX name lisi(若 name 已存在) | 0(失败) | NX = Not eXists |
| GET key | 获取键对应的值 | GET name | "zhangsan" 或 nil | 键不存在返回 nil |
| APPEND key value | 向字符串尾部追加内容 | APPEND name " is 20 years old" | 21(追加后总长度) | 键不存在则创建,value 为追加的内容 |
| STRLEN key | 获取字符串长度 | STRLEN name | 10("zhangsan" 长度) | 二进制数据也能正确获取长度 |
| MSET key1 val1 key2 val2 | 批量设置键值对 | MSET age 20 gender male | OK | 原子操作(要么全成功,要么全失败) |
| MGET key1 key2 | 批量获取键值对 | MGET name age | 1) "zhangsan" 2) "20" | 键不存在返回 nil |
| INCR key | 数字自增 1(仅支持整数 value) | SET num 10 → INCR num | 11 | 非数字 value 会报错 |
| INCRBY key step | 数字自增指定步长 | INCRBY num 5 | 15 | step 可以是负数(相当于自减) |
| DECR key | 数字自减 1 | DECR num | 14 | |
| DECRBY key step | 数字自减指定步长 | DECRBY num 3 | 11 | |
| SETEX key sec value | 设置键值对并指定过期时间(秒) | SETEX code 60 123456 | OK | 过期后键自动删除 |
| PSETEX key ms value | 过期时间单位为毫秒 | PSETEX code 3000 123456 | OK | 精确到毫秒的过期时间 |
(4)应用场景
- 缓存:存储用户信息、商品详情(序列化后存 string)
- 计数器:文章阅读量、商品库存、接口访问次数(INCR/DECR)
- 验证码:存储手机验证码,设置过期时间(SETEX)
- 分布式锁:SETNX + EXPIRE(防止死锁)
2. 哈希(hash):适合存储对象
(1)特点
- 存储字段和字段值的映射(类似 Java 的 Map<String, String>)
- 字段值只能是字符串,不能是其他类型(如 hash、list)
- 适合存储对象(如用户、商品),可单独修改对象的某个字段,无需整体更新
(2)底层实现
- 小数据量(字段少且值短):压缩列表(ziplist)→ 节省内存
- 大数据量:哈希表(hashtable)→ 保证查询效率
(3)常用命令
| 命令 | 功能 | 示例 | 返回值 |
|---|---|---|---|
| HSET key field value | 设置哈希表的字段值 | HSET user:1001 name zhangsan age 20 | 2(成功设置的字段数) |
| HGET key field | 获取哈希表的字段值 | HGET user:1001 name | "zhangsan" |
| HMSET key field1 val1 field2 val2 | 批量设置字段值 | HMSET user:1001 gender male addr beijing | OK |
| HMGET key field1 field2 | 批量获取字段值 | HMGET user:1001 name age | 1) "zhangsan" 2) "20" |
| HGETALL key | 获取哈希表所有字段和值 | HGETALL user:1001 | 1) "name" 2) "zhangsan" 3) "age" 4) "20" |
| HEXISTS key field | 判断字段是否存在 | HEXISTS user:1001 name | 1(存在)/0(不存在) |
| HSETNX key field value | 仅当字段不存在时设置 | HSETNX user:1001 name lisi | 0(失败,name 已存在) |
| HDEL key field1 field2 | 删除字段 | HDEL user:1001 addr | 1(成功删除的字段数) |
| HKEYS key | 获取所有字段名 | HKEYS user:1001 | 1) "name" 2) "age" |
| HVALS key | 获取所有字段值 | HVALS user:1001 | 1) "zhangsan" 2) "20" |
| HLEN key | 获取字段总数 | HLEN user:1001 | 2 |
| HINCRBY key field step | 字段值自增指定步长(仅支持整数) | HINCRBY user:1001 age 1 | 21 |
(4)应用场景
- 存储对象:用户信息(id、name、age、addr)、商品信息(id、name、price、stock)
- 优势:相比 string 序列化(如 JSON),hash 可以单独修改某个字段,节省带宽和内存
3. 列表(list):有序的字符串列表
(1)特点
- 有序(插入顺序)、可重复
- 支持两端插入/删除,中间查询较慢
- 底层实现:双向链表(double linked list)→ 两端操作 O(1),中间操作 O(n)
(2)常用命令(重点:两端操作)
| 命令 | 功能 | 示例 | 返回值 |
|---|---|---|---|
| LPUSH key value1 value2 | 向列表左侧(头部)插入元素 | LPUSH list:1 1 2 3 | 3(列表长度) |
| RPUSH key value1 value2 | 向列表右侧(尾部)插入元素 | RPUSH list:1 4 5 6 | 6(列表长度) |
| LPOP key | 从列表左侧弹出元素(删除并返回) | LPOP list:1 | "3" |
| RPOP key | 从列表右侧弹出元素 | RPOP list:1 | "6" |
| LRANGE key start stop | 获取列表片段(start=0,stop=-1 表示所有) | LRANGE list:1 0 -1 | 1) "2" 2) "1" 3) "4" 4) "5" |
| LLEN key | 获取列表长度 | LLEN list:1 | 4 |
| LINDEX key index | 获取指定索引的元素(0 开头,-1 结尾) | LINDEX list:1 2 | "4" |
| LSET key index value | 修改指定索引的元素值 | LSET list:1 2 44 | OK |
| LREM key count value | 删除前 count 个值为 value 的元素 | LREM list:1 1 44 | 1(删除的元素数) |
| LTRIM key start stop | 保留列表片段,删除其他元素 | LTRIM list:1 0 2 | OK |
| RPOPLPUSH source dest | 从 source 列表右侧弹出元素,插入 dest 左侧 | RPOPLPUSH list:1 list:2 | "5"(弹出的元素) |
(3)应用场景
- 消息队列:LPUSH + RPOP(生产者左侧插入,消费者右侧弹出)
- 最新消息列表:LPUSH 插入新消息,LRANGE 获取前 N 条(如朋友圈最新动态)
- 在线好友列表:列表存储好友 ID,支持添加/删除好友
- 任务队列:RPOPLPUSH 实现安全的任务处理(避免任务丢失)
4. 集合(set):无序的唯一集合
(1)特点
- 无序、元素唯一(自动去重)
- 支持集合运算(交集、并集、差集)
- 底层实现:哈希表(hashtable)→ 查找、插入、删除 O(1)
(2)常用命令
| 命令 | 功能 | 示例 | 返回值 |
|---|---|---|---|
| SADD key member1 member2 | 向集合添加元素 | SADD set:1 a b c d | 4(添加的元素数) |
| SMEMBERS key | 获取集合所有元素 | SMEMBERS set:1 | 1) "a" 2) "b" 3) "c" 4) "d" |
| SISMEMBER key member | 判断元素是否在集合中 | SISMEMBER set:1 a | 1(存在)/0(不存在) |
| SREM key member1 member2 | 从集合删除元素 | SREM set:1 d | 1(删除的元素数) |
| SCARD key | 获取集合元素个数 | SCARD set:1 | 3 |
| SINTER key1 key2 | 求两个集合的交集(共同元素) | SADD set:2 b c e → SINTER set:1 set:2 | 1) "b" 2) "c" |
| SUNION key1 key2 | 求两个集合的并集(所有元素,去重) | SUNION set:1 set:2 | 1) "a" 2) "b" 3) "c" 4) "e" |
| SDIFF key1 key2 | 求两个集合的差集(key1 有而 key2 没有) | SDIFF set:1 set:2 | 1) "a" |
| SRANDMEMBER key [count] | 随机获取集合中的元素 | SRANDMEMBER set:1 2 | 1) "b" 2) "a" |
| SPOP key [count] | 随机弹出集合中的元素(删除并返回) | SPOP set:1 | "c" |
(3)应用场景
- 去重:存储用户标签(如"运动""美食"),自动去重
- 好友关系:存储用户的好友 ID,SINTER 求共同好友
- 抽奖活动:SPOP 随机抽取中奖用户
- 标签匹配:SUNION 合并多个标签的用户,SDIFF 筛选不包含某个标签的用户
5. 有序集合(sorted set/zset):有序的唯一集合
(1)特点
- 元素唯一(去重)、有序(按分数排序)
- 每个元素关联一个分数(score),按分数升序/降序排列
- 底层实现:压缩列表(小数据量)+ 跳表(skiplist)+ 哈希表(大数据量)→ 兼顾排序和查询效率
(2)跳表(为什么 zset 排序这么快?)
跳表是一种有序数据结构,通过在原始链表上增加多层索引,实现快速查找(类似字典的目录):
- 原始链表:查找元素 O(n)
- 跳表:通过索引层"跳跃"查找,平均 O(log n),最坏 O(n)
- Redis zset 用跳表保证排序,哈希表保证元素唯一性(快速判断元素是否存在)
(3)常用命令
| 命令 | 功能 | 示例 | 返回值 |
|---|---|---|---|
| ZADD key score1 member1 score2 member2 | 向有序集合添加元素(分数+元素) | ZADD rank:score 80 zhangsan 90 lisi 85 wangwu | 3(添加的元素数) |
| ZSCORE key member | 获取元素的分数 | ZSCORE rank:score lisi | "90" |
| ZRANGE key start stop [WITHSCORES] | 按分数升序排列,获取片段 | ZRANGE rank:score 0 -1 WITHSCORES | 1) "zhangsan" 2) "80" 3) "wangwu" 4) "85" 5) "lisi" 6) "90" |
| ZREVRANGE key start stop [WITHSCORES] | 按分数降序排列,获取片段 | ZREVRANGE rank:score 0 -1 WITHSCORES | 1) "lisi" 2) "90" 3) "wangwu" 4) "85" 5) "zhangsan" 6) "80" |
| ZRANGEBYSCORE key min max [WITHSCORES] | 按分数范围筛选元素(升序) | ZRANGEBYSCORE rank:score 80 90 WITHSCORES | 同上 |
| ZINCRBY key increment member | 元素分数自增指定值(可负数) | ZINCRBY rank:score 5 zhangsan | "85" |
| ZCARD key | 获取有序集合元素个数 | ZCARD rank:score | 3 |
| ZCOUNT key min max | 统计分数范围内的元素个数 | ZCOUNT rank:score 80 85 | 2 |
| ZRANK key member | 获取元素的升序排名(0 开头) | ZRANK rank:score zhangsan | 0(第一名) |
| ZREVRANK key member | 获取元素的降序排名(0 开头) | ZREVRANK rank:score zhangsan | 2(第三名) |
| ZREM key member1 member2 | 删除有序集合中的元素 | ZREM rank:score wangwu | 1(删除的元素数) |
(4)应用场景
- 排行榜:商品销量排行、用户积分排行、文章阅读量排行(ZREVRANGE)
- 范围筛选:筛选分数在 80-90 分的学生(ZRANGEBYSCORE)
- 延迟队列:分数存储时间戳,ZRANGEBYSCORE 获取到期任务(如订单超时未支付)
七、Redis 键的过期时间(缓存必备)
Redis 支持给键设置过期时间,到期后自动删除,非常适合缓存场景(如缓存商品详情,1 小时后过期)。
1. 常用命令
| 命令 | 功能 | 示例 | 返回值 |
|---|---|---|---|
| EXPIRE key seconds | 设置键的过期时间(秒) | EXPIRE name 60 | 1(成功)/0(键不存在) |
| PEXPIRE key ms | 设置过期时间(毫秒) | PEXPIRE name 30000 | 1 |
| TTL key | 查看键的剩余过期时间(秒) | TTL name | 30(剩余 30 秒)/ -1(永不过期)/ -2(已过期) |
| PTTL key | 查看剩余过期时间(毫秒) | PTTL name | 30000 |
| PERSIST key | 取消键的过期时间(永不过期) | PERSIST name | 1(成功)/0(键不存在或无过期时间) |
2. 注意事项
- 过期时间精确到毫秒(Redis 4.0+)
- 键过期后,Redis 会自动删除(两种策略:惰性删除+定期删除):
- 惰性删除:访问键时才检查是否过期,节省 CPU,可能浪费内存
- 定期删除:每隔一段时间扫描部分过期键,平衡 CPU 和内存
- 过期时间会随键的修改而重置:如
SET name zhangsan → EXPIRE name 60 → SET name lisi,此时 name 的过期时间消失(永不过期)
八、Java 操作 Redis(Jedis + Spring 整合)
Redis 支持多种编程语言客户端,Java 中最常用的是 Jedis(官方推荐),结合 Spring 可以更方便地集成到项目中。
1. Jedis 原生使用(基础)
(1)导入依赖(Maven)
xml
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version> <!-- 稳定版本,兼容性好 -->
</dependency>
(2)两种连接方式
方式 1:单实例连接(不推荐,频繁创建/关闭连接,效率低)
java
import redis.clients.jedis.Jedis;
public class JedisSingleTest {
public static void main(String[] args) {
// 1. 创建 Jedis 实例(参数:Redis 服务器 IP、端口)
Jedis jedis = new Jedis("192.168.0.209", 6379);
// 2. 若设了密码,认证
jedis.auth("123456");
try {
// 3. 操作 Redis(如 set/get)
jedis.set("name", "zhangsan");
String name = jedis.get("name");
System.out.println(name); // 输出:zhangsan
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4. 关闭连接(必须关闭,否则资源泄露)
jedis.close();
}
}
}
方式 2:连接池连接(推荐,复用连接,提高效率)
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisPoolTest {
public static void main(String[] args) {
// 1. 配置连接池参数
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(30); // 最大连接数
poolConfig.setMaxIdle(2); // 最大空闲连接数(空闲时保留的连接)
poolConfig.setTestOnBorrow(true); // 借连接时测试连接是否可用
// 2. 创建连接池
JedisPool jedisPool = new JedisPool(poolConfig, "192.168.0.209", 6379, 10000, "123456");
// 参数说明:连接池配置、IP、端口、超时时间(10秒)、密码
Jedis jedis = null;
try {
// 3. 从连接池获取连接
jedis = jedisPool.getResource();
// 4. 操作 Redis
jedis.hset("user:1002", "name", "lisi");
String userName = jedis.hget("user:1002", "name");
System.out.println(userName); // 输出:lisi
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5. 归还连接(关闭连接池会释放所有连接)
if (jedis != null) {
jedis.close(); // 实际是归还到连接池,而非关闭连接
}
}
}
}
2. Spring 整合 Redis(企业级开发)
Spring 提供 spring-data-redis 模块,简化 Redis 操作,支持依赖注入、事务管理等。
(1)导入依赖(Maven)
xml
<!-- Spring 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.0.RELEASE</version>
</dependency>
<!-- Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
(2)Spring 配置文件(application.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<!-- 扫描组件(如 Redis 工具类) -->
<context:component-scan base-package="com.example.redis"/>
<!-- 1. 配置 Jedis 连接池 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="50"/> <!-- 最大连接数 -->
<property name="maxIdle" value="10"/> <!-- 最大空闲连接数 -->
<property name="minIdle" value="5"/> <!-- 最小空闲连接数 -->
<property name="testOnBorrow" value="true"/> <!-- 借连接时测试 -->
<property name="testOnReturn" value="true"/> <!-- 还连接时测试 -->
</bean>
<!-- 2. 配置 Redis 连接工厂 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="192.168.0.209"/> <!-- Redis 服务器 IP -->
<property name="port" value="6379"/> <!-- 端口 -->
<property name="password" value="123456"/> <!-- 密码 -->
<property name="poolConfig" ref="jedisPoolConfig"/> <!-- 连接池配置 -->
<property name="timeout" value="10000"/> <!-- 连接超时时间(毫秒) -->
</bean>
<!-- 3. 配置 RedisTemplate(核心操作类) -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<!-- 配置序列化方式(默认 JdkSerializationRedisSerializer,可改为 StringRedisSerializer) -->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
</bean>
</beans>
(3)Redis 工具类(封装常用操作)
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 1. 操作 string 类型
public void setString(String key, Object value) {
ValueOperations<String, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
}
public void setString(String key, Object value, long timeout) {
ValueOperations<String, Object> operations = redisTemplate.opsForValue();
operations.set(key, value, timeout, TimeUnit.SECONDS);
}
public Object getString(String key) {
ValueOperations<String, Object> operations = redisTemplate.opsForValue();
return operations.get(key);
}
// 2. 操作 hash 类型(其他类型类似,可自行扩展)
public void setHash(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public Object getHash(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
// 3. 删除键
public boolean deleteKey(String key) {
return redisTemplate.delete(key);
}
// 4. 设置过期时间
public boolean expireKey(String key, long timeout) {
return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
}
(4)测试 Spring 整合 Redis
java
import com.example.redis.RedisUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringRedisTest {
public static void main(String[] args) {
// 1. 加载 Spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// 2. 获取 RedisUtil 实例
RedisUtil redisUtil = context.getBean(RedisUtil.class);
// 3. 测试操作
redisUtil.setString("spring:name", "redis", 60); // 设置过期时间 60 秒
Object name = redisUtil.getString("spring:name");
System.out.println(name); // 输出:redis
redisUtil.setHash("spring:user", "name", "zhangsan");
Object userName = redisUtil.getHash("spring:user", "name");
System.out.println(userName); // 输出:zhangsan
}
}
九、Redis 高级特性(数据安全与高可用)
1. 持久化(防止数据丢失)
Redis 数据默认存储在内存中,服务器重启(如断电、宕机)会导致数据丢失。持久化就是将内存中的数据同步到硬盘,重启后从硬盘恢复数据。
Redis 支持两种持久化方式:RDB 和 AOF,可单独使用或混合使用。
(1)RDB 持久化(默认开启)
原理:
- 定期创建内存数据的快照(二进制文件,.rdb),保存到硬盘
- 触发机制:
- 自动触发:配置文件中
save指令(如save 900 1表示 15 分钟内至少 1 个键修改,触发快照) - 手动触发:执行
SAVE(阻塞服务,不推荐)或BGSAVE(后台执行,推荐)命令
- 自动触发:配置文件中
配置(redis.conf):
ini
# 自动触发快照条件(多个条件"或"关系)
save 900 1 # 900秒(15分钟)内至少1个键修改
save 300 10 # 300秒(5分钟)内至少10个键修改
save 60 10000 # 60秒内至少10000个键修改
# RDB 文件路径和名称
dir /usr/local/redis/data # 文件存放目录
dbfilename dump.rdb # 文件名
优缺点:
| 优点 | 缺点 |
|---|---|
| 快照文件体积小,恢复速度快(适合大规模数据) | 数据丢失风险:两次快照之间的修改会丢失(如配置 15 分钟快照,宕机后丢失 15 分钟数据) |
| 备份和迁移方便(复制 .rdb 文件即可) | 触发快照时可能阻塞服务(BGSAVE 会 fork 子进程,消耗 CPU 和内存) |
(2)AOF 持久化(需手动开启)
原理:
- 记录每一条修改数据的命令(如 SET、HSET、LPUSH),追加到 AOF 文件(文本文件)
- 重启时,Redis 重新执行 AOF 文件中的命令,恢复数据
配置(redis.conf):
ini
appendonly yes # 开启 AOF 持久化(默认 no)
appendfilename appendonly.aof # AOF 文件名
dir /usr/local/redis/data # 文件存放目录(与 RDB 相同)
# AOF 同步策略(重要,影响性能和数据安全性)
appendfsync everysec # 每秒同步一次(推荐,平衡性能和安全)
# appendfsync always # 每次命令都同步(最安全,性能最差)
# appendfsync no # 由操作系统决定同步时机(性能最好,数据丢失风险最高)
AOF 文件重写:
- 问题:AOF 文件会随命令增多而变大,恢复速度变慢
- 解决:执行
BGREWRITEAOF命令,Redis 会创建新的 AOF 文件,只保留恢复数据所需的最小命令集(如多次修改同一个键,只保留最终的 SET 命令) - 自动重写:配置文件中设置
auto-aof-rewrite-percentage 100(AOF 文件大小增长 100% 时触发)和auto-aof-rewrite-min-size 64mb(AOF 文件最小 64MB 才触发)
优缺点:
| 优点 | 缺点 |
|---|---|
| 数据安全性高:最多丢失 1 秒数据(everysec 策略) | AOF 文件体积大,恢复速度比 RDB 慢 |
| 支持秒级数据恢复 | 写入性能比 RDB 低(需记录命令并同步文件) |
(3)混合持久化(Redis 4.0+ 支持)
- 原理:结合 RDB 和 AOF 的优点,AOF 文件中包含 RDB 快照 + 后续的命令日志
- 配置:
aof-use-rdb-preamble yes(默认开启) - 优点:恢复速度快(RDB 快照)+ 数据安全(AOF 命令日志)
(4)持久化方案选择
- 生产环境推荐:混合持久化(Redis 4.0+)
- 数据安全性要求极高(如金融数据):AOF + 主从复制
- 数据可以接受少量丢失(如缓存):RDB 或混合持久化
2. 主从复制(解决单点故障)
(1)什么是主从复制?
- 主节点(master):写入数据,同步数据到从节点
- 从节点(slave):只读数据,从主节点同步数据
- 作用:
- 数据备份:从节点是主节点的副本,防止主节点数据丢失
- 读写分离:主节点写,从节点读,提高并发处理能力
- 负载均衡:多个从节点分担读请求,减轻主节点压力
(2)主从复制原理(同步过程)
- 从节点连接主节点,发送
PSYNC命令(部分同步) - 主节点验证从节点的
runid(主节点唯一标识)和offset(数据偏移量):- 若
runid不匹配(从节点第一次连接或主节点重启):执行全量同步(主节点生成 RDB 快照,发送给从节点,从节点加载快照后,主节点再发送后续命令) - 若
runid匹配但offset不一致:执行部分同步(主节点发送从节点缺失的命令日志)
- 若
- 同步完成后,主节点每执行一条修改命令,都会发送给从节点,保持数据同步
(3)主从配置(简单易懂)
主节点配置:
- 无需特殊配置,确保主节点开启远程访问(
bind 0.0.0.0),设好密码(若需)
从节点配置:
- 修改从节点的
redis.conf文件:
ini
slaveof 192.168.0.209 6379 # 主节点 IP 和端口
masterauth 123456 # 主节点密码(若主节点设了密码)
slave-read-only yes # 从节点只读(默认 yes,生产环境推荐)
- 重启从节点,执行
info replication命令验证:
bash
192.168.0.210:6379> info replication
# Replication
role:slave # 角色为从节点
master_host:192.168.0.209 # 主节点 IP
master_port:6379 # 主节点端口
master_link_status:up # 连接状态正常
(4)主从复制注意事项
- 主节点宕机后,从节点不会自动升级为主节点,需手动切换(或用哨兵模式)
- 主从复制不会阻塞主节点,同步时主节点可正常处理写请求
- 从节点可以作为其他从节点的主节点(级联复制),适合大规模集群
3. Redis 集群(解决高可用和扩容)
当单节点 Redis 性能不足(如并发过高)或存储容量不够时,需要搭建 Redis 集群(Redis Cluster)。
(1)集群核心特性
- 分布式存储:数据分散存储在多个节点(主节点),每个主节点负责一部分哈希槽
- 哈希槽:Redis 集群将 16384 个哈希槽(0-16383)分配给主节点,数据根据
CRC16(key) % 16384计算哈希槽,存储到对应主节点 - 高可用:每个主节点至少有一个从节点,主节点宕机后,从节点自动升级为主节点
- 容错性:集群至少需要 3 个主节点,最多允许
(N-1)/2个主节点宕机(N 为总主节点数)
(2)集群搭建步骤(CentOS7.5)
前置准备:安装 Ruby 环境(集群管理工具依赖)
bash
# 1. 安装 Ruby
yum install -y ruby rubygems
# 2. 安装 Redis Ruby 客户端
gem install redis
# 若报错(Redis 4.0+ 需要 Ruby 2.2+),升级 Ruby:
# 安装 RVM(Ruby 版本管理器)
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable
source /etc/profile.d/rvm.sh
# 安装 Ruby 2.6.3 并设为默认
rvm install 2.6.3
rvm use 2.6.3 default
# 重新安装 Redis Ruby 客户端
gem install redis
步骤 1:创建集群节点目录
bash
# 创建集群根目录
mkdir -p /usr/local/redis-cluster
# 创建 6 个节点目录(3 主 3 从,端口 7001-7006)
mkdir -p /usr/local/redis-cluster/{7001,7002,7003,7004,7005,7006}/{data,logs}
步骤 2:配置每个节点的 redis.conf
bash
# 复制基础配置文件到每个节点目录
cp /usr/local/redis/conf/redis.conf /usr/local/redis-cluster/7001/
# 修改 7001 节点配置(其他节点类似,只需改 port 和目录)
vim /usr/local/redis-cluster/7001/redis.conf
关键配置(每个节点需修改 port、dir、logfile):
ini
daemonize yes
bind 0.0.0.0
port 7001 # 每个节点端口不同(7001-7006)
dir /usr/local/redis-cluster/7001/data # 数据目录
logfile /usr/local/redis-cluster/7001/logs/redis.log # 日志目录
cluster-enabled yes # 开启集群模式
cluster-config-file nodes-7001.conf # 集群配置文件(自动生成)
cluster-node-timeout 15000 # 节点超时时间(15秒)
appendonly yes # 开启 AOF 持久化
requirepass 123456 # 密码
masterauth 123456 # 主节点密码(从节点同步用)
步骤 3:启动所有节点
bash
# 编写启动脚本(cluster-start.sh)
#!/bin/bash
for port in {7001..7006}; do
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/$port/redis.conf
done
# 赋予执行权限并运行
chmod +x cluster-start.sh
./cluster-start.sh
# 验证启动成功(查看 6 个 Redis 进程)
ps aux | grep redis-server
步骤 4:创建集群
bash
# 进入 Redis 安装目录,执行集群创建命令
cd /usr/local/redis/bin
./redis-trib.rb create --replicas 1 192.168.0.209:7001 192.168.0.209:7002 192.168.0.209:7003 192.168.0.209:7004 192.168.0.209:7005 192.168.0.209:7006
--replicas 1:每个主节点分配 1 个从节点- 输入
yes确认哈希槽分配 - 成功后,集群会自动分配主从关系和哈希槽
步骤 5:验证集群
bash
# 连接集群(-c 表示集群模式)
./redis-cli -c -h 192.168.0.209 -p 7001 -a 123456
# 查看集群信息
192.168.0.209:7001> cluster info
cluster_state:ok # 集群状态正常
cluster_slots_assigned:16384 # 所有哈希槽已分配
# 查看节点信息
192.168.0.209:7001> cluster nodes
(3)Java 操作 Redis 集群(JedisCluster)
java
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.Set;
public class JedisClusterTest {
public static void main(String[] args) {
// 1. 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(50);
poolConfig.setMaxIdle(10);
// 2. 集群节点集合
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.0.209", 7001));
nodes.add(new HostAndPort("192.168.0.209", 7002));
nodes.add(new HostAndPort("192.168.0.209", 7003));
nodes.add(new HostAndPort("192.168.0.209", 7004));
nodes.add(new HostAndPort("192.168.0.209", 7005));
nodes.add(new HostAndPort("192.168.0.209", 7006));
// 3. 创建 JedisCluster 实例(自动负载均衡和故障转移)
JedisCluster jedisCluster = new JedisCluster(nodes, 10000, 5000, 3, "123456", poolConfig);
// 参数说明:节点集合、连接超时、读取超时、重试次数、密码、连接池配置
// 4. 操作集群
jedisCluster.set("cluster:name", "redis-cluster");
String name = jedisCluster.get("cluster:name");
System.out.println(name); // 输出:redis-cluster
// 5. 关闭集群连接(释放资源)
jedisCluster.close();
}
}
十、Redis 常见配置参数说明(生产环境必备)
以下是 redis.conf 中常用的配置参数,根据实际需求调整:
| 配置项 | 默认值 | 说明 | 生产环境建议 |
|---|---|---|---|
| daemonize | no | 是否后台运行 | yes |
| bind | 127.0.0.1 | 绑定的 IP 地址(0.0.0.0 允许所有地址访问) | 0.0.0.0(开发/测试),具体 IP(生产) |
| port | 6379 | 服务端口 | 自定义(如 6380),避免默认端口暴露 |
| requirepass | 无 | 访问密码 | 设复杂密码(如 16 位字母+数字+符号) |
| timeout | 0 | 客户端连接超时时间(秒,0 表示无超时) | 300(5 分钟) |
| loglevel | notice | 日志级别(debug/verbose/notice/warning) | notice(生产),debug(开发) |
| logfile | 无 | 日志文件路径 | /usr/local/redis/logs/redis.log |
| dir | ./ | 数据存储目录(RDB/AOF 文件) | /usr/local/redis/data |
| maxclients | 10000 | 最大客户端连接数 | 根据服务器性能调整(如 5000) |
| maxmemory | 0 | 最大使用内存(0 表示无限制) | 设为服务器内存的 70%-80%(如 8GB 内存设 6GB) |
| maxmemory-policy | volatile-lru | 内存满时的淘汰策略(如 LRU 淘汰最近最少使用的键) | volatile-lru(优先淘汰过期键) |
| appendonly | no | 是否开启 AOF 持久化 | yes(生产) |
| appendfsync | everysec | AOF 同步策略 | everysec |
| cluster-enabled | no | 是否开启集群模式 | yes(集群环境) |
| cluster-node-timeout | 15000 | 集群节点超时时间(毫秒) | 15000(15 秒) |
十一、Redis 常见问题与解决方案(实战必备)
1. 缓存穿透(查询不存在的键,导致请求直达数据库)
- 原因:恶意请求查询不存在的键(如用户 ID=-1),Redis 缓存未命中,每次都访问数据库,导致数据库压力过大
- 解决方案:
- 缓存空值:查询不存在的键时,缓存空值(如 ""),设置短期过期时间(如 5 分钟)
- 布隆过滤器:在 Redis 前拦截不存在的键,避免请求直达 Redis 和数据库
2. 缓存击穿(热点键过期,大量请求直达数据库)
- 原因:某个热点键(如热门商品 ID)过期,此时大量请求同时访问,Redis 未命中,全部直达数据库
- 解决方案:
- 热点键永不过期:不设置过期时间,定期后台更新
- 互斥锁:第一个请求查询数据库时,加锁(如 Redis SETNX),其他请求等待锁释放后从缓存获取数据
- 预热缓存:系统启动时,提前加载热点键到缓存
3. 缓存雪崩(大量键同时过期,导致数据库压力骤增)
- 原因:缓存中大量键设置了相同的过期时间,到期后同时失效,大量请求直达数据库
- 解决方案:
- 过期时间加随机值:如原本过期时间 1 小时,改为 1 小时 ± 10 分钟,避免同时过期
- 分层缓存:不同层级的缓存设置不同的过期时间(如本地缓存 + Redis 缓存)
- 服务熔断:数据库压力过大时,暂时拒绝请求,返回默认值
4. Redis 性能优化
- 避免使用
keys *命令(阻塞服务),用scan命令分批遍历键 - 合理选择数据类型:如存储对象用 hash,不用 string 序列化
- 控制键的大小:键名尽量简短(如
user:1001而非user_info_1001),值避免过大(如超过 100KB) - 开启持久化时,选择合适的同步策略(如 AOF everysec),避免影响性能
- 集群环境下,均匀分配哈希槽,避免某个主节点负载过高
5. Redis 未授权访问漏洞
- 风险:未设置密码、绑定 0.0.0.0,导致恶意攻击者访问 Redis,篡改数据或植入木马
- 解决方案:
- 设复杂访问密码(
requirepass) - 绑定具体的 IP 地址(
bind 192.168.0.209),限制访问来源 - 关闭不必要的命令(如
config、flushall),通过rename-command重命名
- 设复杂访问密码(