JAVA后端开发面试基础知识(六)——Redis

1. 内存淘汰策略

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除近少使用的key。(这个是最常用的)
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除近少使用的key。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

2. Redis集群有16384个哈希槽

3. redis缓存雪崩,缓存击穿,缓存穿透是什么,怎么解决

  • 缓存雪崩

    指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

    • 解决方案
      • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
      • 一般并发量不是特别多的时候,使用多的解决方案是加锁排队。
      • 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
  • 缓存穿透

    缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

    • 解决方案
      1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
      2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
      3. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力附加。
        • Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决"冲突"。
        • Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不 在集合中,那么该元素肯定不在集合中。只有在所有的Hash
        • 函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是 Bloom-Filter的基本思想。 Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。
  • 缓存击穿

    缓存击穿是指缓存中没有但数据库中有的数据 (一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

    • 解决方案
      1. 设置热点数据永远不过期。
      2. 加互斥锁,互斥锁缓存预热。
        • 缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据!
        • 解决方案
          • 直接写个缓存刷新页面,上线时手工操作一下;
          • 数据量不大,可以在项目启动的时候自动进行加载;
          • 定时刷新缓存;
  • 缓存降级

    当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

    • 缓存降级的终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。 在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
      • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
      • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
      • 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的大阀值,此时可以根据情况自动降级或者人工降级;
      • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
    • 服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是, Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
  • 热点数据和冷数据

4. redis 数据类型

  • 字符串 :一个字符串允许存储的最大容量为512MB。字符串是其他四种类型的基础,与其他几种类型的区别从本质上来说只是组织字符串的方式不同而已。
  • 散列表:散列类型不能嵌套其他数据类型。一个散列类型键最多可以包含2的32次方-1个字段。
  • 列表:列表内部使用的是双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度是O(1),获取越接近列表两端的元素的速度越快。但是缺点是使用列表通过索引访问元素的效率太低(需要从端点开始遍历元素)。
  • 集合:集合中每个元素都是不同的,集合中的元素个数最多为2的32次方-1个,集合中的元素是没有顺序的。
  • 有序集合:有序集合使用散列表和跳跃表实现,即使读取位于中间部分的数据也很快,时间复杂度为O(log(N)),有序集合比列表更费内存。

5. 跳跃表

Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群结点中用作内部数据结构

  • 原理

    跳跃表在redis中主要是有序表 的一种底层实现。对于普通链表的查找,即使有序,我们也不能使用二分法,需要从头开始,一个一个找,时间复杂度为O(n)。而对于跳跃表,从名字可以看出跳跃表的优势就在于可以跳跃。如何做到呢?在于其特殊的层设计。比如我们查找46,普通链表只能从头开始查找,比对-3,2,17...直到46,要比对7次。但是对于跳 跃表,我们可以从最高层开始查找:

    第一步:在L4层直接与55比对,发现大了,退回到第3层

    第二步:在L3层与21比对,发现小了,继续往前比对55,发现大了,退回到第二层

    第三步:在L2层与37比对,发现小了,往前,与55比对,发现大了,退回到第一层

    第四步:在第1层,与46比对,查找成功。

  • 跳表具有如下性质:

    1. 由很多层结构组成
    2. 每一层都是一个有序的链表
    3. 最底层(Level 1)的链表包含所有元素
    4. 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现
    5. 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
  • redis为啥使用跳表作为索引,不使用B+树?

    • 因为B+树的原理是叶子节点存储数据,非叶子节点存储索引,B+树的每个节点可以存储多个关键字,它将节点大小设置为磁盘页的大小,充分利用了磁盘预读的功能。每次读取磁盘页时就会读取一整个节点,每个叶子节点还有指向前后节点的指针,为的是最大限度的降低磁盘的IO;因为数据在内存中读取耗费的时间是从磁盘的IO读取的百万分之一

    • 而Redis是内存中读取数据,不涉及IO,因此使用了跳表;

6. Redis key的过期时间和永久有效分别怎么设置?

EXPIRE和PERSIST命令。

7. 集群

  • 同步方法
    • 全同步。全同步是第一次从机连主机是进行的同步,主机会生成一个RDB文件给从机,然后从机加载该文件。 并且如果从机掉线时间很长时也会触发这个同步,掉线时间短时使用另外的策略
    • 部分同步。当主机收到修改命令之后会把命令发给从机进行部分同步。 这里会有一个缓存区,主要是用来,如果有从机掉线,再次连接的时候会优先使用缓存区中的数据进行同步,是在不行才使用全同步

8. 消息中间件如何保证消息的一致性

  1. 主动方应用先把消息发送给消息中间件,消息状态标记为待确认;
  2. 消息中间件收到消息之后,把消息持久化到消息存储中,但并不向被动方应用投递消息;
  3. 消息中间件返回消息持久化结果 (成功,或者失效),主动方应用根据返回结果进行判断如何处理业务操作处理;
    • 失败:放弃业务操作处理,结束(必须向上层返回失败结果)
    • 成功:执行业务操作处理
  4. 业务操作完成后,把业务操作结果(成功/失败)发送给消息中间件;
  5. 消息中间件收到业务操作结果后,根据结果进行处理;
    • 失败:删除消息存储中的消息,结束;
    • 成功:更新消息存储中的消息状态为∙待发送(可发送)∙,紧接着执行消息投递;
  6. 前面的正向流程都成功后,向被动方应用投递消息;

9. 哨兵模式

10. 事务

  • 三个阶段

    1. 事务开始 MULTI
    2. 命令入队
    3. 事务执行 EXEC

    事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把请求放入队列中排队

  • Redis会将一个事务中的所有命令序列化,然后按顺序执行。

    1. redis 不支持回滚,"Redis 在事务失败时不进行回滚,而是继续执行余下的命令", 所以 Redis 的内部可以保持简单且快速。
    2. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
    3. 如果在一个事务中出现运行错误,那么正确的命令会被执行。
  • WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监 控一直持续到EXEC命令。

  • MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。

  • EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值null。

  • 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出。

  • Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。

11. 如何保证缓存与数据库双写时的数据一致性

先写数据库,再写缓存。写数据库成功,但写缓存失败,则先读数据库,再回写缓存

相关推荐
Mcworld85721 分钟前
整数分解JAVA
java·开发语言
星星点点洲27 分钟前
【Redis】RedLock实现原理
redis·缓存
我来整一篇31 分钟前
用Redis的List实现消息队列
数据库·redis·list
小南家的青蛙1 小时前
LeetCode面试题 01.09 字符串轮转
java·leetcode
加什么瓦1 小时前
Redis——数据结构
数据库·redis·缓存
秋野酱1 小时前
基于javaweb的SpringBoot爱游旅行平台设计和实现(源码+文档+部署讲解)
java·spring boot·后端
饕餮争锋1 小时前
org.slf4j.MDC介绍-笔记
java·开发语言·笔记
shane-u1 小时前
Maven私服搭建与登录全攻略
java·maven
半部论语1 小时前
jdk多版本切换,通过 maven 指定编译jdk版本不生效,解决思路
java·开发语言·maven·intellij-idea
有梦想的攻城狮1 小时前
spring中的@Async注解详解
java·后端·spring·异步·async注解