摘要:Redis作为高性能的内存数据库,广泛应用于缓存、分布式锁、消息队列等场景,但在高并发场景下,容易出现缓存穿透、缓存击穿、缓存雪崩等问题,同时Redis集群部署的高可用性也是企业级应用的核心需求。本文结合电商库存缓存、分布式秒杀等实战场景,详细讲解Redis的高级用法、缓存问题解决方案、集群部署与运维技巧,附完整源码,适合后端开发者与运维工程师学习。
一、前言:Redis的核心应用场景与常见问题
Redis凭借其高性能、高可用、支持多种数据结构(String、Hash、List、Set、Sorted Set)的优势,成为企业级系统的核心中间件,主要应用场景包括:缓存热点数据、分布式锁、消息队列、计数器、排行榜等。
但在高并发场景下,Redis容易出现一系列问题:缓存穿透(查询不存在的数据,导致请求直达数据库)、缓存击穿(热点key过期,大量请求直达数据库)、缓存雪崩(大量key同时过期,导致数据库压力剧增);同时,单节点Redis存在单点故障风险,需通过集群部署保证高可用性。本文将逐一解决这些问题,实现Redis的企业级实战应用。
二、Redis核心高级用法(实战场景)
2.1 分布式锁(Redis实现)
分布式锁是分布式系统中解决并发问题的核心方案,用于保证多个服务节点对共享资源的互斥访问(如电商秒杀、库存扣减)。Redis实现分布式锁的核心原理:使用SET NX EX命令,保证锁的原子性,同时设置过期时间,避免死锁。
java
// Redis分布式锁工具类(Java实现)
@Component
public class RedisLockUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 锁的前缀
private static final String LOCK_PREFIX = "redis:lock:";
// 锁的默认过期时间(30秒)
private static final long DEFAULT_EXPIRE = 30000L;
// 获取锁
public boolean tryLock(String key, String value, long expire) {
// SET NX EX:不存在则设置,同时设置过期时间,原子操作
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
LOCK_PREFIX + key,
value,
expire,
TimeUnit.MILLISECONDS
);
return Boolean.TRUE.equals(success);
}
// 释放锁(原子操作,避免误释放)
public boolean releaseLock(String key, String value) {
// 使用Lua脚本,保证查询和删除的原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(LOCK_PREFIX + key),
value
);
return result != null && result > 0;
}
// 重载方法,使用默认过期时间
public boolean tryLock(String key, String value) {
return tryLock(key, value, DEFAULT_EXPIRE);
}
}
2.2 缓存设计与缓存问题解决方案
结合电商库存缓存场景,设计合理的缓存策略,同时解决缓存穿透、缓存击穿、缓存雪崩问题。
java
// 电商库存缓存实战(解决缓存三大问题)
@Service
public class ProductStockService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ProductMapper productMapper;
@Autowired
private RedisLockUtil redisLockUtil;
// 缓存key前缀
private static final String STOCK_KEY_PREFIX = "product:stock:";
// 空值缓存过期时间(10分钟)
private static final long NULL_CACHE_EXPIRE = 600000L;
// 热点key过期时间(1小时,加随机值避免雪崩)
private static final long HOT_KEY_EXPIRE = 3600000L;
// 查询商品库存(缓存优先)
public Integer getProductStock(Long productId) {
String key = STOCK_KEY_PREFIX + productId;
// 1. 查询缓存
String stockStr = stringRedisTemplate.opsForValue().get(key);
if (stockStr != null) {
// 空值缓存,返回0
if ("0".equals(stockStr)) {
return 0;
}
// 正常缓存,返回库存
return Integer.parseInt(stockStr);
}
// 2. 缓存未命中,查询数据库(加分布式锁,避免缓存穿透)
String lockKey = "stock:lock:" + productId;
String lockValue = UUID.randomUUID().toString();
try {
// 获取锁,避免高并发下大量请求直达数据库
boolean locked = redisLockUtil.tryLock(lockKey, lockValue);
if (!locked) {
// 未获取到锁,重试
Thread.sleep(50);
return getProductStock(productId);
}
// 3. 查询数据库
Product product = productMapper.selectById(productId);
if (product == null) {
// 缓存空值,解决缓存穿透
stringRedisTemplate.opsForValue().set(key, "0", NULL_CACHE_EXPIRE, TimeUnit.MILLISECONDS);
return 0;
}
// 4. 存入缓存(热点key加随机过期时间,避免缓存雪崩)
long expire = HOT_KEY_EXPIRE + new Random().nextInt(10000);
stringRedisTemplate.opsForValue().set(key, String.valueOf(product.getStock()), expire, TimeUnit.MILLISECONDS);
return product.getStock();
} catch (InterruptedException e) {
throw new RuntimeException("查询库存失败");
} finally {
// 释放锁
redisLockUtil.releaseLock(lockKey, lockValue);
}
}
// 扣减库存(缓存与数据库一致性)
public boolean deductStock(Long productId, Integer num) {
String key = STOCK_KEY_PREFIX + productId;
// 1. 先扣减缓存(原子操作)
Long stock = stringRedisTemplate.opsForValue().decrement(key, num);
if (stock != null && stock >= 0) {
// 2. 缓存扣减成功,异步更新数据库(最终一致性)
CompletableFuture.runAsync(() -> {
productMapper.deductStock(productId, num);
});
return true;
}
// 3. 缓存扣减失败,回滚缓存
stringRedisTemplate.opsForValue().increment(key, num);
return false;
}
}
三、Redis集群部署(主从+哨兵+集群模式)
3.1 主从复制(解决单点故障)
Redis主从复制实现数据同步,主节点负责写入,从节点负责读取,当主节点故障时,从节点可切换为主节点,保证服务可用性。主从配置步骤(Redis 6.2):
text
# 1. 主节点配置(redis.conf)
bind 0.0.0.0
port 6379
daemonize yes
requirepass 123456 # 密码
logfile "/var/log/redis/redis-master.log"
# 2. 从节点配置(redis.conf)
bind 0.0.0.0
port 6380
daemonize yes
requirepass 123456
logfile "/var/log/redis/redis-slave.log"
# 配置主节点地址与密码
slaveof 192.168.1.100 6379
masterauth 123456
3.2 哨兵模式(自动故障转移)
哨兵模式用于监控主从节点,当主节点故障时,自动将从节点切换为主节点,无需人工干预。哨兵配置步骤:
text
# 哨兵配置(sentinel.conf)
daemonize yes
port 26379
logfile "/var/log/redis/sentinel.log"
# 监控主节点(名称、地址、投票数)
sentinel monitor mymaster 192.168.1.100 6379 2
# 主节点密码
sentinel auth-pass mymaster 123456
# 主节点故障判定时间(3秒)
sentinel down-after-milliseconds mymaster 3000
# 故障转移超时时间(10秒)
sentinel failover-timeout mymaster 10000
3.3 集群模式(解决容量与并发问题)
Redis集群模式将数据分片存储在多个节点,提升系统的容量与并发处理能力,适合大数据量、高并发场景。集群部署步骤(3主3从):
bash
# 1. 创建6个Redis节点配置(端口6379-6384)
for port in {6379..6384}; do
mkdir -p /etc/redis/cluster/$port
cp /etc/redis/redis.conf /etc/redis/cluster/$port/
sed -i "s/port 6379/port $port/" /etc/redis/cluster/$port/redis.conf
sed -i "s/daemonize no/daemonize yes/" /etc/redis/cluster/$port/redis.conf
sed -i "s/# cluster-enabled yes/cluster-enabled yes/" /etc/redis/cluster/$port/redis.conf
sed -i "s/# cluster-config-file nodes.conf/cluster-config-file nodes-$port.conf/" /etc/redis/cluster/$port/redis.conf
sed -i "s/# cluster-node-timeout 15000/cluster-node-timeout 15000/" /etc/redis/cluster/$port/redis.conf
done
# 2. 启动所有节点
for port in {6379..6384}; do
redis-server /etc/redis/cluster/$port/redis.conf
done
# 3. 创建集群
redis-cli --cluster create 192.168.1.100:6379 192.168.1.100:6380 192.168.1.100:6381 192.168.1.100:6382 192.168.1.100:6383 192.168.1.100:6384 --cluster-replicas 1 -a 123456
四、总结与延伸
本文结合实战场景,详细讲解了Redis的高级用法(分布式锁、缓存设计)、缓存问题解决方案(穿透、击穿、雪崩),以及Redis集群部署(主从、哨兵、集群),实现了Redis的企业级应用。
延伸学习:可深入研究Redis的持久化机制(RDB、AOF)、数据结构底层实现(如String的SDS、Hash的哈希表)、Redis Cluster的数据分片原理,以及Redis的高级特性(如布隆过滤器、HyperLogLog),进一步提升Redis的使用能力。