Redis面试题整理-四年经验

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、 缓存的类型

缓存的类型分为本地缓存、分布式缓存和多级缓存。

  1. 本地缓存

本地缓存是内存访问没有远程交互开销性能最好但是受限于单机容量一般缓存较小且无法扩展。

  1. 分布式缓存

分布式缓存一般都具有良好的水平扩展能力对较大数据量的场景也能应付自如。缺点就是需要进行远程请求性能不如本地缓存。

  1. 多级缓存

为了平衡这种情况实际业务中一般采用多级缓存本地缓存只保存访问频率最高的部分热点数据其他的热点数据放在分布式缓存中。

二、Redis数据类型

1. string

String 类型是 Redis 中最常使用的类型内部的实现是通过 SDSSimple Dynamic String 来存储的。SDS 类似于 Java 中的 ArrayList可以通过预分配冗余空间的方式来减少内存的频繁分配。

这是最简单的类型就是普通的 set 和 get做简单的 KV 缓存。

但是真实的开发环境中很多仔可能会把很多比较复杂的结构也统一转成String去存储使用比如有的仔他就喜欢把对象或者List转换为JSONString进行存储拿出来再反序列话啥的。

String的实际应用场景

  • 缓存功能String字符串是最常用的数据类型不仅仅是Redis各个语言都是最基本类型因此利用Redis作为缓存配合其它数据库作为存储层利用Redis支持高并发的特点可以大大加快系统的读写速度、以及降低后端数据库的压力。
  • 计数器许多系统都会使用Redis作为系统的实时计数器可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。
  • 共享用户Session用户重新刷新一次界面可能需要访问一下数据进行重新登录或者访问页面缓存Cookie但是可以利用Redis将用户的Session集中管理在这种模式只需要保证Redis的高可用每次用户Session的更新和获取都可以快速完成。大大提高效率。

2.Hash

这个是类似 Map 的一种结构这个一般就是可以将结构化的数据比如一个对象前提是这个对象没嵌套其他的对象给缓存在 Redis 里然后每次读写缓存的时候可以就操作 Hash 里的某个字段。

但是这个的场景其实还是多少单一了一些因为现在很多对象都是比较复杂的比如你的商品对象可能里面就包含了很多属性其中也有对象。我自己使用的场景用得不是那么多。

3.Lish

List 是有序列表这个还是可以玩儿出很多花样的。

比如可以通过 List 存储一些列表型的数据结构类似粉丝列表、文章的评论列表之类的东西。

List本身就是我们在开发过程中比较常用的数据结构了热点数据更不用说了。

应用场景

  • 消息队列Redis的链表结构可以轻松实现阻塞队列可以使用左进右出的命令组成来完成队列的设计。比如数据的生产者可以通过Lpush命令从左边插入数据多个数据消费者可以使用BRpop命令阻塞的“抢”列表尾部的数据。

  • 比如可以搞个简单的消息队列从 List 头怼进去从 List 屁股那里弄出来。

  • 比如可以通过 lrange 命令读取某个闭区间内的元素可以基于 List 实现分页查询这个是很棒的一个功能基于 Redis 实现简单的高性能分页可以做类似微博那种下拉不断分页的东西性能高就一页一页走。

4.Set

Set 是无序集合会自动去重的那种。

直接基于 Set 将系统里需要去重的数据扔进去自动就给去重了如果你需要对一些数据进行快速的全局去重你当然也可以基于 JVM 内存里的 HashSet 进行去重但是如果你的某个系统部署在多台机器上呢得基于Redis进行全局的 Set 去重。

可以基于 Set 玩儿交集、并集、差集的操作比如交集吧我们可以把两个人的好友列表整一个交集看看俩人的共同好友是谁对吧。

5. Sorted Set

Sorted set 是排序的 Set去重但可以排序写进去的时候给一个分数自动根据分数排序。

有序集合的使用场景与集合类似但是set集合不是自动有序的而Sorted set可以利用分数进行成员间的排序而且是插入时就排序好。所以当你需要一个有序且不重复的集合列表时就可以选择Sorted set数据结构作为选择方案。

应用场景

  • 排行榜有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜榜单维护可能是多方面按照时间、按照播放量、按照获得的赞数等。
  • 用Sorted Sets来做带权重的队列比如普通消息的score为1重要消息的score为2然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

三、事务

Redis 提供的不是严格的事务Redis 只保证串行执行命令并且能保证全部执行但是执行命令失败时并不会回滚而是会继续执行下去。

MULTI开启命令串行添加命令最后EXEC执行。

  • 若在事务队列中存在命令性错误类似于java编译性错误则执行EXEC命令时所有命令都不会执行。
  • 若在事务队列中存在语法性错误类似于java的1/0的运行时异常则执行EXEC命令时其他正确命令会被执行错误命令抛出异常。

四、持久化

Redis 提供了 RDB 和 AOF 两种持久化方式RDB 是把内存中的数据集以快照形式写入磁盘实际操作是通过 fork 子进程执行采用二进制压缩存储AOF 是以文本日志的形式记录 Redis 处理的每一个写入或删除操作。

RDB 把整个 Redis 的数据保存在单一文件中比较适合用来做灾备但缺点是快照保存完成之前如果宕机这段时间的数据将会丢失另外保存快照时可能导致服务短时间不可用。

AOF 对日志文件的写入操作使用的追加模式有灵活的同步策略支持每秒同步、每次修改同步和不同步缺点就是相同规模的数据集AOF 要大于 RDBAOF 在运行效率上往往会慢于 RDB。

五、高可用

Redis 支持主从同步提供 Cluster 集群部署模式通过 Sentine l哨兵来监控 Redis 主服务器的状态。当主挂掉时在从节点中根据一定策略选出新主并调整其他从 slaveof 到新主。

选主的策略简单来说有三个

  • slave 的 priority 设置的越低优先级越高
  • 同等情况下slave 复制的数据越多优先级越高
  • 相同的条件下 runid 越小越容易被选中。

在 Redis 集群中sentinel 也会进行多实例部署sentinel 之间通过 Raft 协议来保证自身的高可用。

Redis Cluster 使用分片机制在内部分为 16384 个 slot 插槽分布在所有 master 节点上每个 master 节点负责一部分 slot。数据操作时按 key 做 CRC16 来计算在哪个 slot由哪个 master 进行处理。数据的冗余是通过 slave 节点来保障。

哨兵集群最起码需要一个哨兵两个节点

某个节点所在的机器挂了哨兵还有两个两个人一看他不是挂了嘛那我们就选举一个出来执行故障转移就好了。

哨兵组件的主要功能

  • 集群监控负责监控 Redis master 和 slave 进程是否正常工作。
  • 消息通知如果某个 Redis 实例有故障那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移如果 master node 挂掉了会自动转移到 slave node 上。
  • 配置中心如果故障转移发生了通知 client 客户端新的 master 地址。

主从

主要是一台机器顶不住又读又写的需求但让master负责写同时同步数据给slave就能分担负载。

启动一台slave 的时候他会发送一个psync命令给master 如果是这个slave第一次连接到master他会触发一个全量复制。master就会启动一个线程生成RDB快照还会把新的写请求都缓存在内存中RDB文件生成后master会将这个RDB发送给slave的slave拿到之后做的第一件事情就是写进本地的磁盘然后加载进内存然后master会把内存里面缓存的那些新命名都发给slave。

新的slaver进来的时候用RDB那之后的数据呢有新的数据进入master怎么同步到slaver啊

AOF嘛增量的就像MySQL的Binlog一样把日志增量同步给从服务就好了。

六、Key失效机制

Redis 的 key 可以设置过期时间过期后 Redis 采用主动和被动结合的失效机制一个是和 MC 一样在访问时触发被动删除另一种是定期的主动删除。

定期+惰性+内存淘汰

七、缓存常见问题

1.缓存更新方式

缓存的数据在数据源发生变更时需要对缓存进行更新数据源可能是 DB也可能是远程服务。更新的方式可以是主动更新。数据源是 DB 时可以在更新完 DB 后就直接更新缓存。

当数据源不是 DB 而是其他远程服务可能无法及时主动感知数据变更这种情况下一般会选择对缓存数据设置失效期也就是数据不一致的最大容忍时间。

但这样做有个问题如果依赖的远程服务在更新时出现异常则会导致数据不可用。改进的办法是异步更新就是当失效时先不清除数据继续使用旧的数据然后由异步线程去执行更新任务。这样就避免了失效瞬间的空窗期。另外还有一种纯异步更新方式定时对数据进行分批更新。实际使用时可以根据业务场景选择更新方式。

2.数据不一致

第二个问题是数据不一致的问题可以说只要使用缓存就要考虑如何面对这个问题。缓存不一致产生的原因一般是主动更新失败例如更新 DB 后更新 Redis 因为网络原因请求超时或者是异步更新失败导致。

解决的办法是如果服务对耗时不是特别敏感可以增加重试如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新或者短期的数据不一致不会影响业务那么只要下次更新时可以成功能保证最终一致性就可以。

3.缓存穿透

产生这个问题的原因可能是外部的恶意攻击例如对用户信息进行了缓存但恶意攻击者使用不存在的用户id频繁请求接口导致查询缓存不命中然后穿透 DB 查询依然不命中。这时会有大量请求穿透缓存访问到 DB。

解决的办法如下。

  • 对不存在的用户在缓存中保存一个空对象进行标记防止相同 ID 再次访问 DB。不过有时这个方法并不能很好解决问题可能导致缓存中存储大量无用数据。
  • 使用 BloomFilter 过滤器BloomFilter 的特点是存在性检测如果 BloomFilter 中不存在那么数据一定不存在如果 BloomFilter 中存在实际数据也有可能会不存在。非常适合解决这类的问题。

4.布隆过滤器

布隆过滤器是一种空间效率很高的随机数据结构专门用来检测集合中是否存在特定的元素。布隆过滤器由一个长度为m比特的位数组与k个独立的哈希函数组成的数据结构。

位数组初始化均为0所有的哈希函数都可以分别把输入数据尽量均匀地散列。当要向布隆过滤器中插入一个元素时该元素经过k个哈希函数计算产生k个哈希值以哈希值作为位数组中的下标将所有k个对应的比特值由0置为1。当要查询一个元素时同样将其经过哈希函数计算产生哈希值然后检查对应的k个比特值如果有任意一个比特为0表明该元素一定不在集合中如果所有比特均为1表明该元素有可能性在集合中。

Counting Bloom Filter将标准 Bloom Filter位数组的每一位扩展为一个小的计数器counter在插入元素时给对应的kk为哈希函数个数个Counter的值分别加1删除元素时给对应的k个Counter的值分别减1。Counting Bloom Filter通过多占用几倍的存储空间的代价给Bloom Filter增加了删除操作。

5.缓存击穿

缓存击穿就是某个热点数据失效时大量针对这个数据的请求会穿透到数据源。

解决这个问题有如下办法。

  1. 可以使用互斥锁更新保证同一个进程中针对同一个数据不会并发请求到 DB减小 DB 压力。
  2. 使用随机退避方式失效时随机 sleep 一个很短的时间再次查询如果失败再执行更新。
  3. 针对多个热点 key 同时失效的问题可以在缓存时使用固定时间加上一个小的随机数避免大量热点 key 同一时刻失效。

6.缓存雪崩

缓存雪崩产生的原因是缓存挂掉这时所有的请求都会穿透到 DB。

解决方法

  1. 使用快速失败的熔断策略减少 DB 瞬间压力
  2. 使用主从模式和集群模式来尽量保证缓存服务的高可用。
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: redis