当前位置: 首页 > news >正文

面试复盘整理

面试复盘


总结:

​ 面试了将近一个月,大厂基本上都面过了,大厂技术栈基本上都是springcloud+mysql+redis+kafka,一面基本上都是自我介绍,问问kafka问问redis问问mysql问问并发。问题基本上都很简单,虽然说都是八股文背一背就行了,不过最好还是有自己的理解,不然稍微厉害的面试官会考察你的思路根据你的理解出场景题,如果只是背诵问两个问题就能发现你根本不理解。简历上写的技术一定要重点准备。

​ 面试碰到不会的场景题目没关系,跟面试官表达出自己的思考过程,面试官一般也会提点下。如果碰到不会的知识点,直接坦言自己接触过较少,不会即可。但是自我介绍,尤其是项目介绍一定要条理清楚,逻辑清晰。建议先面试一两个然后录音复盘。

中间件:

​ Kafka(RocketMQ只有腾讯阿里问了几句)

​ Redis(数据类型基本都会问,持久化也基本都会问,厉害一点的面试官会根据数据类型的原理出场景题问你怎么实现)

数据库:

​ Mysql(架构+索引、调优)

Java:

​ LinkedList与ArrayList、hashmap

​ 并发编程:volatile、synchronized、同步器、线程数据交换

算法:

​ 我是没碰到特别难得算法,面试过的大厂只有lazada给45min做了算法题,其他都是面试结束给20min左右,所以题目基本是leetcode初中级足够了,考点基本上就是链表、bfs、dfs、堆(topK)。

典型考题:

​ topK

​ 链表题目(考的次数非常多)

​ N个线程按顺序输出(考了两次,之前专门写了个博客)


消息队列篇

介绍下消息队列

消息队列我个人的理解,其实就是做一件事:系统A将消息发送给消息队列,然后系统B从消息队列去获取A发送的消息。

那么,消息队列需要有几点能力:

  • 消息的堆积能力:如果系统A发送上去了,系统B因为网络原因或者是出现故障宕机,导致消费延迟,消息都在消息队列中堆积了,不能因为消息堆积影响消息队列的性能
  • 高可靠性:消息不能发送成功,但是丢失了啊
  • 高可用性:就是说因为一些原因消息队列不能提供了,需要短时间内有解决方案——一般是副本机制,原来的消息队列不能提供了,那就换一个备用的来
  • 低延迟:系统A发送了消息如果系统B需要过很久很久才能获取,那肯定体验是不好的

为什么需要消息队列?

​ 从上面消息队列的介绍和能力可以看出,它其实可以做三件事:异步、解耦、削峰。

异步

​ 异步的价值,个人理解是可以把非核心的业务逻辑从核心的业务中抽取出来,比如我们购买商品,通常下完订单后不仅仅是商品库存-1,还会包含将商品从购物车中删除、生成订单等操作。但是其实只有减去库存跟支付才是核心的业务逻辑,如果刚才说的操作都是同步调用,那么假设后面再来一个发送优惠券,或者给用户发个短信等需求,那商品支付的代码就越来越多,掺杂了一大堆跟他自己没关系的业务,而且请求响应也会变慢。所以这个时候我们就需要消息队列,好处有两点:

  • 可以更快地返回结果;
  • 减少等待,自然实现了步骤之间的并发,提升系统总体的性能。

解耦

​ 还是刚才的例子,商品支付成功,减库存成功了,就可以给用户返回响应了。它只需要把用户下订单这个消息发送给消息队列,让购物车服务、订单服务、优惠券服务自己去订阅然后处理就行了。如果后面再来一个别的需求,也仅仅是去订阅这个事件,然后做自己的业务逻辑就好了,把商品服务与这一堆其他的服务解耦。这样不管需求怎么变,商品服务不需要做任何修改。

削峰

​ 如果是在秒杀的情况下,秒杀的特性:在短时间内会有非常多的请求,那就有可能因为过多的请求压垮我们的系统。这种情况下也可以使用消息队列,隔离网关与后端服务,达到一个流量控制与保护后端服务的目的。

​ 那流程就是:网关收到请求->放入消息队列->后端服务从消息队列取出请求,处理秒杀逻辑->返回结果,这样秒杀开始后,大量的请求会堆积在消息队列中,后端按照自己的能力来处理,而超时的请求则可以直接拒绝。

优点:

​ 可以根据下游的处理能力自动调节流量,也就是所谓的“削峰填谷”

缺点:

​ 因为加上了消息队列,多了系统调用的环节,总体响应时间可能会延长

​ 上游下游变为异步调用,系统变复杂了。

​ 当然流量控制还有其他的方法,比如可以加上限流策略,使用令牌桶算法或者漏桶算法。

你的项目在哪里用到了消息队列?


RocketMQ:

事务反查机制怎么实现的?

在这里插入图片描述

RocketMQ是怎么持久化的?

RocketMQ 在持久化的设计上,采取的是**「消息顺序写、随机读的策略」**,利用磁盘顺序写的速度,让磁盘的写速度不会成为系统的瓶颈。并且采用 MMPP 这种“零拷贝”技术,提高消息存盘和网络发送的速度。极力满足 RocketMQ 的高性能、高可靠要求。

消息数据刷盘

因为操作系统 PAGECACHE 的存在,PageCache是OS对文件的缓存,用于加速对文件的读写,所以一般都是先写入到 PAGECACHE 中,然后再持久化到磁盘上。我们熟悉的其他组件,MySQL、Redis 等都是如此。RocketMQ 也不列外。在 RocketMQ 中提供了**「同步刷盘」「异步刷盘」两种刷盘方式,可以通过 Broker 配置文中中的 flushDiskType 参数来设置(SYNC_FLUSH、ASYNC_FLUSH)。「异步刷盘方式(默认)」:消息写入到内存的 PAGECACHE中,就立刻给客户端返回写操作成功,当 PAGECACHE 中的消息积累到一定的量时,触发一次写操作,将 PAGECACHE 中的消息写入到磁盘中。这种方式「吞吐量大,性能高,但是 PAGECACHE 中的数据可能丢失,不能保证数据绝对的安全」「同步刷盘方式」:消息写入内存的 PAGECACHE 后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。这种方式「可以保证数据绝对安全,但是吞吐量不大」**


Kafka

Kafka概念介绍

Kafka 属于分布式的消息引擎系统。(摘自官网)

Kafka的核心概念?Kafka有哪些组件?

  • 消息:Record。Kafka 是消息引擎嘛,这里的消息就是指 Kafka 处理的主要对象。
  • Broker:Kafka 的服务器端由被称为 Broker 的服务进程构成,即一 个 Kafka 集群由多个 Broker 组成,Broker 负责接收和处理客户端发送过来的请求,以及 对消息进行持久化。
  • 主题:Topic。主题是承载消息的逻辑容器,在实际使用中多用来区分具体的业务。
  • 分区:Partition。一个有序不变的消息序列。每个主题下可以有多个分区。
  • 消息位移:Offset。表示分区中每条消息的位置信息,是一个单调递增且不变的值。
  • 副本:Replica。Kafka 中同一条消息能够被拷贝到多个地方以提供数据冗余,这些地方 就是所谓的副本。副本还分为领导者副本和追随者副本,各自有不同的角色划分。副本是在分区层级下的,即每个分区可配置多个副本实现高可用。
  • 生产者:Producer。向主题发布新消息的应用程序。
  • 消费者:Consumer。从主题订阅新消息的应用程序。
  • 消费者位移:Consumer Offset。表征消费者消费进度,每个消费者都有自己的消费者 位移。
  • 消费者组:Consumer Group。多个消费者实例共同组成的一个组,同时消费多个分区 以实现高吞吐。
  • 重平衡:Rebalance。消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配 订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。

Kafka为什么这么快(可以问到的频率非常高,可以进一步问你0拷贝、pageCache)

  • partition 并行处理提高消费者端消费吞吐量
  • 顺序写磁盘,充分利用磁盘特性
  • 利用了现代操作系统分页存储 Page Cache 来利用内存提高 I/O 效率
  • 采用了零拷贝技术
  • Producer 生产的数据持久化到 broker,采用 mmap 文件映射,实现顺序的快速写入
  • Customer 从 broker 读取数据,采用 sendfile,将磁盘文件读到 OS 内核缓冲区后,转到 NIO buffer进行网络发送,减少 CPU 消耗

Kafka是怎么保证消息不丢失的?

生产者端:

kafka生产者端是异步发送+ack机制

  • 不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。记住,一 定要使用带有回调通知的 send 方法,因为kafka是异步发送,最好配合消息表,在回调方法去修改消息表的消息发送状态
  • 设置 acks = all。acks 是 Producer 的一个参数,代表了你对“已提交”消息的定义。 如果设置成 all,则表明所有副本 Broker 都要接收到消息,该消息才算是“已提交”。 这是最高等级的“已提交”定义。
  • 设置 retries 为一个较大的值。这里的 retries 同样是 Producer 的参数,对应前面提到 的 Producer 自动重试。当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了retries > 0 的 Producer 能够自动重试消息发送,避免消息丢失
Broker端:

通过副本机制保证高可靠性(ISR集合)

  • 设置 unclean.leader.election.enable = false。这是 Broker 端的参数,它控制的是哪些 Broker 有资格竞选分区的 Leader。如果一个 Broker 落后原先的 Leader 太多,那么 它一旦成为新的 Leader,必然会造成消息的丢失。故一般都要将该参数设置成 false, 即不允许这种情况的发生。
  • 设置 replication.factor >= 3。这也是 Broker 端的参数。其实这里想表述的是,最好将消息多保存几份,毕竟目前防止消息丢失的主要机制就是冗余。这样就可以保证每个 分区(partition) 至少有 3 个副本。虽然造成了数据冗余,但是带来了数据的安全性。
  • 设置 min.insync.replicas > 1。ISR集合中的最少副本数,默认值是1,这样配置代表消息至少要被写入到 2 个副本才算是被成功发送。,并只有在acks=all或-1时才有效,acks与min.insync.replicas搭配使用,才能为消息提供最高的持久性保证。(leader副本默认就包含在ISR中)
  • 确保 replication.factor > min.insync.replicas。如果两者相等,那么只要有一个副本挂机,整个分区就无法正常工作了。我们不仅要改善消息的持久性,防止数据丢失,还要 在不降低可用性的基础上完成。推荐设置成 replication.factor = min.insync.replicas + 1。
消费者端:
  • 确保消息消费完成再提交。Consumer 端有个参数 enable.auto.commit,最好把它设 置成 false,并采用手动提交位移的方式。就像前面说的,这对于单 Consumer 多线程 处理的场景而言是至关重要的。

Kafka在什么情况下消息会发重复,如何处理?

生产者:

​ 如果网络原因,kafka给生产者发ack确认超时或者干脆发送失败,生产者会发送多次

消费者:

​ ack机制同理

处理:业务幂等性

数据库唯一主键——唯一主键又可以继续考察kafka是否可以保证全局有序(并不,partition内有序,可以在生产者端自定义分区机制保证一个业务维度的消息都分配在一个partition的方式处理)

流水表校验是否处理过(这个时候表有可能因为间隙锁导致死锁)

Kafka的日志?Kafka的消息是否可以持久化?保存默认时间是多少?

Kafka 的存储以 Partition 为单位,每个 Partition 包含一组消息文件 (Segment file)和一组索引文件(Index),并且消息文件和索引文件一一对应,具有相 同的文件名(但文件扩展名不一样),文件名就是这个文件中第一条消息的索引序号。 每个索引中保存索引序号(也就是这条消息是这个分区中的第几条消息)和对应的消息在消 息文件中的绝对位置。在索引的设计上,Kafka 采用的是稀疏索引,为了节省存储空间,它不会为每一条消息都创建索引,而是每隔几条消息创建一条索引。

查找消息时,首先根据文件名找到所在的索引文件,然后用二分法遍历索引文件内的 索引,在里面找到离目标消息最近的索引,再去消息文件中,找到这条最近的索引指向的消 息位置,从这个位置开始顺序遍历消息文件,找到目标消息。

Kafka 使用消息日志(Log)来保存数据,一个日志就是磁盘上一个只能追加写(Append-only)消 息的物理文件。因为只能追加写入,故避免了缓慢的随机 I/O 操作,改为性能较好的顺序 I/O 写操作,这也是实现 Kafka 高吞吐量特性的一个重要手段。不过如果你不停地向一个 日志写入消息,最终也会耗尽所有的磁盘空间,因此 Kafka 必然要定期地删除消息以回收 磁盘。怎么删除呢?简单来说就是通过日志段(Log Segment)机制。在 Kafka 底层,一 个日志又近一步细分成多个日志段,消息被追加写到当前最新的日志段中,当写满了一个日 志段后,Kafka 会自动切分出一个新的日志段,并将老的日志段封存起来。Kafka 在后台还 有定时任务会定期地检查老的日志段是否能够被删除,从而实现回收磁盘空间的目的。

Kafka的offset?保存在哪里?

老版本的 Consumer Group 把位移保存在 ZooKeeper 中。在新版本的 Consumer Group 中,Kafka 社区重新设计了 Consumer Group 的位移管理方式,采用了将位移 保存在 Kafka 内部主题的方法。这个内部主题就是让人既爱 又恨的 __consumer_offsets.

Consumer 需要为分配给它的每个分区提交各自的位 移数据,种提交位移的方法:

  • 从用户的角度来说,位移提交分为自动提交和手动提交;

    • 开启自动提交位移参数:enable.auto.commit,默认true
    • 每几秒自动提交位移:auto.commit.interval.ms,默认5s
  • 从 Consumer 端的角度来说,位移提交分为同步提交和异步提交。

    • 同步提交:KafkaConsumer#commitSync(),consumer会阻塞直到收到Broker的响应
    • 异步提交:KafkaConsumer#commitAsync(),出错不会自动重试

    两个方法结合使用:

    try {
                while (true) {
                    ConsumerRecords<String, String> records =
                            consumer.poll(Duration.ofSeconds(1));
                    process(records); // 处理消息
                    commitAysnc(); // 使用异步提交规避阻塞
                }
            } catch (Exception e) {
                handle(e); // 处理异常
            } finally {
                try {
                    consumer.commitSync(); // 最后一次提交使用同步阻塞式提交
                } finally {
                    consumer.close();
                }
            }
    

操作系统

PageCache

引入 Cache 层的目的是为了提高 Linux 操作系统对磁盘访问的性能。Cache 层在内存中缓存了磁盘上的部分数据。当数据的请求到达时,如果在 Cache 中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。Cache 层也正是磁盘 IOPS 为什么能突破 200 的主要原因之一。

PageCache做了两件事:

  • 刚被访问的数据在短时间内再次被访问的概率很高(这 也叫“时间局部性”原理),用 PageCache 缓存最近访问的数据,当空间不足时淘汰最久 未被访问的缓存(即 LRU 算法)。读磁盘时优先到 PageCache 中找一找,如果数据存在 便直接返回,这便大大提升了读磁盘的性能
  • 第二,读取磁盘数据时,需要先找到数据所在的位置,对于机械磁盘来说,就是旋转磁头到 数据所在的扇区,再开始顺序读取数据。其中,旋转磁头耗时很长,为了降低它的影响, PageCache 使用了预读功能

使用 Page Cache 的好处:

  • I/O Scheduler 会将连续的小块写组装成大块的物理写从而提高性能
  • I/O Scheduler 会尝试将一些写操作重新按顺序排好,从而减少磁盘头的移动时间
  • 充分利用所有空闲内存(非 JVM 内存)。如果使用应用层 Cache(即 JVM 堆内存),会增加 GC 负担
  • 读操作可直接在 Page Cache 内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘(直接通过 Page Cache)交换数据
  • 如果进程重启,JVM 内的 Cache 会失效,但 Page Cache 仍然可用

PageCache不起作用

传输大文件的时候。比如,你有很多 GB 级的文件需要传输,每 当用户访问这些大文件时,内核就会把它们载入到 PageCache 中,这些大文件很快会把有 限的 PageCache 占满。然而,由于文件太大,文件中某一部分内容被再次访问到的概率其实非常低。这带来了 2 个问题:首先,由于 PageCache 长期被大文件占据,热点小文件就无法充分使用 PageCache,它们读起来变慢了;其次,PageCache 中的大文件没有享受到缓存的好处, 但却耗费 CPU 多拷贝到 PageCache 一次。

所以,高并发场景下,为了防止 PageCache 被大文件占满后不再对小文件产生作用,大文件不应使用 PageCache,进而也不应使用零拷贝技术处理。

大文件处理

大文 件:异步 IO + 直接 IO

异步 IO(异步 IO 既可以处理网络 IO,也可以处理磁盘 IO,这里我们只关注磁盘 IO)可 以解决阻塞问题。它把读操作分为两部分,前半部分向内核发起读请求,但不等待数据就位 就立刻返回,此时进程可以并发地处理其他任务。当内核将磁盘中的数据拷贝到进程缓冲区 后,进程将接收到内核的通知,再去处理数据,这是异步 IO 的后半部分。

直接 IO

绕过 PageCache 的 IO 是个新物种,我们把它称为直接 IO。对于磁盘,异步 IO 只支持直 接 IO。

直接 IO 的应用场景并不多,主要有两种:第一,应用程序已经实现了磁盘文件的缓存,不 需要 PageCache 再次缓存,引发额外的性能消耗。比如 MySQL 等数据库就使用直接 IO;第二,高并发下传输大文件,我们上文提到过,大文件难以命中 PageCache 缓存,又 带来额外的内存拷贝,同时还挤占了小文件使用 PageCache 时需要的内存,因此,这时应 该使用直接 IO。

当然,直接 IO 也有一定的缺点。除了缓存外,内核(IO 调度算法)会试图缓存尽量多的 连续 IO 在 PageCache 中,最后合并成一个更大的 IO 再发给磁盘,这样可以减少磁盘的 寻址操作;另外,内核也会预读后续的 IO 放在 PageCache 中,减少磁盘操作。直接 IO 绕过了 PageCache,所以无法享受这些性能提升。


内核空间与用户空间

操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限。
为了避免用户进程直接操作内核,保证内核安全,操作系统将虚拟内存划分为两部分,一部分是内核空间(Kernel-space),一部分是用户空间(User-space)。

零拷贝

零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间。
它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现 CPU 的零参与,彻底消除 CPU 在这方面的负载。


Redis(高频)

Redis为什么这么快?

一方面,这是因为它是内存数据库, 所有操作都在内存上完成,内存的访问速度本身就很快。

另一方面,这要归功于它的数据结构。这是因为,键值对是按一定的数据结构来组织的,操作键值对最终就是对数据结构 进行增删改查操作,所以高效的数据结构是 Redis 快速处理数据的基础。

另一方面,则是因为它使用I/O多路复用

基于多路复用的高性能 I/O 模型

Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同 时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据 请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果

下图就是基于多路复用的 Redis IO 模型。图中的多个 FD 就是刚才所说的多个套接字。 Redis 网络框架调用 epoll 机制,让内核监听这些套接字。此时,Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理 上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。

为了在请求到达时能通知到 Redis 线程,select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。那么,回调机制是怎么工作的呢?其实,select/epoll 一旦监测到 FD 上有请求到达时,就 会触发相应的事件。

这些事件会被放进一个事件队列,Redis 单线程对该事件队列不断进行处理。这样一来, Redis 无需一直轮询是否有请求实际发生,这就可以避免造成 CPU 资源浪费。同时, Redis 在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件 的回调。因为 Redis 一直在对事件队列进行处理,所以能及时响应客户端请求,提升 Redis 的响应性能。

在这里插入图片描述

String、Zset、Hashtable的底层数据结构?(较高频)

在这里插入图片描述

压缩列表

压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。和数组不同 的是,压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的 偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。

在压缩列表中,如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段 的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查 找,此时的复杂度就是 O(N) 了。

在这里插入图片描述

跳表:

有序链表只能逐一查找元素,导致操作起来非常缓慢,于是就出现了跳表。具体来说,跳表在链表的基础上,增加了多级索引,可以使用二分查找,实现数据的快速定位,

在这里插入图片描述

Redis持久化?有什么区别?你们怎么使用的?

Redis 的持久化主要有两大机制,即 AOF 日志和 RDB 快照。

AOF 日志

AOF的全称是Append Only File,表示文件只能追加写。 Redis记日志时,就是用追 加写文件的方式记录写命令操作的。

Redis 是先执行命令,把数据写入内存,然后才记录日志,如下图所示:

在这里插入图片描述

后写日志优点:

  • 为了避免额外的检查开销,先让系统执行命令,只有命令能执行成功,才会被记录到日志 中,否则,系统就会直接向客户端报错。
  • 它是在命令执行后才记录日志,所以不会阻塞当前的写操作。

缺点:

  • 如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数 据就有丢失的风险。
  • AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因 为,AOF 日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就 会导致写盘很慢,进而导致后续的操作也无法执行了。
appendfsync三种写回策略
  • Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
  • Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲 区,每隔一秒把缓冲区中的内容写入磁盘;
  • No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓 冲区,由操作系统决定何时将缓冲区内容写回磁盘。
    在这里插入图片描述
AOF 重写机制

​ 重写过程是由后台线程 bgrewriteaof 来完成的,这也是 为了避免阻塞主线程,导致数据库性能下降。重写的过程总结为“一个拷贝,两处日志”。旧日志文件中的多条命令,在重写后的新日志中变成了一条命 令。

“一个拷贝”就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此 时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的 最新数据。

fork子进程时,子进程是会拷贝父进程的页表,即虚 实映射关系,而不会拷贝物理内存。子进程复制了父进程页表,也能共享访问父进程的内存数据 了,此时,类似于有了父进程的所有内存数据。

“两处日志”:

  • 因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第一处日志就是指 正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这 个 AOF 日志的操作仍然是齐全的,可以用于恢复。
  • 新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区。这 样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日 志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我 们就可以用新的 AOF 文件替代旧文件了。

在这里插入图片描述

RDB内存快照

原理:fork+cow

对 Redis 来说,它实现类似照片记录效果的方式,就是把某一时刻的状态以文件的形式写 到磁盘上,也就是快照。这样一来,即使宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。这个快照文件就称为 RDB 文件,其中,RDB 就是 Redis DataBase 的缩写。

和 AOF 相比,RDB 记录的是某一时刻的数据,并不是操作,所以,在做数据恢复时,我 们可以直接把 RDB 文件读入内存,很快地完成恢复。

Redis 的数据都在内存中,为了提供所有数据的可靠性保证,它执行的是全量快照,也就 是说,把内存中的所有数据都记录到磁盘中。Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。

  • save:在主线程中执行,会导致阻塞;
  • bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。

bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。 bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C), 那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本 数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。

在这里插入图片描述

混合持久化

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。

选择

数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;

如果允许分钟级别的数据丢失,可以只使用 RDB;

如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。

rehash

Redis的一个database中所有key到value的映射,就是使用一个dict来维护的。dict是一个用于维护key和value映射关系的哈希表数据结构,与很多语言中的Map或dictionary类似。dict本质上是为了解决算法中的查找问题(Searching),也会存在哈希冲突,redis使用链式解决哈希冲突。

Redis 会对哈希表做 rehash 操作。rehash 也就是增加现有的哈希桶数量,让逐渐增多的 entry 元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突。那具体怎么做呢?

为了使 rehash 操作更高效,Redis 默认使用了两个全局哈希表:哈希表 1 和哈希 表 2。一开始,当你刚插入数据时,默认使用哈希表 1,此时的哈希表 2 并没有被分配空 间。随着数据逐步增多,Redis 开始执行 rehash,这个过程分为三步:

  • 给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;
  • 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
  • 释放哈希表 1 的空间。

到此,我们就可以从哈希表 1 切换到哈希表 2,用增大的哈希表 2 保存更多数据,而原来 的哈希表 1 留作下一次 rehash 扩容备用。

渐进式 rehash

简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求 时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝 到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries。

这样就巧妙地把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操 作,保证了数据的快速访问。

Redis 什么时候做 rehash?

Redis 会使用装载因子(load factor)来判断是否需要做 rehash。装载因子的计算方式 是,哈希表中所有 entry 的个数除以哈希表的哈希桶个数。Redis 会根据装载因子的两种 情况,来触发 rehash 操作:

  • 装载因子≥1,同时,哈希表被允许进行 rehash;
  • 装载因子≥5。

在第一种情况下,如果装载因子等于 1,同时我们假设,所有键值对是平均分布在哈希表 的各个桶中的,那么,此时,哈希表可以不用链式哈希,因为一个哈希桶正好保存了一个 键值对。

但是,如果此时再有新的数据写入,哈希表就要使用链式哈希了,这会对查询性能产生影 响。在进行 RDB 生成和 AOF 重写时,哈希表的 rehash 是被禁止的,这是为了避免对 RDB 和 AOF 重写造成影响。如果此时,Redis 没有在生成 RDB 和重写 AOF,那么,就 可以进行 rehash。否则的话,再有数据写入时,哈希表就要开始使用查询较慢的链式哈希 了。

在第二种情况下,也就是装载因子大于等于 5 时,就表明当前保存的数据量已经远远大于 哈希桶的个数,哈希桶里会有大量的链式哈希存在,性能会受到严重影响,此时,就立马 开始做 rehash。

刚刚说的是触发 rehash 的情况,如果装载因子小于 1,或者装载因子大于 1 但是小于 5, 同时哈希表暂时不被允许进行 rehash(例如,实例正在生成 RDB 或者重写 AOF),此 时,哈希表是不会进行 rehash 操作的。

采用渐进式 hash 时,如果实例暂时没有收到新请求,是不是就不做 rehash 了?

其实不是的。Redis 会执行定时任务,定时任务中就包含了 rehash 操作。所谓的定时任 务,就是按照一定频率(例如每 100ms/ 次)执行的任务。在 rehash 被触发后,即使没有收到新请求,Redis 也会定时执行一次 rehash 操作,而 且,每次执行时长不会超过 1ms,以免对其他任务造成影响。

Redis部署

cluster集群(水平扩展)

主从+哨兵

分布式锁中Redis实现方式

Redisson

红锁算法

缓存相关问题

缓存穿透

缓存雪崩

缓存击穿

数据一致性


Mysql

MVCC原理

索引、什么情况下索引会失效?

Mysql的架构

慢查询定位、优化


Java

集合类:

ArrayList与LinkedList(问了3次)

HashMap(就问了一次)

并发:

知道哪几种同步手段?

synchronized、volatile原理?

几种并发安全的集合类?

cocurrentHashMap原理?


JVM

JVM调优

项目中有没有JVM调优的场景?怎么做的?怎么分析处理的?


开源框架

spring:

事务机制?隔离级别?传播特性?

泛型?(二面领导非常爱问,我被问到了两次)

反射?(二面领导非常爱问,我被问到了两次)

springcloud:

SpringCloud组件?你用过那些?

挑个你熟悉的组件介绍下?(最起码要看过一个组件的原理,我是都说的hystrix)


计算机网络

TCP与UDP区别

。。。太多了记不住,反正我是问到了计算机网络就躺平我不会


部署

docker+k8s


操作系统

linux命令?

怎么查找文件?

怎么查看端口被哪个进程占用?

相关文章:

  • Go语言基础_数据类型、基本语法篇
  • Go学习笔记_环境搭建
  • Markdown学习
  • Markdown下载客户端
  • JDK,JRE,JVM三者的区别
  • 2020-12-01
  • 2020-12-02
  • 比较三个数字,求出最大值
  • Scanner限制次数猜数字
  • ArrayList,随机抽取6个数字在【1-33】中的随机数,并且遍历
  • 利用ArrayList遍历集合
  • 用一个大集合存入20个随机数字,然后筛选其中的偶数元素,放到小集合中
  • String的用法截取,转换,切割
  • 由{1,2,3}转换成[word1#word2#word3#]]
  • 统计大写小写数字和其他字符串
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • es6--symbol
  • JavaScript异步流程控制的前世今生
  • mysql_config not found
  • php面试题 汇集2
  • PV统计优化设计
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • spring security oauth2 password授权模式
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 检测对象或数组
  • 批量截取pdf文件
  • 前端
  • 前端之React实战:创建跨平台的项目架构
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 从如何停掉 Promise 链说起
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​iOS安全加固方法及实现
  • #{}和${}的区别?
  • #数学建模# 线性规划问题的Matlab求解
  • ()、[]、{}、(())、[[]]等各种括号的使用
  • (03)光刻——半导体电路的绘制
  • (3)llvm ir转换过程
  • (pt可视化)利用torch的make_grid进行张量可视化
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (二)WCF的Binding模型
  • (二十三)Flask之高频面试点
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (三)elasticsearch 源码之启动流程分析
  • (四)JPA - JQPL 实现增删改查
  • (小白学Java)Java简介和基本配置
  • (轉貼) 資訊相關科系畢業的學生,未來會是什麼樣子?(Misc)
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • .NET6实现破解Modbus poll点表配置文件
  • .NET值类型变量“活”在哪?
  • /dev/sda2 is mounted; will not make a filesystem here!