一、为什么Redis单线程还能这么快?
(震惊!)很多小伙伴第一次听说Redis是单线程模型时,脑子里肯定蹦出大大的问号——这性能不得炸成烟花?但现实是Redis的QPS轻轻松松突破10万+!!!这波反向操作到底是怎么实现的?
核心秘诀就藏在两个关键设计里:
- 纯内存操作:数据都放在内存里读写,没有磁盘I/O这个最大瓶颈(内存速度比磁盘快10万倍以上)
- I/O多路复用:通过epoll/kqueue等系统调用实现非阻塞I/O,一个线程就能处理大量并发连接
举个栗子🌰:就像银行柜台只开一个窗口,但用了智能叫号系统+自动业务办理机,处理速度反而比开十个传统窗口还快!
二、Redis的五种数据结构用对了吗?
(灵魂拷问)你以为知道五种数据结构名字就完事了?实际面试官想听的是底层实现和应用场景!
数据结构 | 底层实现 | 典型场景 | 坑点预警 |
---|---|---|---|
String | SDS动态字符串 | 计数器、分布式锁 | 大value导致内存碎片 |
Hash | ziplist+hashtable | 对象存储、购物车 | 字段太多时性能断崖式下跌 |
List | quicklist | 消息队列、最新消息 | lrange大范围操作会阻塞 |
Set | intset+hashtable | 共同好友、抽奖去重 | 大数据量时union性能陷阱 |
Zset | ziplist+skiplist | 排行榜、延迟队列 | zrangebyscore注意时间复杂度 |
(划重点)Hash类型在字段超过512个时会从ziplist转成hashtable,内存占用瞬间翻倍!这个配置项对应hash-max-ziplist-entries参数,调优必知!
三、Redis持久化到底怎么选?
遇到这个问题别慌,先画个对比表镇场子:
RDB快照:
- 优点:二进制紧凑存储,恢复速度快
- 痛点:可能丢失最后一次保存后的数据
- 生产环境配置建议:
save 900 1 # 15分钟有1个key变化就保存
save 300 10 # 5分钟有10个key变化就保存
save 60 10000 # 1分钟有10000个key变化就保存
AOF日志:
- 写后日志 vs 写前日志(MySQL的redo log是写前日志)
- AOF重写原理:fork子进程生成新AOF文件,期间增量数据写入缓冲区
- 性能黑洞:每次刷盘(appendfsync always)会让QPS暴跌到几千
(保命技巧)混合持久化了解下?Redis 4.0+支持AOF+RDB混合模式,重启时先加载RDB再重放增量AOF,恢复速度提升10倍不是梦!
四、缓存穿透、雪崩、击穿怎么破?
这三个缓存界的"死亡三连"必须用不同姿势破解:
- 缓存穿透(请求不存在的数据)
- 布隆过滤器拦截(1亿数据仅需12MB内存)
- 缓存空对象(记得设置短过期时间)
- 缓存雪崩(大量key同时过期)
- 随机过期时间:基础时间+随机偏移量
- 热点数据永不过期+异步更新
- 缓存击穿(热点key突然失效)
- 互斥锁重建:Redisson的分布式锁
- 逻辑过期时间:实际数据永不过期,程序判断是否要更新
(血泪教训)某电商大促时因为缓存雪崩导致DB被打挂,直接损失上千万!现在他们的Key过期时间都加了随机数后缀。
五、Redis集群方案如何选型?
三种主流方案对比:
- 主从复制
- 一主多从架构,从节点只读
- 同步方式:全量同步(首次) + 增量同步
- 致命缺陷:主节点宕机需要手动切换
- 哨兵模式
- 哨兵集群监控主节点状态
- 自动故障转移(平均20秒完成切换)
- 配置建议:至少3个哨兵节点,quorum设为2
- Cluster模式
- 数据分片(16384个slot)
- 节点间使用gossip协议通信
- 官方推荐方案,支持水平扩展
(避坑指南)跨slot操作会报错!比如mget多个key分布在不同的slot就会失败,这种情况要用hash tag强制分配相同slot。
六、Redis为什么变慢了?
性能问题排查三板斧:
- 慢查询分析:
# 设置慢查询阈值(单位微秒)
config set slowlog-log-slower-than 10000
# 保存最近1000条慢查询
config set slowlog-max-len 1000
# 查看慢日志
slowlog get 10
- 内存瓶颈排查:
- 大key扫描:redis-cli --bigkeys
- 内存碎片率:info memory查看mem_fragmentation_ratio
- 碎片整理:config set activedefrag yes
- 网络问题定位:
- 监控每秒流量:redis-cli --stat
- 连接数突增:client list查看异常连接
- 使用Redis的自身延迟检测:redis-cli --latency
(真实案例)某次线上事故发现Redis响应变慢,最后发现是有人误用了keys *命令,直接导致单线程阻塞!
七、Redis事务是鸡肋吗?
先看经典代码示例:
MULTI
SET stock:1001 50
DECR stock:1001
EXEC
Redis事务的三大特征:
- 批量执行(非原子性,某条失败不会回滚)
- 无隔离级别(不会看到中间状态)
- 不支持回滚(要开发者自己处理)
(使用场景)适合需要批量执行的场景,比如:
- 库存扣减
- 批量更新配置项
- 计数器组合操作
八、Lua脚本的正确打开方式
相比事务,Lua脚本才是真香警告!优势包括:
- 原子性执行
- 减少网络开销
- 复杂逻辑封装
经典限流脚本示例:
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
return 0
else
redis.call("INCR", key)
redis.call("EXPIRE", key, expire_time)
return 1
end
(性能警告)Lua脚本不要写太复杂的逻辑,否则会阻塞整个Redis实例!
九、Redis6.0的多线程真香吗?
Redis6.0开始支持多线程,但别激动——这个多线程仅用于处理网络I/O,命令执行还是单线程!
配置方法:
io-threads 4 # 建议设置为CPU核心数的3/4
io-threads-do-reads yes
实测数据:
- 4核机器开启多线程后,吞吐量提升2-3倍
- 但CPU占用率也会相应升高
(选型建议)如果业务场景以简单命令为主(如get/set),开启多线程收益明显;如果是复杂命令(lua脚本、事务),提升有限。
十、Redis在微服务中的正确姿势
最后来点高阶玩法!在Spring Cloud架构中:
- 二级缓存架构:
本地缓存(Caffeine) + Redis分布式缓存
@Cacheable(cacheNames = "users", key = "#userId",
cacheManager = "combinedCacheManager")
public User getUser(String userId) {
// 查询DB
}
- 分布式锁进阶:
- Redisson的看门狗机制(自动续期)
- 分段锁优化(ConcurrentHashMap思想)
- 热点数据发现:
- 使用Redis的LFU算法(allkeys-lfu)
- 客户端埋点统计key访问频率
(架构思考)别把Redis当数据库用!持久化方案再完善,也不能替代MySQL等持久化存储,数据安全红线不能碰!