摘要:Redis作为高性能的键值对数据库,凭借其高速读写、支持多种数据结构、可持久化等特性,已成为企业级项目中缓存、分布式锁、消息队列等场景的首选工具。本文基于Redis 7.x,结合电商、微服务等实战场景,详细讲解Redis核心数据结构、缓存设计模式、缓存问题解决(缓存穿透、缓存击穿、缓存雪崩)、分布式锁实现、持久化配置等核心知识点,附完整命令与代码案例,帮助后端开发者、运维工程师快速掌握Redis实战技巧,提升系统性能与稳定性,适合Redis入门与进阶学习。
一、前言:Redis的核心价值与应用场景
Redis 7.x相比之前版本,带来了诸多优化:支持多线程IO,提升读写性能;新增Redis Functions(函数),支持自定义脚本逻辑;优化持久化机制,提升数据安全性;增强集群功能,提升可扩展性。
在企业级项目中,Redis的核心应用场景包括:数据缓存(减轻数据库压力)、分布式锁(解决微服务并发问题)、消息队列(简单的异步通信)、计数器(如点赞数、访问量)、会话存储(如用户登录会话)等。本文聚焦Redis最核心的缓存设计与分布式锁,结合实战场景,解决实际开发中的常见问题。
二、核心基础:Redis 7.x安装与核心数据结构
2.1 Redis 7.x安装(CentOS 8)
bash
# 1. 安装依赖
yum install -y gcc gcc-c++ make
# 2. 下载Redis 7.x源码包
wget https://download.redis.io/releases/redis-7.2.4.tar.gz
# 3. 解压源码包
tar -zxvf redis-7.2.4.tar.gz
# 4. 编译安装
cd redis-7.2.4
make
make install PREFIX=/usr/local/redis
# 5. 配置Redis(复制配置文件)
cp redis.conf /usr/local/redis/conf/
# 6. 修改配置文件(/usr/local/redis/conf/redis.conf)
# 允许远程访问(注释bind 127.0.0.1)
# protected-mode no(关闭保护模式)
# daemonize yes(后台运行)
# requirepass 123456(设置密码,可选)
# 7. 启动Redis
/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf
# 8. 连接Redis
/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 6379 -a 123456
# 9. 验证启动
127.0.0.1:6379> ping
PONG # 启动成功
2.2 Redis核心数据结构(实战必备)
Redis支持5种核心数据结构,每种结构有其特定的应用场景,掌握其用法是Redis实战的基础。
bash
# 1. String(字符串):适用于缓存、计数器、会话存储等场景
# 设置值(key=user:1001, value=JSON字符串)
set user:1001 '{"id":1001,"username":"zhangsan","age":20}'
# 获取值
get user:1001
# 设置过期时间(3600秒)
setex user:1001 3600 '{"id":1001,"username":"zhangsan","age":20}'
# 自增(计数器)
incr like:1001 # 点赞数+1
# 自减
decr like:1001 # 点赞数-1
# 2. Hash(哈希):适用于存储对象(如用户信息、商品信息)
# 设置哈希值(key=user:1001,字段=username,值=zhangsan)
hset user:1001 username zhangsan age 20
# 获取单个字段值
hget user:1001 username
# 获取所有字段值
hgetall user:1001
# 删除字段
hdel user:1001 age
# 3. List(列表):适用于消息队列、最新消息列表等场景
# 从左侧插入
lpush message:1001 "hello" "world"
# 从右侧插入
rpush message:1001 "redis"
# 获取列表所有元素
lrange message:1001 0 -1
# 从左侧弹出
lpop message:1001
# 从右侧弹出
rpop message:1001
# 4. Set(集合):适用于去重、交集、并集等场景(如好友列表、标签)
# 添加元素
sadd tag:1001 java python go
# 获取所有元素
smembers tag:1001
# 判断元素是否存在
sismember tag:1001 java
# 求两个集合的交集(好友共同关注)
sinter tag:1001 tag:1002
# 删除元素
srem tag:1001 go
# 5. Sorted Set(有序集合):适用于排行榜、优先级队列等场景
# 添加元素(score=分数,用于排序)
zadd rank:1001 90 zhangsan 85 lisi 95 wangwu
# 获取有序集合(升序)
zrange rank:1001 0 -1 withscores
# 获取有序集合(降序)
zrevrange rank:1001 0 -1 withscores
# 获取元素分数
zscore rank:1001 zhangsan
# 删除元素
zrem rank:1001 lisi
三、实战模块:缓存设计与分布式锁
3.1 模块1:缓存设计模式(实战案例)
缓存设计的核心是"缓存数据库热点数据",减少数据库访问压力,常用的缓存设计模式包括:Cache-Aside(旁路缓存)、Write-Through(写穿透)、Write-Back(写回),其中Cache-Aside模式最常用。
java
// 1. Cache-Aside模式(旁路缓存):最常用,适用于读多写少场景
// 核心逻辑:查询时先查缓存,缓存没有则查数据库,再将数据存入缓存;更新时先更数据库,再删缓存
import redis.clients.jedis.Jedis;
import com.alibaba.fastjson.JSON;
public class CacheAsideDemo {
private final Jedis jedis = new Jedis("127.0.0.1", 6379);
private final UserDao userDao = new UserDao(); // 数据库操作类
// 1. 查询用户信息(先查缓存,再查数据库)
public User getUserById(Long userId) {
String key = "user:" + userId;
// 1. 先查缓存
String userJson = jedis.get(key);
if (userJson != null) {
// 缓存命中,返回数据
return JSON.parseObject(userJson, User.class);
}
// 2. 缓存未命中,查数据库
User user = userDao.selectById(userId);
if (user != null) {
// 3. 将数据库数据存入缓存(设置过期时间,避免缓存雪崩)
jedis.setex(key, 3600, JSON.toJSONString(user));
}
return user;
}
// 2. 更新用户信息(先更数据库,再删缓存)
public void updateUser(User user) {
// 1. 更新数据库
userDao.update(user);
// 2. 删除缓存(避免缓存与数据库数据不一致)
String key = "user:" + user.getId();
jedis.del(key);
}
// 3. 删除用户信息(先删数据库,再删缓存)
public void deleteUser(Long userId) {
userDao.deleteById(userId);
String key = "user:" + userId;
jedis.del(key);
}
}
// 2. 缓存key设计规范(避免key冲突、便于管理)
// 规范:业务模块:对象:唯一标识[:属性]
// 示例:
// 用户模块:user:1001(用户信息)、user:1001:orders(用户订单)
// 商品模块:product:2001(商品信息)、product:2001:stock(商品库存)
// 缓存过期时间:根据业务场景设置,热点数据可设置1-2小时,非热点数据可设置更久
3.2 模块2:缓存常见问题解决(穿透、击穿、雪崩)
缓存使用过程中,容易出现缓存穿透、缓存击穿、缓存雪崩三个问题,若不解决,会导致数据库压力剧增,甚至系统崩溃,本文提供针对性的解决方案。
java
// 1. 缓存穿透:查询不存在的数据,缓存和数据库都没有,导致每次都查数据库
// 解决方案:缓存空值、布隆过滤器(推荐)
// 方案1:缓存空值(简单易实现,适合数据量不大的场景)
public User getUserById(Long userId) {
String key = "user:" + userId;
String userJson = jedis.get(key);
if (userJson != null) {
// 缓存命中(包括空值)
return JSON.parseObject(userJson, User.class);
}
// 查数据库
User user = userDao.selectById(userId);
if (user != null) {
jedis.setex(key, 3600, JSON.toJSONString(user));
} else {
// 缓存空值,设置较短的过期时间(如60秒),避免缓存占用过多空间
jedis.setex(key, 60, JSON.toJSONString(null));
}
return user;
}
// 方案2:布隆过滤器(适合数据量大、查询频繁的场景,提前过滤不存在的key)
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class BloomFilterDemo {
// 初始化布隆过滤器(预计数据量100万,误判率0.01)
private final BloomFilter<Long> bloomFilter = BloomFilter.create(
Funnels.longFunnel(),
1000000,
0.01
);
// 系统启动时,将所有用户ID存入布隆过滤器
public void initBloomFilter() {
List<Long> allUserId = userDao.selectAllUserId();
for (Long userId : allUserId) {
bloomFilter.put(userId);
}
}
// 查询前先通过布隆过滤器判断key是否存在
public User getUserById(Long userId) {
// 布隆过滤器判断不存在,直接返回null,不查缓存和数据库
if (!bloomFilter.mightContain(userId)) {
return null;
}
// 后续逻辑和Cache-Aside模式一致
String key = "user:" + userId;
String userJson = jedis.get(key);
if (userJson != null) {
return JSON.parseObject(userJson, User.class);
}
User user = userDao.selectById(userId);
if (user != null) {
jedis.setex(key,