redis篇面试

发布时间:2026/7/1 6:23:37

redis篇面试 一. redis的数据类型string类型使用场景缓存计数共享session。操作命令 1)set key value get key 2) setnx key value (key不存在时候设置) 3)incr 自增4)decr自减hash类型key是字符串value是map集合。键值对集合key 是字符串value 是一个 Map 集合比如说value {name: 沉默王二, age: 18}name 和 age 属于字段 field沉默王二 和 18 属于值 value。hset key filed valuehget key filed使用场景: 缓存对象缓存用户信息。list类型List 底层基于quicklist 实现结合了压缩列表和链表的优点。使用场景消息队列文章列表。命令LPUSH从左插入RPUSH从右插入。LPOP从左弹出RPOP从右弹出。set类型无序集合元素唯一不允许重复。sadd key value: 添加元素srem key value删除元素使用场景去重共同的好友。zset类型底层原理当数据量比较小的时候采用ziplist节省内存空间。当数据量比较大的时候采用skiplisthash结构。常用的操作命令添加成员127.0.0.1:6379 zadd rank 1000 player1(integer) 1127.0.0.1:6379 zadd rank 1100 player2(integer) 1127.0.0.1:6379 zadd rank 1200 player3(integer) 1给成员增加分数:127.0.0.1:6379 zincrby rank 50 player11050127.0.0.1:6379 zincrby rank 50 player21150127.0.0.1:6379 zincrby rank 50 player31250127.0.0.1:6379查询成员的排名:127.0.0.1:6379 zrank rank player1(integer) 0127.0.0.1:6379 zrank rank player2(integer) 1127.0.0.1:6379 zrevrank rank player1(integer) 2查询所有成员的信息127.0.0.1:6379 zrang rank 0 2 withsscores127.0.0.1:6379127.0.0.1:6379 zrange rank 0 2 withscores1) player12) 10503) player24) 11505) player36) 1250127.0.0.1:6379127.0.0.1:6379 zrevrange rank 0 2 withscores1) player32) 12503) player24) 11505) player16) 1050127.0.0.1:6379127.0.0.1:6379 zscore range player4100实现排行榜的思路1. 用户完成任务或得分通过incrby增加分数。2. 定期检查用户范围排行信息通过zrange/zrevrange检查排行信息3. 检查用户的个人排行信息通过zrank/zrevrank检查用户的个人排行信息4. 检查用户的个人分数通过zscore检查用户的个人分数zscore range player4zset提供了查排名查范围查分数改分数等特性。是实现排行榜积分榜的重要数据结构。1. Bitmaps位图不是新增的数据类型而是对String类型的位操作的封装。特点通过一个二进制位来表示元素的状态(0/1),极大的节省了内存的资源。setbit key offset valuegetbit key offset应用场景1. 用户签到系统每天一个位记录用户是否签到。2. 活跃用户统计例如按天记录用户登录状态2. Geospatial地理空间底层实现sorted set 实现zset)解决问题存储经度信息计算距离查询附近的人按距离排序常用命令GEOADD key longitude latitude member添加地理位置。GEOPOS key member获取某成员的经纬度。GEODIST key member1 member2 [unit]计算两点距离。3. HyperLogLog用于大规模的去重计算4. Stream消息流特点类似消息队列支持消费组支持 ACK支持持久化二. redis为啥这么快总的来说有四点原因。1. 基于内存存储 2. 单线程操作 3. 底层采用I/O多路复用基础 4. 高效的数据结构三. redis的多路复用技术select: 多路复用系统调用默认是1024。poll: 无固定上限受资源限制epoll总的来说epoll主要功能实现是通过linux系统内核提供的三个函数实现的分别是1. epoll_create 2. epoll_ctl 3. epoll_wait具体工作流程1. redis启动的时候首先会分配一块内存创建一个selector收集器同时会创建一个accept事件接受客户端的请求。2. 当客户端需要连接redis的时候首先由selector事件收集器感知到有accept事件发生然后通过redis服务端与客户端建立连接。3. 客户端发数据的时候会将I/O事件也注册到selector收集器。如果有多个客户端进行连接会注册多个I/O事件。4. selector收集器会返回一个event数组给redis进行处理5. 客户端与redis建立连接以后没有发数据selector收集器就会进入阻塞的状态等待I/0事件发生一旦有I/O事件发生就会返回一个event数组给redis进行处理。epoll_create创建selector收集器epoll_ctl 注册事件到selector收集器epoll_wait: 对注册的事件进行监听四 .redis是单线程还是多线程的命令操作是单线程的保证了操作的原子性和高效性。但是I/O操作采用的是多线程来优化性能。多线程与客户端进行网络通信单线程处理命令操作。redis是长连接还是短连接的Redis 是长连接模型连接建立后持续使用结合连接池使用可以提升高并发下的性能表现。短连接每次执行命令需要建立新的连接执行完以后需要断开连接。频繁的进行TCP的三次握手会造成延迟和资源的浪费不适合高并发的场景。五. redis的RDB和AOFRDB是redis提供的一种持久化方式用于定期将内存中的数据以快照的形式保存到磁盘的二进制文件中去dump.rdb实现持久化。可以通过save和besave命令实现。save: 阻塞式保存主线程执行。besave异步形式后台执行主线程调用fork()函数创建一个子线程进行保存快照。优点1. 适合冷备份 2. 数据恢复周期短 3. 对性能影响比较小 4. 文件体积小缺点1. 定期对数据备份可能丢失几分钟内的数据 2. fork开销比较大AOF是redis的一种持久化方式他通过将每一条写命令以日志的形式追加到AOF文件中从而实现持久化最大限度的避免了数据的丢失。优点1. 数据更安全 2. 增量持久化 3. 可读性比较好缺点1.恢复时间比较长 2. 性能影响比较大RDB 和 AOF 如何选择对数据精确度要求比较高采用AOF对性能要求比较高采用RDB。生成环境两种方式都开启使用RDB来进行快照备份使用AOF在出现异常的时候进行全量数据恢复。Redis 默认开启的是 RDB持久化AOF是默认关闭的。redis的数据恢复当 Redis 中的数据丢失时可以从 RDB 或者 AOF 中恢复数据。可以将 RDB 文件或者 AOF 文件复制到 Redis 的数据目录下然后重启 Redis 服务Redis 会自动加载数据文件并恢复数据。Redis启动时加载数据的流程AOF 开启且存在 AOF 文件时优先加载 AOF 文件。AOF 关闭或者 AOF 文件不存在时加载 RDB 文件RDB 和 AOF 如何选择六. redis主从复制原理总的来说redis的主从复制模式就是部署多台redis节点其中只有一个是主节点其它的都是从节点。增删改由主节点完成其它的slave节点只能进行读操作。具体流程 1. 首先由slave节点发起sync命令到master2. 然后master节点执行bgsave命令生成RDB文件3. master节点把slave上面的写命令记录到缓存4. besave命令执行完成以后将生成的RDB文件发送到slave上面执行5. master节点将缓存的内容发送到salve上面执行可能出现的问题1. 主从复制异步的主出现异常可能导致数据丢失。2. 写操作在主节点从节点只能读操作无法分担主节点的压力。3. 可能会出现脑裂问题由于网络分区和节点故障可能导致主从之间无法通信出现多个主节点redis的哨兵模式哨兵主要监控主从架构中的实例并在主出现异常以后进行故障的转移。七. 什么是redis的缓存击穿缓存穿透缓存雪崩。缓存击穿某个热点key在某个时刻失效了这时大量的请求落在了数据库上面给数据库造成了很大的压力。解决方法 1. 设置热点数据永远不过期2. 设置互斥锁分布式锁setnx3. 采用多级缓存springCaffeineredis用户请求↓[一级缓存]本地内存缓存如 Caffeine / Guava / Map↓未命中[二级缓存]Redis 缓存↓未命中[数据库]MySQL、MongoDB 等核心思想当发现缓存未命中时只有一个线程或请求去加载数据并更新缓存其余线程等待。实现方法请求发现 key 不存在先SETNX设置一个互斥锁如lock:key。加锁成功说明我是第一个去加载的线程从数据库查数据并回填缓存。加锁失败说明其他线程正在加载等待一段时间后重试或失败返回。// Java 示例伪代码if (redis.get(key) null){if (redis.setnx(lock: key, 1)){ // 查库、写缓存 redis.set(key, db.getData()); redis.del(lock: key); }else{ // 等待或快速失败 Thread.sleep(50); retry(); }}缓存穿透查询了缓存中不存在的数据由于缓存没有命中每次查询都从数据库中进行查询。给数据库造成了压力。出现原因 1. 代码的问题 2. 黑客的攻击解决方法 1.缓存空值,并设置过短的过期时间。2.布隆过滤器原理先通过布隆过滤器缓存所有可能存在的key在进行查询的时候通过布隆过滤器查询该key是否存在如果存在才到库里面进行查询如果不存在直接返回。BloomFilterString bloomFilter new BloomFilter(expectedInsertions, fpp); // 期望插入量和误判率bloomFilter.put(valid_key_1);bloomFilter.put(valid_key_2); // 判断请求的键是否存在于布隆过滤器中if (!bloomFilter.mightContain(requestedKey)){ // 如果布隆过滤器认为该键不存在则直接返回空 return null;}else { // 继续正常的缓存查询和数据库查询流程}缓存雪崩1. 大量的热点数据在某一时刻突然过期或者缓存服务器突然宕机了导致大量请求落在数据库上面给数据库造成很大的压力甚至宕机。解决方法1. 缓存的过期时间离散化 2. 集群部署避免单点故障3. 采用多级缓存 Guava CacheCaffeine redis出现异常切换到本地缓存4. 限流和降级通过设置合理的系统限流策略如令牌桶或漏斗算法来控制访问流量防止在缓存失效时数据库被打垮。此外系统可以实现降级策略在缓存雪崩或系统压力过大时暂时关闭一些非核心服务确保核心服务的正常运行。八. redis key的过期回收策略有哪些1. 惰性删除如果访问每个key发现其过期了就对其进行删除。如果不访问就一直不删除。缺点:可能会出现已经过期长时间没有被删除的key。2. 定期删除每隔一段时间就随机检查一批如果过期就进行删除。这种方法可以保证过期的key可以及时的被删除但是也会造成cpu的资源的浪费。1. 每隔100ms定时启动任务随机检查20个设置了过期时间的key2. 如果过期就进行删除3. 如果过期的key超过25%就再次进行扫描但是扫描的次数不能超过16%3. 定时删除策略在设置redis key的时候给它设置过期时间到期对其进行删除。redis的内存淘汰机制当redis的内存不足的时候不在进行插入数据而是触发内存淘汰机制根据策略删除一些数据。1. 删除快过期的 2. 删除最久未使用的 3. 删除最不常用的九. redis如何实现异步队列第一list结构LPUSH生成消息 RPOP消费消息第二基于发布与订阅pub/sub发布/订阅模式可以 1N 的消息发布/订阅。发布者将消息发布到指定的频道频道channel订阅相应频道的客户端都能收到消息。十. redis如何实现延迟队列核心思想1. 每一个任务是集合的一个成员2. 任务的执行时间是score3. 消费者定期拉取已过期的任务进行执行实现步骤1.生产者先添加任务ZADD key score value:ZADD delay_queue 执行时间戳 任务内容2. 消费者定时拉取任务每秒轮询一次可用定时任务ZRANGEBYSCORE delay_queue -inf 当前时间戳 LIMIT 0 1取出 score 小于当前时间的任务即已经到期的任务3. 任务完成以后并删除ZREM delay_queue 任务内容public void pollDelayQueue() {long now System.currentTimeMillis() / 1000;SetString tasks jedis.zrangeByScore(delay_queue, 0, now, 0, 1);for (String task : tasks) {if (jedis.zrem(delay_queue, task) 0) {// 执行任务逻辑process(task);}}}注意为了保证任务不会被多个消费者重复消费必须在 执行前先 ZREM 删除或使用 Lua 脚本保证原子性。十一. redis如何实现分布式锁redis实现加锁: 采用set key value nx px :SET lock_key unique_id NX PX 30000NX仅当 key 不存在时才设置加锁PX 30000设置过期时间防止死锁unique_id唯一标识确保只有加锁者能释放锁释放锁:为了避免误删他人锁释放锁时必须校验 value 是否一致常用 Lua 脚本保证原子性lua脚本if redis.call(get, KEYS[1]) ARGV[1]then return redis.call(del, KEYS[1])else return 0 end使用eval命令执行确保“检查删除”是原子操作。缺点1. 锁过期未执行完任务2. 锁的误删3. 单点故障采用redission来进行实现通过看门狗线程进行锁的续约。采用Redisson来实现分布式锁如果没有对锁设置超时时间redisson会自动对锁进行续约。如果设置了不会进行续约。整体流程1. 线程A获取锁以后会执行对应业务逻辑并开启看门狗线程。2. 看门狗线程每隔10s检查当前线程是否还持有锁如果持有锁将TTL时间设置为30s实现自动续约。3. 如果线程不在或者宕机不在进行续约锁到到期释放。4. 在线程A持有锁的过程中如何线程B也来尝试获取锁。发现锁已经被线程A持久会通过while循环的方式重试的获取锁。Redission锁实现的代码流程1. 先在pom.xml文件中引入相关的依赖dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactIdversion3.23.5/version !-- 替换成最新版本 --/dependency2. 客户端的初始化创建redission对象Config config new Config();config.useSingleServer().setAddress(redis://127.0.0.1:6379).setDatabase(0);RedissonClient redisson Redisson.create(config);3. 获取分布式锁并使用RLock lock redisson.getLock(my-lock);try {// 加锁默认 30 秒自动续期看门狗机制lock.lock();// 如果希望指定过期时间无续期机制// lock.lock(10, TimeUnit.SECONDS);System.out.println(执行业务逻辑...);Thread.sleep(5000); // 模拟业务耗时} catch (InterruptedException e) {e.printStackTrace();} finally {// 解锁Redisson 会检查当前线程是否持有锁防止误删lock.unlock();}步骤1. 引入redission相关的依赖2. 创建redission对象通过trylock尝试获取锁3. 获取锁成功以后进行加锁try里面执行对应的逻辑。finally里面释放锁。十二. 知识点总结什么是跳表跳表Skip List是一种支持快速查找、插入和删除操作的有序链表结构它通过维护每个节点有多个指向其它节点的指针来实现快速查找的功能。什么是压缩列表压缩列表是 Redis为了节约内存而使用的一种数据结构由一系列特殊编码的连续内存块组成的顺序型数据结构。什么是quicklist: QuickList 是 Redis 中用于实现列表list结构的一种数据结构它是 ziplist 的双向链表组合兼具压缩内存和快速访问的优点。十三. redis如何防止超卖redis进行IO接收命令是多线程进行实际命令操作是单线程。方式一通过Redis Lua解决将对商品的判断以及减少库存封装成原子性操作。local stock tonumber(redis.call(GET, KEYS[1]))if stock 0 thenreturn -1endredis.call(DECR, KEYS[1])return stock - 1关键原因1Redis是单线程模型。Redis 的核心执行模型是同一时间只会有一个命令在执行。Redis 对 Lua 的保证在 Lua 脚本执行期间Redis 不会执行任何其他命令local stock redis.call(GET, KEYS[1])-- 这里不会被打断redis.call(DECR, KEYS[1])-- 这里也不会被打断GET stock → 1stock 0DECR stock → 0return 02. 通过redis的单线程模型DECR / INCR原子性if redis.call(GET, KEYS[1]) 0 thenreturn redis.call(DECR, KEYS[1])elsereturn -1end3. Redis 扣库存 MQ 异步落库推荐请求↓Redis Lua 原子扣库存↓成功 → 发送 MQ↓消费者异步更新 MySQL优点Redis 扛高并发MySQL 异步写削峰不超卖性能极高如何保证最终一致性MQ 失败-重试MySQL 扣库存用乐观锁update product set stock stock -1where id ? and stock 0update是否是乐观锁UPDATE ... WHERE stock 0本质上是「乐观并发控制思想」但它不是“标准意义上的乐观锁version乐观锁”。“这是基于条件更新的乐观并发控制属于无锁乐观方案的一种。”UPDATE productSET stock stock - 1WHERE id ? AND stock 0;标准乐观锁教科书版:update prodcutset stock stock-1,version version1where id ? and version ?;特点有 version / timestamp明确检测“是否被别人修改过”冲突-update 行数 0使用悲观锁的顺序SELECT stock FROM product WHERE id 1 FOR UPDATE;流程加行锁判断库存扣库存提交事务释放锁十四. redis的内存淘汰机制1. 定义Redis在内存达到maxmemory限制时会根据配置的内存淘汰策略来删除key。常见的策略有LRULFUTTL。LRU 最近最常不使用的Key。LFU 使用频率最少的key。TTL 离过期时间最近的key2. 三种实现方式1. 默认值不进行淘汰。防止数据丢失。内存满了❌ 写入失败✅ 读正常2. 对设置过期时间的key进行淘汰volatile-* 只动“有 TTL 的 key”| 策略 | 含义 || --------------- | -------- || volatile-lru | 最近最少使用 || volatile-lfu | 最不常使用 || volatile-ttl | 剩余过期时间最短 || volatile-random | 随机 |3. 对所有 key 进行淘汰allkeys-*不管有没有 TTL都可能被删策略 含义allkeys-lru 最近最少使用最常用allkeys-lfu 最不常使用新推荐allkeys-random 随机4. TTL 淘汰是怎么回事volatile-ttl优先淘汰离过期时间最近的 key常用于session、临时数据十五. 为什么需要哨兵模式和集群模式1. Redis的单机问题1. 单点故障2. 内存受限3. 并发能力有限所以需要高可用主挂了直接切换高容量数据分片高并发于是有两种方案哨兵模式解决高可用集群模式解决高可用 扩容什么是哨兵模式主从复制 自动故障转移2. 哨兵做什么1️.监控主从节点2️.主观下线SDOWN3️.客观下线ODOWN4️.选举新的 Master5️.通知客户端3. 故障转移流程?1️.Master 宕机2️.多个 Sentinel 投票确认3️.选举一个 Sentinel 负责故障转移4️.从 Slave 中选一个提升为 Master5️.其他 Slave 重新复制适用场景数据量不大只需要高可用不需要水平扩展4.Redis 集群模式Cluster1、本质分片高可用主挂了直接切换架构16384个slot数据分布在多个Master上每个Master有自己的Slave2、核心机制哈希槽机制0 - 16383 共 16384 个 slotCRC16(key) % 163843. 特点每个 Master 负责一部分 slot每个 Master 有 Slave自动故障转移去中心化无单点4. 优点支持水平扩容支持高可用去中心化读写能力强5. Redis 哨兵模式至少需要几个节点理论上1 Master1 Slave3 Sentinel实际上生产环境1 Master2 Slave3 Sentinel十六. 面试Redis功能主要有哪些1. 数据存储高并发场景提高并发访问的能力。2. 分布式锁3. 限流4. 防止超卖5. 双向队列6. Kafka会造成数据丢失吗?Kafka 在某些配置或异常场景下会丢数据但可以通过合理配置把丢数据风险降到极低。十七. redis池化的思想1. 为啥使用连接池?提高吞吐量控制并发量。redis每次和客户端建立连接都需要做一下三件事:1. TCP三次握手 2. 建立Socket 3. 认证(如果需要密码)通过连接池复用连接。如果每次操作都创建连接QPS 高-大量连接创建-性能下降因此需要 连接池复用连接流程变成从连接池获取连接↓执行Redis命令↓归还连接2. 使用方式1. 通过Jedis实现java代码层面JedisPool:JedisPoolConfig config new JedisPoolConfig();config.setMaxTotal(100);config.setMaxIdle(10);config.setMinIdle(5);JedisPool jedisPool new JedisPool(config, 127.0.0.1, 6379);try (Jedis jedis jedisPool.getResource()) {jedis.set(name, tom);}JedisPool Redis连接池2. LettuceSpringBoot 默认使用 Lettuce。特点基于 Netty支持异步线程安全spring:redis:host: localhostport: 6379lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-wait: -1| 参数 | 说明 || ---------- | ------ || max-active | 最大连接数 || max-idle | 最大空闲连接 || min-idle | 最小空闲连接 || max-wait | 最大等待时间 |Redis 连接池的核心作用1. 减少创建的开销2. 提高性能复用连接提高吞吐量3. 控制连接数

相关新闻