还有好几个实在想不起来了 /(ㄒoㄒ)/~~
openfeign 的执行原理
- 首先通过@EnableFeignClients注解后会导入FeignClientsRegistrar这个类,类中会通过一个方法registerFeignClients扫描所有的定义了@FeignClients接口获取到@FeignClients中定义的属性,并且生成代理类。
- 在接口方法上,Feign 会通过SpringMvcContract类中的processAnnotationOnClass方法解析如
@RequestMapping、@PathVariable、@RequestParam等注解,以构建实际的 HTTP 请求。 - 发送HTTP请求,如果什么都没配置的话默认使用Apache HttpClient,也可以配置成OKhttp。
- 响应处理 :接收到响应后,Feign 会解析返回的数据,并根据方法的返回类型进行反序列化。如果请求失败,Feign 会根据配置的错误解码器处理异常情况。
你们openfeign用的长链接还是短链接
openfeign默认使用的短链接。即当请求完毕后就关闭此次链接,下次请求再开启。
这里也说一下长短链接的优缺点
使用长连接和短连接各有其优缺点,适用的场景也有所不同。
短连接
优点:
- 简单性:每个请求后自动关闭连接,减少了连接管理的复杂性。
- 资源释放:每次请求后,系统会释放资源,减少潜在的资源泄露风险。
- 防止闲置:避免了长时间闲置连接带来的资源浪费。
缺点:
- 性能损失:频繁的建立和关闭连接会增加延迟,降低性能。
- 开销:每个请求都需要进行 TCP 握手,增加网络开销。
场景示例:
- 低频请求:例如一个用户信息查询接口,用户并不频繁访问,使用短连接可以减少资源占用。
长连接
优点:
- 提高性能:连接可以重用,减少了 TCP 握手的频率,降低延迟。
- 适合高频请求:在高频交互场景中,重用连接可以显著提高系统吞吐量。
缺点:
- 资源占用:连接长时间保持可能导致资源泄露,尤其是未被正确关闭的情况下。
- 管理复杂性:需要对连接的生命周期进行管理,增加了实现的复杂性。
场景示例:
- 实时通信:如聊天应用或实时数据推送,频繁的请求和响应需要使用长连接以保持低延迟和高效的交互。
在我们系统中使用长链接还是短链接具体还要根据业务场景来决定,如果我们服务之间的信息传递非常频繁,使用长链接可以有效降低建立连接的开销,提高性能。
如果交互并不频繁可以选用短链接,能够自动管理连接状态,也能保证安全性。
如果一个其他系统想要我们系统认证才能启动,请设计
这个问题当时问的时候,说了一大堆什么双向认证或者什么Token啦、JWT啦,面后想了想面试官根本不是想问这个,不然也不会突然从Spirng的问答中跳出问这个,而且题目也说了被认证系统连启动都不行。我在看Spring官网的时候,看到了事件这一章才明白这个题的答案。
可以通过实现BootstrapRegistryInitializer接口,在实现类中进行认证,如果认证无法通过则项目无法启动,这个题的核心目的还是Spring的核心内容——事件监听。
@Configuration
public class CustomBootstrapInitializer implements BootstrapRegistryInitializer {
@Override
public void initialize(BootstrapRegistry bootstrapRegistry) {
// 实现你的认证逻辑,可以是从配置文件中读取密钥或者其他方式
boolean isAuthenticated = performAuthentication();
if (!isAuthenticated) {
throw new RuntimeException("Authentication failed!");
}
}
private boolean performAuthentication() {
// 你的认证代码
return true; // 或 false 取决于认证结果
}
}
关于BootstrapRegistryInitializer这个类代表感知引导初始化监听器,这个监听器会在bootstrapContext加载后触发,系统中环境、上下文等都是在这之后加载的。
如果每个字段都加上索引会怎么样?
在数据库中为每个字段都加上索引可能会带来一些严重的弊端,尽管索引可以提高某些查询的性能,但过多的索引也会导致负面影响。以下是详细的弊端分析:
写操作性能下降
索引的存在意味着每当有数据插入、更新或删除时,数据库不仅需要修改数据本身,还需要相应地维护所有的索引。
- 插入操作:每次插入新数据时,数据库不仅要把数据写入表中,还要在相关索引中插入相应的条目。如果每个字段都有索引,插入操作就会变得非常缓慢。
- 更新操作:如果更新涉及到被索引的字段,数据库需要重新调整索引中的条目,维护索引的有序性。这会增加更新操作的复杂性和时间开销。
- 删除操作:删除操作同样需要维护索引中的记录,也会降低性能。
存储空间的消耗
每个索引都占用额外的存储空间。为每个字段都创建索引会显著增加数据库的存储需求,特别是在大表中,这种存储空间的开销会非常显著。索引越多,占用的磁盘空间越大。
- 索引文件大小:数据库中的每个索引通常会生成独立的文件或结构来存储,这些文件会占用磁盘资源,导致存储成本增加。
- 内存开销:数据库在查询时需要将索引加载到内存中以加速检索操作。大量的索引会占用更多的内存资源,从而降低数据库的整体性能。
查询优化器的困惑
现代数据库系统使用查询优化器来确定最优的查询执行计划。然而,如果表中存在过多的索引,优化器可能会花费更多的时间来分析和选择适合的索引,甚至可能做出错误的选择。
- 过度选择:优化器需要为每个查询确定最优的索引路径。过多的索引可能会导致优化器在选择最佳索引时遇到困难,从而选择不适合的索引路径,导致查询性能下降。
- 索引冲突:多个索引可能会彼此竞争,特别是在多列查询时,数据库可能无法明确选择最佳的组合索引,导致不必要的全表扫描或非最优查询路径。
维护与管理复杂性
索引需要定期进行维护,比如重建或重组,尤其是当索引碎片化严重时(插入、删除操作频繁导致的碎片)。当为每个字段都创建索引时,管理这些索引将变得非常复杂和耗时。
- 定期维护:大量的索引需要频繁的维护操作,这会占用更多的资源并增加管理负担。
- 性能监控:管理员需要定期检查哪些索引是有效的,哪些是冗余的或者未被使用的。索引的过度增加会使得这个监控过程更加繁琐。
冗余索引的可能性
如果为每个字段都加上索引,许多索引可能在实际使用中是冗余的。例如,假设表中有复合索引(多列索引),单独为每列再创建索引是没有必要的,因为复合索引已经可以满足多个字段的查询需求。
- 重复性:一些复合查询可能已经被现有的组合索引覆盖,再为单独的列加索引会变得冗余,既占用存储空间又影响性能。
- 无用索引:某些字段的索引可能在实际查询中几乎从未使用,这样的索引不仅浪费空间,还拖慢写操作的性能。
优化并不是线性递增的
并不是每个字段的索引都会带来显著的性能提升。某些字段的索引在查询中可能很少使用,甚至有些字段的数据特性(例如高重复性或低选择性)使得索引的效果微乎其微。
- 低选择性字段的索引:如果字段的取值范围非常小(如性别字段,只有“男”和“女”两种可能值),为这些字段加索引可能不会带来显著的性能提升,反而可能增加额外的负担。
锁竞争增加
当数据库执行并发操作时,过多的索引会增加锁的竞争。在写入数据时,数据库需要锁住相关的索引条目,确保数据一致性。索引越多,锁竞争的可能性越大,尤其在高并发环境下,这会导致整体性能的下降。
一个文件每一行都是一个整形的数字,从这个文件中找出最大的 10 数字,怎么做
可以使用最小堆来做。
- 初始化最小堆 :首先,你创建一个最小堆,并初始化为空。这个堆将用于存储当前找到的最大的10个数字。
- 逐个读取文件中的数字 :你逐个读取文件中的数字,并将每个数字与最小堆的根节点(即当前堆中最小的数字)进行比较。
- 维护堆的大小 :如果当前数字大于堆的根节点,则将根节点替换为当前数字,并重新调整堆以保持最小堆的性质。如果当前数字小于或等于根节点,则忽略该数字。
- 重复直到文件结束 :重复上述过程,直到你读取完文件中的所有数字。此时,最小堆中将包含文件中最大的10个数字。
具体步骤
- 初始化最小堆 :创建一个大小为10的最小堆。
- 逐个读取数字 :
- 如果堆未满(即堆中的元素数量小于10),直接将当前数字插入堆中,并调整堆。
- 如果堆已满,将当前数字与堆的根节点进行比较:
- 如果当前数字大于根节点,则替换根节点,并调整堆。
- 如果当前数字小于或等于根节点,则忽略该数字。
- 调整堆 :每次替换根节点后,通过下沉操作重新调整堆,确保堆的性质(即每个父节点都小于其子节点)得以保持。
- 完成读取 :当文件中的所有数字都被读取并处理后,最小堆中将包含文件中最大的10个数字。
import java.util.Arrays;
public class MinHeap {
private int[] heap;
private int size;
private int capacity;
public MinHeap(int capacity) {
this.capacity = capacity;
this.heap = new int[capacity];
this.size = 0;
}
private int parent(int index) {
return (index - 1) / 2;
}
private int leftChild(int index) {
return 2 * index + 1;
}
private int rightChild(int index) {
return 2 * index + 2;
}
private void swap(int i, int j) {
int temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
public void insert(int value) {
if (size == capacity) {
if (value > heap[0]) {
heap[0] = value;
heapifyDown(0);
}
} else {
heap[size] = value;
size++;
heapifyUp(size - 1);
}
}
private void heapifyUp(int index) {
while (index > 0 && heap[index] < heap[parent(index)]) {
swap(index, parent(index));
index = parent(index);
}
}
private void heapifyDown(int index) {
int smallest = index;
int left = leftChild(index);
int right = rightChild(index);
if (left < size && heap[left] < heap[smallest]) {
smallest = left;
}
if (right < size && heap[right] < heap[smallest]) {
smallest = right;
}
if (smallest != index) {
swap(index, smallest);
heapifyDown(smallest);
}
}
public int[] getTopK() {
int[] result = Arrays.copyOf(heap, size);
Arrays.sort(result);
return result;
}
}
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TopKNumbers {
public static void main(String[] args) {
String filePath = "numbers.txt"; // 文件路径
int k = 10; // 找出最大的10个数字
MinHeap minHeap = new MinHeap(k);
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = br.readLine()) != null) {
int number = Integer.parseInt(line);
minHeap.insert(number);
}
} catch (IOException e) {
e.printStackTrace();
}
int[] topK = minHeap.getTopK();
System.out.println("最大的 " + k + " 个数字是:");
for (int num : topK) {
System.out.println(num);
}
}
}