问题
Redis的优势,解决了哪些问题?
答案
1. 核心优势
1.1 极高的性能
特点:
- 单线程模型:避免线程切换和锁竞争(Redis 6.0+ 网络IO多线程)
- 内存存储:数据全在内存,避免磁盘IO
- 高效数据结构:底层优化的C实现
- IO多路复用:epoll/kqueue处理并发连接
性能指标:
# 官方基准测试(单实例)
GET: ~100,000 OPS
SET: ~80,000 OPS
INCR: ~100,000 OPS
LPUSH: ~100,000 OPS
# 实际生产环境(优化后)
QPS可达10万+
对比关系型数据库:
MySQL查询:~1000 QPS(有索引)
Redis查询:~100,000 QPS
性能提升:100倍+
1.2 丰富的数据结构
五大基础类型:
- String:缓存、计数器、分布式锁
- List:消息队列、时间线
- Hash:对象存储、购物车
- Set:标签、去重、集合运算
- ZSet:排行榜、延时队列
高级数据结构:
- Bitmap:签到、在线状态
- HyperLogLog:UV统计
- Geo:地理位置、附近的人
- Stream:消息队列(5.0+)
- BloomFilter:去重过滤(通过模块)
优势:一个Redis顶多个数据库
// 传统方案:多个系统协作
MySQL: 存储用户信息
Memcached: 缓存
RabbitMQ: 消息队列
ElasticSearch: 排行榜
// Redis方案:一站式解决
Redis Hash: 用户信息
Redis String: 缓存
Redis List: 消息队列
Redis ZSet: 排行榜
1.3 持久化机制
RDB(快照):
- 定期全量备份
- 适合灾难恢复
- 恢复速度快
AOF(追加日志):
- 记录每条写命令
- 数据安全性高
- 支持自动重写
混合持久化(Redis 4.0+):
# RDB + AOF结合
# RDB作为基础快照
# AOF记录增量修改
解决问题:内存数据不丢失
1.4 高可用架构
主从复制:
Master(写) -> Slave1(读)
-> Slave2(读)
-> Slave3(读)
哨兵模式(Sentinel):
- 自动故障转移
- 监控主从状态
- 通知客户端切换
集群模式(Cluster):
- 数据分片(16384槽)
- 水平扩展
- 去中心化
解决问题:单点故障、容量瓶颈
1.5 原子性操作
单命令原子性:
INCR counter # 原子递增
GETSET key value # 原子读取并设置
Lua脚本:
-- 原子性检查并删除(分布式锁释放)
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
事务支持:
MULTI
SET key1 value1
SET key2 value2
EXEC # 原子性执行
解决问题:并发竞态条件
1.6 过期策略与内存管理
过期策略:
- 惰性删除:访问时检查是否过期
- 定期删除:定时扫描过期key
- 主动删除:内存不足时淘汰
内存淘汰策略:
maxmemory-policy allkeys-lru # 常用策略
# 可选策略:
noeviction: 不淘汰(返回错误)
allkeys-lru: LRU淘汰所有key
volatile-lru: LRU淘汰有过期时间的key
allkeys-random: 随机淘汰
volatile-ttl: 淘汰最快过期的key
解决问题:自动管理内存,防止溢出
2. 解决的核心问题
2.1 数据库性能瓶颈
问题场景:
// 热点数据频繁查询
SELECT * FROM user WHERE id = 1001; // 每秒10000次
// 数据库扛不住
Redis解决方案:
// 缓存策略
String user = redisTemplate.opsForValue().get("user:1001");
if (user == null) {
user = userMapper.selectById(1001);
redisTemplate.opsForValue().set("user:1001", user, 1, TimeUnit.HOURS);
}
return user;
效果:
- 数据库压力降低99%
- 响应时间从100ms降到1ms
2.2 缓存穿透
问题:大量请求不存在的数据,击穿缓存直达数据库
解决方案1:缓存空值
String user = redisTemplate.opsForValue().get("user:9999");
if (user == null) {
user = userMapper.selectById(9999);
if (user == null) {
// 缓存空值,防止穿透
redisTemplate.opsForValue().set("user:9999", "", 5, TimeUnit.MINUTES);
} else {
redisTemplate.opsForValue().set("user:9999", user, 1, TimeUnit.HOURS);
}
}
解决方案2:BloomFilter
// 启动时加载所有ID到布隆过滤器
BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 10000000);
// 查询前先判断
if (!bloomFilter.mightContain(userId)) {
return null; // 一定不存在
}
// 可能存在,继续查询
2.3 缓存击穿
问题:热点key过期瞬间,大量请求打到数据库
解决方案1:互斥锁
public String getUser(String userId) {
String key = "user:" + userId;
String user = redisTemplate.opsForValue().get(key);
if (user == null) {
String lockKey = "lock:" + key;
// 只有一个线程能获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (lock) {
try {
// 查询数据库
user = userMapper.selectById(userId);
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 等待后重试
Thread.sleep(100);
return getUser(userId);
}
}
return user;
}
解决方案2:永不过期
// 逻辑过期而非真实过期
class CacheData {
String data;
long expireTime;
}
// 异步刷新
if (cacheData.expireTime < System.currentTimeMillis()) {
// 返回旧数据
// 异步线程池刷新
executor.submit(() -> refreshCache(key));
}
2.4 缓存雪崩
问题:大量key同时过期,数据库瞬间压力巨大
解决方案1:过期时间加随机值
// 不要设置统一过期时间
int randomSeconds = ThreadLocalRandom.current().nextInt(300); // 0-5分钟
redisTemplate.opsForValue().set(key, value, 3600 + randomSeconds, TimeUnit.SECONDS);
解决方案2:高可用集群
# 哨兵模式,主节点宕机自动切换
# 或使用Redis Cluster集群
解决方案3:限流降级
// Sentinel限流
@SentinelResource(value = "getUser", blockHandler = "handleBlock")
public String getUser(String userId) {
// 查询逻辑
}
public String handleBlock(String userId, BlockException e) {
return "系统繁忙,请稍后再试";
}
2.5 分布式锁
问题:分布式环境下的资源竞争
Redis实现:
public class RedisDistributedLock {
public boolean tryLock(String lockKey, String requestId, int expireTime) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
public void unlock(String lockKey, String requestId) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), requestId);
}
}
解决问题:
- 库存扣减
- 防止重复下单
- 定时任务防重
2.6 分布式Session
问题:集群环境下Session不共享
Redis解决方案:
// Spring Session配置
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class SessionConfig {
// 自动将Session存储到Redis
}
// 使用方式不变
@GetMapping("/user")
public User getUser(HttpSession session) {
return (User) session.getAttribute("user");
}
优势:
- 多台服务器共享Session
- Session持久化,服务器重启不丢失
- 支持跨域Session
2.7 实时排行榜
问题:MySQL排序查询慢,且频繁更新
Redis ZSet实现:
// 更新分数
redisTemplate.opsForZSet().incrementScore("game:rank", "player:1001", 100);
// 获取Top 10(实时)
Set<ZSetOperations.TypedTuple<String>> top10 =
redisTemplate.opsForZSet().reverseRangeWithScores("game:rank", 0, 9);
// 查询个人排名
Long rank = redisTemplate.opsForZSet().reverseRank("game:rank", "player:1001");
性能对比:
-- MySQL方案
SELECT * FROM scores ORDER BY score DESC LIMIT 10;
-- 100万数据:~500ms
-- Redis方案
ZREVRANGE game:rank 0 9 WITHSCORES
-- 100万数据:~1ms
2.8 消息队列
问题:轻量级消息队列需求
Redis List实现:
// 生产者
redisTemplate.opsForList().leftPush("queue:tasks", task);
// 消费者(阻塞)
String task = redisTemplate.opsForList().rightPop("queue:tasks", 5, TimeUnit.SECONDS);
Redis Stream实现(5.0+):
// 支持消费组、ACK、持久化
redisTemplate.opsForStream().add("stream:tasks", record);
适用场景:
- 异步任务处理
- 日志收集
- 简单MQ场景(非核心业务)
3. 性能优化最佳实践
3.1 合理使用数据结构
- 小对象用压缩编码:控制Hash/ZSet元素数量
- 大文本拆分:避免大Key
- 选择合适类型:计数用String,去重用Set
3.2 避免阻塞操作
# 慎用的命令
KEYS * # 改用SCAN
FLUSHALL # 慎用
SMEMBERS # 大Set改用SSCAN
3.3 批量操作
// 使用Pipeline
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (String key : keys) {
connection.get(key.getBytes());
}
return null;
});
3.4 监控与调优
# 慢查询日志
SLOWLOG GET 10
# 内存分析
MEMORY DOCTOR
# 监控指标
INFO stats
4. 面试答题总结
标准回答模板:
Redis的核心优势包括:
- 高性能:单线程+内存存储+IO多路复用,QPS达10万+
- 丰富数据结构:String/List/Hash/Set/ZSet等,一站式解决多种场景
- 持久化:RDB+AOF保证数据不丢失
- 高可用:主从复制、哨兵、集群,支持故障转移
- 原子性:支持Lua脚本和事务
解决的核心问题:
- 性能:缓存热点数据,降低数据库压力99%
- 缓存三大问题:穿透(布隆过滤器)、击穿(互斥锁)、雪崩(随机过期)
- 分布式:分布式锁、Session共享
- 实时统计:排行榜、UV统计(HyperLogLog)
- 消息队列:List/Stream实现轻量级MQ
常见追问:
- 为什么Redis这么快? → 内存存储、单线程避免锁、高效数据结构、IO多路复用
- Redis如何保证高可用? → 主从复制、哨兵自动切换、集群分片
- Redis单线程为什么还能支持高并发? → IO多路复用、内存操作快、避免上下文切换