拓冰建站拓冰建站
首页 / 资讯中心 / 正文

五、Redis

Redis 为什么快题目 7Redis 为什么快一、纯内存特点数据存放在内存优点读写速度极快二、单线程优点无锁竞争无线程切换CPU 开销低Redis 瓶颈内存网络而不是 CPU。三、I/O 多路复用使用epoll实现单线程监听大量 socket四、Redis 6.0 补充网络 IO 多线程命令执行仍单线程Redis 数据类型Redis 常用数据类型类型用途String缓存、计数器Hash存对象List队列Set去重ZSet排行榜扩展类型BitmapHyperLogLogGeoStreamRedis 是否线程安全一、服务端Redis 服务端线程安全原因单线程执行命令二、客户端客户端是否线程安全Jedis否Lettuce是RedisTemplate是Redis 缓存三大问题题目 6Redis 缓存三大问题一、缓存穿透问题查询不存在的数据解决布隆过滤器缓存空对象二、缓存击穿问题热点 key 失效解决互斥锁逻辑过期三、缓存雪崩问题大量 key 同时过期解决过期时间加随机值Redis 集群布隆过滤器布隆过滤器是什么布隆过滤器概率型数据结构用于判断元素是否存在核心特点一定不存在如果返回不存在那么一定不存在可能存在如果返回存在那么可能不存在即有误判底层结构组成结构作用位数组存储标记位多个哈希函数计算位置添加元素步骤用 k 个哈希函数计算位置对应 bit 位设为 1查询元素步骤同样计算 k 个位置判断是否全部为 1查询结果结果含义有 0一定不存在全是 1可能存在优点空间占用极小查询速度快例如1 亿数据 ≈ 100 多 MB缺点缺点说明有误判可能误判存在不支持删除普通布隆过滤器无法删除典型应用防止缓存穿透流程请求 → 布隆过滤器 → Redis → MySQL如果布隆过滤器判断不存在直接返回避免请求打到数据库Redis 实现使用RedisBloom模块。常见命令添加BF.ADD查询BF.EXISTSRedis 集群故障切换Redis 主从 哨兵一、故障检测流程PING 检测 ↓ 主观下线 ↓ 客观下线二、Leader 选举使用RaftLeader 哨兵负责选择新主节点更新配置三、部署要求至少 3 个哨兵Redis Cluster一、节点通信使用Gossip 协议二、故障切换流程PFAIL → FAIL ↓ 从节点发起选举 ↓ 获得多数票 ↓ 升级为新主三、特点内置高可用无需 SentinelRedis 数据丢失一、异步复制丢失问题主节点未同步成功就宕机导致数据丢失解决方案min-replicas-to-write1作用至少一个从节点确认后才返回成功二、脑裂问题问题网络分区导致双主后果恢复后旧主数据被丢弃解决方案min-replicas-max-lag10作用从节点延迟过大时拒绝写入三、强一致方案WAIT作用强制同步复制缺点性能下降Redis Cluster 原理一、哈希槽总数16384计算方式CRC16(key) % 16384二、作用数据分片快速定位节点三、扩缩容优势特点只迁移部分槽不需要全量迁移四、客户端重定向类型作用MOVED永久重定向ASK临时重定向五、Cluster 优势水平扩展多主写入高并发六、Cluster 缺点不支持跨节点事务不支持跨节点批量操作解决Hash Tag哈希槽原理Redis 哈希槽一、核心概念Redis Cluster共 16384 个槽二、槽位计算CRC16(key) % 16384三、三大优势1自动分片key 自动分布到不同节点。2扩缩容方便只迁移部分槽数据。3客户端直连客户端缓存槽位映射直接访问目标节点四、为什么是 16384原因bitmap 只需约 2KB兼顾网络开销和灵活性Redis 主从问题Redis主从复制是异步的问题主节点宕机时数据可能未同步结果发票号重复方案一雪花算法雪花算法特点本地生成不依赖 Redis高性能无单点故障核心要求保证workerId 不重复方案二Redis MySQL 兜底流程Redis INCR 发号Redis 故障时降级 MySQL兜底措施数据库唯一索引重复重新生成方案三Redisson 锁思路加分布式锁再执行 INCR问题仍无法100% 防止主从切换丢数据推荐方案主方案雪花算法兜底数据库唯一索引Redis 分布式锁实现可重入锁的层次回答层次内容基础方案ThreadLocal 存本地计数器 Redis SET NX但状态可能不一致标准方案Redis Hash Lua 脚本用线程 ID 做 fieldvalue 存重入次数工业实践生产直接用 Redisson支持自动续期、公平锁、红锁等进阶思考考虑可重入锁的性能开销和复杂性明确适用场景Redis 分布式锁高频十问1Redis 分布式锁怎么实现加锁SET lock_key unique_id NX EX 30参数说明NX互斥只有不存在才设置EX过期时间防止宕机死锁unique_id标识锁归属防止误删解锁ifredis.call(get,KEYS[1])ARGV[1]thenreturnredis.call(del,KEYS[1])elsereturn0end2为什么不能用 SETNX EXPIRE 分开写因为两条命令不是原子操作若执行完 SETNX 服务宕机EXPIRE 没执行锁会永久存在导致死锁必须用SET key value NX EX一条原子命令完成。3为什么解锁要用 Lua 脚本因为get 判断 del 删除是两步操作不原子。极端场景判断后锁过期别人重新加锁当前线程误删别人锁Lua 保证判断删除原子执行。4为什么要存唯一 IDUUID/requestId用于标识锁持有者解锁时校验是不是自己加的锁防止删别人的锁5什么是看门狗WatchDogRedisson 内置后台续期线程。机制锁默认 30s 过期每 10s 检查一次业务未执行完自动续期 30s解决业务执行时间过长锁提前过期问题。6有看门狗还需要 Lua 解锁吗必须需要。原因看门狗只保证锁不过期不能保证判断 删除原子性GC、线程延迟等情况下仍可能误删。Lua 是安全底线。7什么是分布式锁可重入Redis 怎么实现同一个客户端/线程可多次加同一把锁。Redis 实现key 锁名 field 客户端ID 线程ID value 重入次数加锁是自己 → count 1不是自己 → 阻塞解锁count -1为 0 才真正删除锁8Redis 主从切换会有什么问题问题场景主节点加锁成功还没同步到从节点主节点宕机从节点升级为主结果锁丢失其他线程可再次加锁出现并发安全问题9主从锁丢失怎么解决方案 1Redlock 红锁多数节点加锁成功才算成功。缺点复杂性能差实践争议较大方案 2使用强一致组件例如ZooKeeperEtcd方案 3业务兜底例如幂等版本号唯一索引防止重复执行。10实现可靠 Redis 分布式锁注意事项加锁必须使用SET NX EX必须存唯一标识解锁必须 Lua 原子删除必须设置过期时间长业务使用看门狗续期finally 中保证释放锁高可用场景考虑主从锁丢失问题MySQL 和 Redis 一致性题目 8MySQL 和 Redis 一致性一、核心方案旁路缓存Cache Aside二、读流程查 Redis ↓ 命中直接返回 ↓ 未命中查 MySQL ↓ 写入 Redis三、写流程更新 MySQL ↓ 删除 Redis四、为什么删除缓存原因幂等避免并发覆盖避免双写不一致五、删除失败兜底方案MQ 异步重试Canal 监听 binlog 删除缓存多级缓存题目 7多级缓存一、架构本地缓存 → Redis → MySQL二、流程先查 Caffeine ↓ 未命中查 Redis ↓ 未命中查 MySQL三、特点优点极致性能缺点一致性复杂维护成本高Redis String 和 Zset 底层实现 - 面试高频题一、String 底层5个最常见问题Q1Redis String 底层用什么实现和 C 字符串有什么区别答案用SDSSimple Dynamic String简单动态字符串。对比项C 字符串SDS获取长度O(N) 遍历O(1) 读 len缓冲区安全不安全可能溢出安全自动扩容二进制安全不安全遇\0停止安全用 len 判断修改性能每次重分配内存预分配减少重分配Q2SDS 的扩容策略是什么答案if (新长度 1MB) {新容量 新长度 * 2; // 翻倍} else {新容量 新长度 1MB; // 线性增长}原因翻倍重分配次数从 O(N) 降到 O(log N)1MB 上限避免大字符串翻倍造成巨大浪费Q3String 的三种编码方式及使用条件答案编码条件内存布局int整数值可用 long 表示值直接存 ptr无额外分配embstr长度 ≤ 44 字节redisObject 和 SDS 连续分配1次 mallocraw长度 44 字节redisObject 和 SDS 分开分配2次 malloc44字节来源redisObject(16B) SDS头(3B) ‘\0’(1B) 20B对齐到64字节缓存行后剩44B。Q4什么是二进制安全SDS 如何实现答案二进制安全能存储任意数据图片、序列化对象、含 ‘\0’ 的数据。实现方式不依赖 ‘\0’ 判断结尾用 len 字段记录长度。sds s sdsnewlen(“ABC\0DEF”, 7); // 能完整存7个字节Q5SDS 的 buf 为什么还要以 ‘\0’ 结尾答案为了兼容 C 标准库函数如 printf、strcmp可直接使用无需复制。二、Zset 底层5个最常见问题Q1Zset 底层有几种实现什么时候切换答案编码条件特点ziplist元素数 ≤ 128 且 member ≤ 64 字节内存紧凑查找 O(N)skiplistdict超过任一阈值查找 O(log N)功能完整注意切换不可逆。Q2为什么 Zset 需要同时维护 dict 和 skiplist答案结构用途命令dictmember → score O(1) 查找ZSCOREskiplist按 score 排序范围操作ZRANGE、ZRANK关键两者通过指针共享member 和 score无数据冗余。Q3什么是跳表为什么用它而不是红黑树答案跳表多层有序链表上层是下层的快速通道查找 O(log N)。对比项跳表红黑树实现复杂度简单~500行复杂~2000行范围查询天然支持需额外实现并发友好相对容易困难Q4跳表的层数如何确定答案随机生成25% 概率升层。int level 1;while (random() 0.25 level 32) level;最大32 层2^32 ≈ 42亿元素。Q5ziplist 的连锁更新是什么答案插入 ≥254 字节的 entry 时后续 entry 的 prevlen 从 1B 扩展到 5B导致自身长度增加影响下一个形成连锁反应。实际影响触发条件苛刻生产环境极少遇到是 Redis 的权衡设计。三、速记卡片StringSDSO(1)长度、自动扩容、二进制安全、预分配编码int、embstr(≤44)、raw(44)扩容1MB翻倍1MB加1MBZset小数据ziplist≤128且≤64B→ 省内存大数据skiplistdict → 保性能跳表25%概率升层最大32层好的给你一个精简版30秒内说完适合面试时快速清晰地表态Redis的过期key清理采用惰性删除 定期删除两种策略。惰性删除访问key时检查是否过期过期就删。优点是省CPU缺点是可能积压过期key。定期删除每秒执行10次定时任务随机抽查一批key删除其中的过期key防止内存堆积。两者结合平衡CPU和内存。另外内存达到上限时会触发内存淘汰策略作为兜底但那是另一回事。好的帮你把内存淘汰策略的核心逻辑浓缩成一句话速记版内存淘汰策略4种核心算法算法全称淘汰逻辑LRULeast Recently Used淘汰最近最少使用的看时间LFULeast Frequently Used淘汰最不常使用的看频率Random随机随机淘汰TTLTime To Live淘汰剩余存活时间最短的只针对有过期时间的keyRedis 主从同步的核心流程用三句话概括就是第一次全量拷贝平时实时追随断线短时间内补差价。1. 第一次建立连接全量同步抓快照 补增量发 RDB 文件主节点Master在后台生成一份当前内存的RDB 镜像快照发给从节点Replica。从节点收到后清空自身加载这个 RDB。补同步期间的新数据在生成和传输 RDB 的过程中主节点新收到的写命令会存入内存缓冲区等 RDB 加载完后主节点把缓冲区里的命令发给从节点执行。2. 正常运行中长连接增量传播主从节点之间维持一个 TCP 长连接。主节点每收到一条写命令就会异步同步给从节点从节点照着执行保持数据实时一致。3. 网络短暂中断增量同步按进度断点续传主节点内部维护了一个固定大小的环形缓冲区repl_backlog_buffer一直记录着最新的写命令。网络恢复后从节点带上自己中断时的数据进度Offset向主节点发起重连进度还在缓冲区里主节点直接把缺失的那部分命令发过去断点续传。断网时间太长进度已被覆盖不得不重新触发一次完整的全量同步。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门