Java知识之Redis

目前 Redis 也是后端技术栈中的一员大将,后端面试过程中对 Redis 的考察也越来越多。不过在后端面试过程中对 Redis 的要求没有像 MySQL 那么高(就是面试官问你 Redis,你直接否定三连,这是啥?没听说过!我不会!问题也不是太大)。不过还是我在之前面试八股文系列的文章中所提到的,大家在准备后端面试时,后端技术栈中除了 Java 基础外至少还要有两到三项做到熟悉,作为面试亮点,这样在面试大厂时才和面试官有的聊。我在准备秋招的过程中就把 Redis 部分详细准备,当作我面试的亮点了。

救急准备

对于没有太多时间准备 Redis 的同学,我在这里给大家准备一些面试常问的八股问题。你在面试大厂时,只要别给面试官对你 Redis 部分太高的期望,你把下面这些问题能回答清楚就算过关了。

  1. 什么是 Redis?【⭐⭐】
    简单来说 Redis 就是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。

  2. Redis 除了做缓存,还能做什么?【⭐⭐⭐⭐】
    另外,Redis 除了做缓存之外,也经常用来做分布式锁,甚至是消息队列。

  3. Redis 有哪些数据类型?这些数据类型的应用场景分别是什么?你在项目中用到了吗?【⭐⭐⭐⭐⭐】
    string:string 数据结构是简单的 key-value 类型。
    set,get,strlen,exists,decr,incr,setex 等等。
    一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
    list:list 即是 链表。
    rpush,lpop,lpush,rpop,lrange,llen 等。
    应用场景: 发布与订阅或者说消息队列、慢查询。
    hash:类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表);
    常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。
    应用场景: 系统中对象数据的存储。
    set:set 类似于 Java 中的 HashSet。无序不重复。
    常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。
    应用场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景。

  4. Redis6.0 之后为何引入了多线程?【⭐⭐⭐】
    6.0之前,redis是单线程,通过IO 多路复用程序 来监听来自客户端的大量连接。
    单线程的好处:
    1.单线程编程容易并且更容易维护;
    2.Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
    3.多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
    Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。
    虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
    Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件。开启多线程后,还需要设置线程数,否则是不生效的。

  5. Redis 给缓存数据设置过期时间有啥用?
    因为内存是有限的,如果缓存中的所有数据都是一直保存的话,分分钟直接 Out of memory。很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。

  6. Redis 是如何判断数据是否过期的呢?
    Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。

  7. Redis 过期数据删除策略讲一下。【⭐⭐⭐】
    常用的过期数据的删除策略就两个:
    惰性删除:只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
    定期删除:每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
    定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性/懒汉式删除 。
    但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。
    怎么解决这个问题呢?答案就是:Redis 内存淘汰机制。
    Redis 提供 6 种数据淘汰策略:
    1.volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
    2.volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
    3.volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
    4.allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
    5.allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
    6.no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
    4.0 版本后增加以下两种:
    7.volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
    8.allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。

  8. Redis 的持久化策略了解嘛?分别介绍下 RDB 和 AOF。【⭐⭐⭐⭐】
    很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
    Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)。
    快照(snapshotting)持久化(RDB):
    Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
    快照持久化是 Redis 默认采用的持久化方式。
    AOF(append-only file)持久化:
    与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开。
    开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。
    AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。
    在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

    1
    2
    3
    appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
    appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘
    appendfsync no #让操作系统决定何时进行同步
    NGINX

    为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

  9. 什么是缓存穿透?什么是缓存击穿?什么是缓存雪崩?怎么解决(最高频问题)
    缓存穿透:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
    解决:
    1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
    2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
    缓存击穿:是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
    解决:设置热点数据永远不过期。
    缓存雪崩:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
    解决方案:
    1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
    2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
    3.设置热点数据永远不过期。

  10. 设计一个分布式锁?【⭐⭐】
    见之前分布式锁设计的方案。

  11. Redis 内存淘汰机制了解么?类似问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?【⭐⭐⭐⭐】
    见上题。

  12. Redis 事务你了解嘛?【⭐⭐】
    Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能。
    使用 MULTI 命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 EXEC 命令将执行所有命令。
    这个过程是这样的:
    开始事务(MULTI)。
    命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
    执行事务(EXEC)。

  13. 如何保证 Redis 和 MySQL 的数据一致性?(如果项目同时用到 Redis 和 MySQL,这个问题特别容易被问)【⭐⭐⭐⭐】
    https://www.jb51.net/article/224160.htm
    1、强一致性
    如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存。这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。
    2、弱一致性
    这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。
    3、最终一致性
    最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。一般情况下,高可用只确保最终一致性,不确保强一致性。
    强一致性,读请求和写请求会串行化,串到一个内存队列里去,这样会大大增加系统的处理效率,吞吐量也会大大降低。

    这张图,大多数人的很多业务操作都是根据这个图来做缓存的。但是一旦设计到双写或者
    数据库和缓存更新等操作,就很容易出现数据一致性的问题。无论是先写数据库,在删除缓存,还是先删除缓存,在写入数据库,都会出现数据一致性的问题。列举两个小例子。
    1、 先删除了redis缓存,但是因为其他什么原因还没来得及写入数据库,另外一个线程就来读取,发现缓存为空,则去数据库读取到之前的数据并写入缓存,此时缓存中为脏数据。
    2、 如果先写入了数据库,但是在缓存被删除前,写入数据库的线程因为其他原因被中断了,没有删除掉缓存,就也会出现数据不一致的情况。
    总的来说,写和读在多数情况下都是并发的,不能绝对保证先后顺序,就会很容易出现缓存和数据库数据不一致的情况,还怎么解决呢?
    1、方案一:采用延时双删策略
    基本思路: 在写库前后都进行删除缓存操作,并且设置合理的超时时间
    基本步骤: 先删除缓存–再写数据库—休眠一段时间—再次删除缓存
    注:休眠的时间是根据自己的项目的读数据业务逻辑的耗时来确定的。这样做主要是为了保证在写请求之前确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
    该方案的弊端: 集合双删策略+缓存超时策略设置,这样最差的结果就是在超时时间内数据存在不一致,又增加了写请求的耗时。
    2、方案二:一步更新缓存(基于订阅Binlog的同步机制)
    基本思路: mysql Binlog增强订阅消费+消息队列+增量数据更新到redis—读redis:热数据基本上都在redis—写mysql:增删改都是操作mysql—更新redis数据:mysql的数据操作Binlog,来更新redis
    我们再来看看详细的过程
    1、Redis更新
    1)、数据操作主要分为两大块:
    一个是全量,将全部数据写去redis;另一个就是增量(update、insert、delete),实时更新。
    2)、读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
    这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
    其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
    这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。
    当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。

由于篇幅问题,我并没有写上面这些问题的答案。你可以自行查阅,你也可以参考 JavaGuide 这个开源项目,涵盖了大部分 Java 程序员所需要掌握的核心知识。背面试八股文,这是一个必看的开源项目(学 Java 基础以及实战也推荐看这个项目)。

当然了,你也可以直接百度/Google 搜索,篇幅有限,我就不贴答案了。

系统学习

我在准备 Redis 前没任何 Redis 经验,我集中准备 Redis 用了差不多三周的时间,每天看四个小时左右。后期每次在重要面试前也会花一两个小时进行复习。这是我在 Redis 上花的时间,供大家参考。

学 Redis 我推荐这本书《Redis 的设计与实现》,我先声明,我可不是卖书的哈。我推荐的是我实际看过的书,没书的大家自己想办法呀~不过还是尽可能支持一下作者噢。

推荐理由:这本书讲的特别特别详细,又有概念讲解,又有实际例子。十分通俗易懂。

学习内容

第一部分 数据结构与对象

Redis 的数据结构设计十分巧妙,我也是在看这部分时决定详细学习 Redis 的。大家千万不要去背这些数据结构的代码是怎么写的,这么多你肯定记不住。大家主要体会一下 Redis 的这些 数据结构设计好在哪,面试的时候如果面试官问到这些数据结构,可以相应的抛出一些我觉得 xxx 这样设计的很不错,他用 xxx 优化了什么问题的观点,也是不错的。另外在这本书上没有,但是一个挺重要的数据结构叫 布隆过滤器 强烈建议大家看一下,这个数据结构在解决许多面试官提出的场景题时都是很管用的。

第二部分 单机数据库的实现

面试官特别喜欢从这一部分出问题,所以是需要花点功夫把这些八股文背熟的。

第 9 章 数据库

这一章要记牢 Redis 的过期删除策略、以及 AOF、RDB 和复制功能对过期键的处理。其它的内容做到理解就好。

第 10 章 RDB 持久化

记熟Redis 服务器保存和载入 RDB 文件的方法,要辨别出SAVE 命令BGSAVE 的区别。把自动保存功能的实现原理也尽可能记熟。RDB 的文件结构了解就好。

第 11 章 AOF 持久化

记熟 AOF 持久化 的实现方式,记熟 AOF 文件载入与数据还原的流程,记熟 AOF 重写方式。这一章学习过程中要与 RDB 去做对比。

第 12 章 事件

Redis 文件事件处理器 的组成部分要记熟,特别是 I/O 多路复用要充分理解,这里在面试官问 I/O 相关的时候可以给面试官讲,并且可以很自然的把话题扯到 Redis~

时间事件理解就好了。

第 13 章 客户端

这一章理解就好

第 14 章 服务器

把一个命令请求从发送到获得回复的过程理解,不用硬背,不过要大概能复述出来。serverCron 函数负责管理服务器的资源,把这个函数理解了给面试官讲也是不错的。

第三部分 多机数据库的实现

这一部分如果你不主动提,面试官一搬不会主动问。不过把这里理解清楚以后融合到项目中主动给面试官讲还是挺不错的。我就把这部分融合到我的秒杀系统中了,给面试官讲完以后感觉效果还不错。

第 15 章 复制

主从复制的流程要尽可能记熟呀,把这里记熟给面试官讲还是挺不错的。注意区分新旧版本的 Redis 在主从复制上的差异。

第 16 章 哨兵

记牢 Sentinel 的初始化过程,记牢 Sentinel 监视服务器的方法和原理,记牢 Sentinel 系统对主服务器执行故障转移的过程。

第 17 章 集群

我把 Redis 集群用在我的秒杀系统上了,所以集群的节点槽指派命令执行重新分片转向故障转移消息都背的很熟了。讲项目的时候也能用上这些。

第四部分 独立功能的实现

这一部分,我就重点看了发布与订阅、看了 Lua 脚本,其它的就没怎么看了。大家感兴趣可以多看一下。

好了,经过上面的学习,Redis 的基础就打的很牢了。不过我在这里还要给大家多推荐一本书,它就是《Redis 深度历险》。《Redis 设计与实现》偏向与 Redis 的原理,而《Redis 深度历险》偏向于实际场景的应用。大家在学完我上述讲的知识点以后,再选择性的学习两三个《Redis 深度历险》中的实例。在校招面试中就不用担心面试官的发难了。

如果你比较喜欢看视频的话,推荐你看一下尚硅谷的 《Redis 入门到精通》。

这套课程采用的是 Redis 6.2.1 版本,从 Redis 的基本概念开始讲解一直到 Redis 持久化 RDB 和 AOF、Redis 主从复制和集群、Redis 应用中的问题和解决方案(缓存穿透、击穿、雪崩、分布式锁)、Redis 的新数据类型以及 Redis 6 的新特性等 Redis 进阶内容。

看看这个点赞、投币、收藏、转发量是多么恐怖!

尚硅谷算得上是比价良心的培训机构了,免费开源了很多免费且高质量的教学视频,帮助了很多小伙伴学习编程。


Java知识之Redis
https://leehoward.cn/2022/03/20/Java知识之Redis/
作者
lihao
发布于
2022年3月20日
许可协议