Skip to content

Commit

Permalink
面试题整理
Browse files Browse the repository at this point in the history
  • Loading branch information
971230 committed Oct 8, 2024
1 parent e968d12 commit 069b70b
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 71 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
183 changes: 112 additions & 71 deletions docs/开始阅读/面试题/遇到的面试题整理(51-75).md
Original file line number Diff line number Diff line change
Expand Up @@ -6,71 +6,57 @@ description: 放一些自己面试时候遇到的面试题,总结一下

## 51.消息队列(Kafka)的应用场景

传统的消息队列的主要应用场景:**<u>缓存/消峰、解耦和异步通信</u>**
传统的消息队列的主要应用场景:<u>**缓存/消峰、解耦和异步通信**</u>

> 1. **缓冲/消峰**:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
> 2. **解耦**允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束
> 3. **异步通信**允许用户把一个消息放入队列,但并不立即处理它,然后在需要的时候再去处理它们
>
!!! Note ""
1. **缓冲/消峰**有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
2. **解耦**允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束
3. **异步通信**:允许用户把一个消息放入队列,但并不立即处理它,然后在需要的时候再去处理它们

------

## 52.消息队列的两种模式

**1. 点对点模式**:消费者主动拉取数据,消息收到后清除消息
1. **点对点模式**:消费者主动拉取数据,消息收到后清除消息

**2. 发布/订阅模式**
2. **发布/订阅模式**

> - 可以有多个topic主题(`分期信息上送同步流水号至订单系统返回``调用订单系统保存申请编码``请求订单系统获取材料信息``银数影像上送``获取金融网关服务编码`
> - 消费者消费数据之后,不删除数据
> - 每个消费者相互独立,都可以消费到数据
>
- 可以有多个topic主题(`分期信息上送同步流水号至订单系统返回``调用订单系统保存申请编码``请求订单系统获取材料信息``银数影像上送``获取金融网关服务编码`
- 消费者消费数据之后,不删除数据
- 每个消费者相互独立,都可以消费到数据

------

## 53.kafka消息堆积怎么解决

、理论上:当Kafka消息堆积时,可能会导致消费者无法及时处理消息,影响系统的性能和可用性。以下是一些解决堆积问题的常见方法:
1️⃣、理论上:当Kafka消息堆积时,可能会导致消费者无法及时处理消息,影响系统的性能和可用性。以下是一些解决堆积问题的常见方法:

> 1. **增加消费者数量**:可以通过增加消费者的数量来提高消息的处理速度。每个消费者可以并行处理多个分区的消息,从而提高整体的消费能力。
>
> 2. **调整分区数量**:可以根据消息的产生速率和消费能力调整Kafka的分区数量。增加分区数量可以增加消息的并行处理能力,减少单个分区的消息堆积。
>
> 3. **提高消费者的处理能力**:可以优化消费者的处理逻辑,提高消费者的处理能力。例如,使用多线程或多进程来并行处理消息,优化消费者的算法和性能等。
>
> 4. **增加Kafka的吞吐量和性能**:可以通过调整Kafka的配置参数来提高其吞吐量和性能。例如,增加Kafka的副本数量、调整消息的批处理大小、调整Kafka的缓冲区大小等。
>
> 5. **设置适当的消息超时时间**:可以设置适当的消息超时时间,当消息在一定时间内没有被消费者处理时,可以进行重试或其他处理,避免消息长时间堆积。
>
> 6. **监控和报警**:可以设置监控和报警机制,及时发现消息堆积的情况,并采取相应的措施进行处理。 需要根据具体的业务场景和需求来选择合适的解决方法。
>
1. **增加消费者数量**:可以通过增加消费者的数量来提高消息的处理速度。每个消费者可以并行处理多个分区的消息,从而提高整体的消费能力。
2. **调整分区数量**:可以根据消息的产生速率和消费能力调整Kafka的分区数量。增加分区数量可以增加消息的并行处理能力,减少单个分区的消息堆积。
3. **提高消费者的处理能力**:可以优化消费者的处理逻辑,提高消费者的处理能力。例如,使用多线程或多进程来并行处理消息,优化消费者的算法和性能等。
4. **增加Kafka的吞吐量和性能**:可以通过调整Kafka的配置参数来提高其吞吐量和性能。例如,增加Kafka的副本数量、调整消息的批处理大小、调整Kafka的缓冲区大小等。
5. **设置适当的消息超时时间**:可以设置适当的消息超时时间,当消息在一定时间内没有被消费者处理时,可以进行重试或其他处理,避免消息长时间堆积。
6. **监控和报警**:可以设置监控和报警机制,及时发现消息堆积的情况,并采取相应的措施进行处理。 需要根据具体的业务场景和需求来选择合适的解决方法。

同时,还需要综合考虑系统的**硬件资源、网络带宽、数据量**等因素,以及平衡消费者的处理能力和Kafka的吞吐量,以达到合理的消息处理效率。
同时,还需要综合考虑系统的 **硬件资源、网络带宽、数据量** 等因素,以及平衡消费者的处理能力和Kafka的吞吐量,以达到合理的消息处理效率。

、根据生产经验:数据积压(消费者如何提高吞吐量)
2️⃣、根据生产经验:数据积压(消费者如何提高吞吐量)

> 1)如果是Kafka消费能力不足,则可以考虑 **增加Topic的分区数** ,并且同时提升消费组的消费者
>
> 2)如果是下游的数据处理不及时:**提高每批次拉取的数量**。批次拉取数据过少(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。
1. 如果是Kafka消费能力不足,则可以考虑 **增加Topic的分区数** ,并且同时提升消费组的消费者
2. 如果是下游的数据处理不及时:**提高每批次拉取的数量**。批次拉取数据过少(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。

------

## 54.kafka重复消费怎么解决

Kafka的重复消费问题是指消费者在处理消息时,由于一些原因(如消费者故障、网络问题等)导致消息被重复消费。以下是一些解决Kafka重复消费问题的方法:

> 1. **设置消费者的消费位移提交方式(自动提交->手动提交)**:Kafka提供了两种消费位移提交方式,分别是自动提交和手动提交。如果使用自动提交方式,当消费者处理完消息后,Kafka会自动提交消费位移。但如果消费者在处理消息之后发生故障,可能会导致消息被重复消费。因此,建议使用手动提交方式,即消费者在处理完消息后手动提交消费位移,确保消息被正确处理。
>
> 2. **设置消费者的消费位移存储方式**:Kafka支持将消费位移存储在ZooKeeper或者Kafka自身的__consumer_offsets主题中。如果使用ZooKeeper来存储消费位移,可以通过设置合适的ZooKeeper会话超时时间和心跳间隔来减少重复消费的可能性。
>
> 3. **使用幂等消费**:Kafka 0.11版本及以上的版本支持幂等消费,即消费者可以通过设置enable.idempotence=true来确保消息的幂等性。这样即使消费者在处理消息时发生重试,也不会导致消息被重复消费。
>
> 4. **使用唯一标识符进行消息去重**:在消费者处理消息时,可以为每条消息生成一个唯一的标识符,并将该标识符与已消费的消息进行比对,避免重复消费。
>
> 5. **使用消息的业务主键进行去重**:如果消息中包含有业务主键,可以通过在消费者端维护一个已处理的业务主键列表,避免重复消费相同的业务主键对应的消息。
>
> 6. **监控和报警**:可以设置监控和报警机制,及时发现重复消费的情况,并采取相应的措施进行处理。
>
1. **设置消费者的消费位移提交方式(自动提交->手动提交)**:Kafka提供了两种消费位移提交方式,分别是自动提交和手动提交。如果使用自动提交方式,当消费者处理完消息后,Kafka会自动提交消费位移。但如果消费者在处理消息之后发生故障,可能会导致消息被重复消费。因此,建议使用手动提交方式,即消费者在处理完消息后手动提交消费位移,确保消息被正确处理。
2. **设置消费者的消费位移存储方式**:Kafka支持将消费位移存储在ZooKeeper或者Kafka自身的__consumer_offsets主题中。如果使用ZooKeeper来存储消费位移,可以通过设置合适的ZooKeeper会话超时时间和心跳间隔来减少重复消费的可能性。
3. **使用幂等消费**:Kafka 0.11版本及以上的版本支持幂等消费,即消费者可以通过设置enable.idempotence=true来确保消息的幂等性。这样即使消费者在处理消息时发生重试,也不会导致消息被重复消费。
4. **使用唯一标识符进行消息去重**:在消费者处理消息时,可以为每条消息生成一个唯一的标识符,并将该标识符与已消费的消息进行比对,避免重复消费。
5. **使用消息的业务主键进行去重**:如果消息中包含有业务主键,可以通过在消费者端维护一个已处理的业务主键列表,避免重复消费相同的业务主键对应的消息。
6. **监控和报警**:可以设置监控和报警机制,及时发现重复消费的情况,并采取相应的措施进行处理。

需要根据具体的业务场景和需求来选择合适的解决方法,并综合考虑消息处理的幂等性、性能开销等因素。

Expand All @@ -80,16 +66,11 @@ Kafka的重复消费问题是指消费者在处理消息时,由于一些原因

当Kafka消息乱序时,可以采取以下几种方法来解决:

> 1. **使用消息的时间戳**:在消息中添加时间戳,消费者可以根据时间戳来对消息进行排序和处理。这种方法适用于消息的时间戳与消息的顺序相关的场景。
>
> 2. **使用消息的序列号**:在消息中添加序列号,消费者可以根据序列号来对消息进行排序和处理。这种方法适用于消息的序列号与消息的顺序相关的场景。
>
> 3. **使用分区**:将消息分发到不同的分区中,每个分区只能由一个消费者进行消费。这样可以保证每个分区内的消息是有序的,但不同分区之间的消息可能是乱序的。
>
> 4. **使用全局顺序**:将所有消息发送到同一个分区中,这样可以保证所有消息的全局顺序。但是这种方法可能会导致性能瓶颈,因为只有一个消费者可以消费该分区。
>
> 5. **使用有序消息中间件**:使用支持有序消息的中间件,如RocketMQ等。这些中间件可以保证消息的有序性,但是需要引入新的中间件。
>
1. **使用消息的时间戳**:在消息中添加时间戳,消费者可以根据时间戳来对消息进行排序和处理。这种方法适用于消息的时间戳与消息的顺序相关的场景。
2. **使用消息的序列号**:在消息中添加序列号,消费者可以根据序列号来对消息进行排序和处理。这种方法适用于消息的序列号与消息的顺序相关的场景。
3. **使用分区**:将消息分发到不同的分区中,每个分区只能由一个消费者进行消费。这样可以保证每个分区内的消息是有序的,但不同分区之间的消息可能是乱序的。
4. **使用全局顺序**:将所有消息发送到同一个分区中,这样可以保证所有消息的全局顺序。但是这种方法可能会导致性能瓶颈,因为只有一个消费者可以消费该分区。
5. **使用有序消息中间件**:使用支持有序消息的中间件,如RocketMQ等。这些中间件可以保证消息的有序性,但是需要引入新的中间件。

总之,根据具体的业务场景和需求,可以选择适合的方法来解决Kafka消息乱序的问题。

Expand All @@ -111,7 +92,6 @@ Kafka的重复消费问题是指消费者在处理消息时,由于一些原因
- **范围查询高效**:叶子节点之间的链接使得范围查询非常高效。
- **自平衡**:B+树通过分裂和合并节点保持平衡,从而保证了查询效率。


#### InnoDB 存储引擎中的B+树

在 MySQL 的 InnoDB 存储引擎中,B+树主要用于实现各种索引,包括主键索引、唯一索引和普通索引等。
Expand All @@ -138,32 +118,32 @@ Kafka的重复消费问题是指消费者在处理消息时,由于一些原因

#### 1、方案一 互斥锁

互斥锁方案的思路就是如果从redis中没有获取到数据,就让一个线程去数据库查询数据,然后构建缓存,其他的线程就等着,过一段时间后再从redis中去获取。
互斥锁方案的思路就是如果从redis中没有获取到数据,就让一个线程去数据库查询数据,然后构建缓存,其他的线程就等着,过一段时间后再从redis中去获取。

<img src="../img/缓存击穿解决方案之互斥锁.png" style="zoom: 50%;" />

伪代码如下:
伪代码如下:

```java
String get(String ycf) {
String music = redis.get(ycf);
if (music == null) {
//nx的方式设置一个key=ycf_lock,
//value=y_lock的数据,60秒后过期
if (redis.set("ycf_lock", "y_lock","nx",60)) {
//从数据库里获取数据
music = db.query(ycf);
//构建数据,24*60*60s后过期
redis.set(ycf, music,24*60*60);
//构建成功,可以删除互斥锁
redis.delete("ycf_lock");
} else {
//其他线程休息100ms后重试
Thread.sleep(100);
//再次获取数据,如果前面在100ms内设置成功,则有数据
music = redis.get(ycf);
String music = redis.get(ycf);
if (music == null) {
//nx的方式设置一个key=ycf_lock,
//value=y_lock的数据,60秒后过期
if (redis.set("ycf_lock", "y_lock","nx",60)) {
//从数据库里获取数据
music = db.query(ycf);
//构建数据,24*60*60s后过期
redis.set(ycf, music,24*60*60);
//构建成功,可以删除互斥锁
redis.delete("ycf_lock");
} else {
//其他线程休息100ms后重试
Thread.sleep(100);
//再次获取数据,如果前面在100ms内设置成功,则有数据
music = redis.get(ycf);
}
}
}
}
```

Expand Down Expand Up @@ -558,3 +538,64 @@ MySQL 的最左匹配原则是指在<wavy>使用复合索引(即由多个列
<wavy>对于尾插法插入数据,无论是ArrayList还是LinkedList都能够以较高的效率进行插入操作。</wavy>

-----

## 说说对Spring中IOC和AOP的理解

### IOC
`IOC`(Inversion of Control),即“`控制反转`”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

!!! Note ""
**谁控制谁,控制什么**:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;
而<wavy>IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建</wavy>;<br>
**谁控制谁**?当然是IoC 容器控制了对象;**控制什么**?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

**为何是反转,哪些方面反转了**:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;
而反转则是<wavy>由容器来帮忙创建及注入依赖对象,及DI</wavy>;<br>
**为何是反转**?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;**哪些方面反转了**?依赖对象的获取被反转了。

<figure markdown="span">
![Image title](./img/传统应用程序对象创建.png){ width="300" }
<figcaption>1️⃣传统应用程序对象创建</figcaption>
</figure>

<figure markdown="span">
![Image title](./img/IOC-DI容器管理对象.png){ width="300" }
<figcaption>2️⃣IOC-DI容器管理对象</figcaption>
</figure>

`DI`(Dependency Injection),即“`依赖注入`”:组件之间依赖关系由容器在运行期决定,形象的说,<wavy>即由容器动态的将某个依赖关系注入到组件之中</wavy>。
依赖注入的目的并非为软件系统带来更多功能,而是<wavy>为了提升组件重用的频率</wavy>,并为系统搭建一个灵活、可扩展的平台。
通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

!!! Note ""
- **谁依赖于谁**:当然是<wavy>应用程序依赖于IoC容器</wavy>;<br>
- **为什么需要依赖**:<wavy>应用程序需要IoC容器来提供对象需要的外部资源</wavy>;<br>
- **谁注入谁**:很明显是<wavy>IoC容器注入应用程序某个对象</wavy>,应用程序依赖的对象;<br>
- **注入了什么**:就是<wavy>注入某个对象所需要的外部资源</wavy>(包括对象、资源、常量数据)。

[2004年Martin Fowler提出DI概念🔗](https://www.martinfowler.com/articles/injection.html)
[相关链接🔗](https://www.cnblogs.com/xdp-gacl/p/4249939.html)

### AOP
Spring AOP(面向切面编程),就是在<wavy>不改变原有方法的基础上,对方法进行增强</wavy>,添加额外的功能。
可以做 **日志记录****性能监控****异常处理**

#### JDK和Cglib动态代理

**JDK原生**<wavy>需要被代理的目标类必须实现接口</wavy>,因为这个技术要求代理对象和目标对象实现同样的接口。<br>
适用于接口代理,创建代理实例速度较快,适合代理接口的方法调用频率不高的场景。

!!! Note ""
**优点**:实现简单,使用Java内置API;无需依赖第三方库。
**缺点**:只能代理接口,不能代理普通类;方法调用时使用反射,性能相对较低。

**cglib动态代理**<wavy>通过继承被代理的目标类实现代理</wavy>,所以不需要目标类实现接口。<br>
适用于没有接口的类代理,方法调用性能较高,适合方法调用频率较高的场景。

!!! Note ""
**优点**:可以代理没有接口的类;方法调用性能较高,避免了反射调用。
**缺点**:创建代理类时需要进行字节码操作,性能开销较大;需要依赖cglib和ASM库。

其中<wavy>JDK动态代理虽启动快,但在创建代理实例时的性能优于Cglib;而Cglib启动慢,但在方法调用时,Cglib的性能则优于JDK动态代理</wavy>。

0 comments on commit 069b70b

Please sign in to comment.