0114java面经
1,为什么项目用了API网关?
在项目中使用 API 网关主要有以下几个方面的原因:
统一入口与流量管理
- 统一接入:API 网关为所有的微服务提供了一个统一的入口点。外部客户端只需要与 API 网关进行交互,而不需要了解各个微服务的具体地址和接口细节。这样可以简化客户端的调用逻辑,提高系统的可维护性。
- 流量控制与限流:API 网关可以对进入系统的流量进行精确控制。通过设置限流规则,如每秒允许的最大请求数、并发连接数等,防止系统因流量过大而导致崩溃,确保系统的稳定性和可用性。
安全防护
- 认证与授权:API 网关可以集中处理认证和授权逻辑。它可以验证客户端的身份信息,如令牌、证书等,并根据用户的角色和权限来决定是否允许访问相应的微服务。这样可以将安全逻辑从各个微服务中分离出来,提高系统的安全性和可管理性。
- 防止恶意攻击:API 网关可以作为系统的第一道防线,对外部请求进行过滤和检测,防止恶意攻击,如 SQL 注入、XSS 攻击等。它可以通过配置防火墙规则、WAF(Web 应用防火墙)等功能来保护后端微服务的安全。
服务治理与监控
- 服务路由与负载均衡:API 网关可以根据请求的特征,如请求路径、请求参数等,将请求路由到相应的微服务实例上。同时,它还可以实现负载均衡功能,将请求均匀地分发到多个微服务实例上,提高系统的性能和可扩展性。
- 监控与日志:API 网关可以对所有的请求和响应进行监控和记录,收集关键指标数据,如请求响应时间、请求成功率、流量等。这些数据可以帮助运维人员及时了解系统的运行状态,发现潜在的问题,并进行性能优化。
协议转换与数据处理
- 协议转换:不同的微服务可能使用不同的协议进行通信,如 HTTP、gRPC 等。API 网关可以作为协议转换的桥梁,将外部请求的协议转换为微服务所支持的协议,实现不同协议之间的互联互通。
- 数据处理与缓存:API 网关可以对请求和响应数据进行处理,如数据格式转换、数据加密和解密等。此外,它还可以设置缓存策略,对一些常用的数据进行缓存,减少后端微服务的压力,提高响应速度。
版本管理与灰度发布
- 版本管理:随着业务的发展,微服务的接口可能会不断升级和变化。API 网关可以方便地管理不同版本的 API,允许客户端根据自己的需求选择使用不同版本的接口。这样可以实现新旧版本的兼容过渡,避免对现有客户端造成影响。
- 灰度发布:API 网关可以支持灰度发布功能,即逐步将新版本的微服务推向生产环境,让一部分用户先体验新版本的功能,同时监控系统的运行状态。如果发现问题,可以及时回滚,降低发布风险。
2,Redis 基本数据结构及其底层实现是什么?
Redis 是一种高性能的内存数据库,支持多种数据结构,以下是 Redis 基本数据结构及其底层实现:
字符串(String)
- 基本介绍:字符串是 Redis 最基本的数据结构,它可以存储任何类型的数据,如整数、浮点数、字符串等。
- 底层实现:Redis 的字符串是基于动态字符串(SDS)实现的。SDS 是一种类似于 C 语言中字符串的数据结构,但它克服了 C 字符串的一些缺点,如获取长度的时间复杂度为等。SDS 在结构中记录了字符串的长度和剩余空间,使得获取长度的操作时间复杂度为,并且在进行字符串修改时,能够自动进行内存分配和释放,避免了缓冲区溢出等问题。
列表(List)
- 基本介绍:列表是一个有序的字符串元素集合,它按照插入顺序排列元素,可以在列表的头部或尾部进行插入和删除操作。
- 底层实现:列表的底层实现是双向链表和压缩列表。当列表中的元素较少且每个元素的长度较短时,Redis 会使用压缩列表来存储列表数据,以节省内存空间。压缩列表是一种连续的内存空间,它将多个元素紧凑地存储在一起,通过特殊的编码方式来表示元素的长度和内容。当列表中的元素较多或元素长度较大时,Redis 会将列表转换为双向链表来存储,双向链表的每个节点包含指向前一个节点和后一个节点的指针,方便在列表的头部和尾部进行插入和删除操作。
哈希(Hash)
- 基本介绍:哈希是一个键值对集合,类似于编程语言中的字典或关联数组。它可以将一个字符串类型的键映射到一个字符串类型的值,常用于存储对象的属性。
- 底层实现:哈希的底层实现是哈希表和压缩列表。当哈希中的元素较少且每个元素的键和值的长度较短时,Redis 会使用压缩列表来存储哈希数据。当哈希中的元素较多或元素的键和值长度较大时,Redis 会使用哈希表来存储。哈希表是通过哈希函数将键映射到一个固定大小的数组中,通过解决哈希冲突来保证数据的正确性。
集合(Set)
- 基本介绍:集合是一个无序的字符串元素集合,它不允许重复的元素存在。集合支持交集、并集、差集等操作。
- 底层实现:集合的底层实现是哈希表和整数集合。当集合中的元素都是整数且元素数量较少时,Redis 会使用整数集合来存储集合数据。整数集合是一种紧凑的数组结构,它根据元素的类型动态调整数组的大小,以节省内存空间。当集合中的元素不是整数或元素数量较多时,Redis 会使用哈希表来存储集合数据,哈希表的键用于存储集合中的元素,值可以为空或者存储一些额外的信息。
有序集合(Sorted Set)
- 基本介绍:有序集合是一个有序的字符串元素集合,每个元素都关联一个分数(score),通过分数来对元素进行排序。有序集合常用于排行榜等场景。
- 底层实现:有序集合的底层实现是跳跃表和哈希表。跳跃表是一种特殊的数据结构,它通过在每个节点上维护多个指针,使得在查找元素时可以快速跳过一些节点,从而提高查找效率。哈希表用于存储元素和分数的映射关系,以便快速根据元素获取分数。
3,TCP和UDP的区别是什么?
TCP(传输控制协议)和 UDP(用户数据报协议)是两种常用的传输层协议,它们之间的区别如下:
连接特性
- TCP:是面向连接的协议,在数据传输之前,需要先建立连接,然后再进行数据传输,传输完成后还需要释放连接。就像打电话一样,通话双方需要先拨通号码建立连接,通话结束后再挂断电话释放连接。
- UDP:是无连接的协议,发送数据时不需要事先建立连接,直接将数据报发送出去即可。类似于写信,写信人不需要事先通知收信人,直接将信件投入邮箱即可。
可靠性
- TCP:提供可靠的传输服务,通过序列号、确认应答、重传机制等保证数据的准确传输。如果发送方发送的数据在传输过程中丢失或损坏,接收方会要求发送方重新发送,直到数据正确到达。
- UDP:不保证数据的可靠传输,它只是将数据报发送出去,不关心数据是否能够正确到达接收方。如果数据在传输过程中丢失或损坏,UDP 不会进行重传。
数据传输效率
- TCP:由于需要建立连接、确认应答、重传等机制,所以传输效率相对较低。在传输大量数据时,可能会有一定的延迟。
- UDP:不需要建立连接和进行复杂的确认机制,数据传输速度快,效率高。但是由于不保证可靠性,可能会出现数据丢失的情况。
数据传输顺序
- TCP:保证数据按照发送的顺序到达接收方,通过序列号和确认应答机制来实现数据的有序传输。
- UDP:不保证数据的传输顺序,数据报可能会因为网络拥塞、路由变化等原因而乱序到达接收方。
应用场景
- TCP:适用于对数据可靠性要求高的场景,如文件传输、电子邮件、远程登录等。
- UDP:适用于对实时性要求高、对数据准确性要求相对较低的场景,如视频直播、音频通话、在线游戏等。
首部开销
- TCP:首部长度一般为 20 字节,包含了源端口、目的端口、序列号、确认号、窗口大小等多个字段,用于实现可靠传输和流量控制等功能。
- UDP:首部长度固定为 8 字节,只包含源端口、目的端口、长度和校验和四个字段,开销较小。
4,GET 和 POST的区别是什么?
GET 和 POST 是 HTTP 协议中常用的两种请求方法,它们有以下一些主要区别:
数据传输方式
- GET:通过 URL 传递参数,参数会附加在 URL 后面,以问号 “?” 分隔,多个参数之间用 “&” 连接。例如:
https://example.com/api?param1=value1¶m2=value2
。这种方式使得参数在 URL 中是可见的,因此不适合传递敏感信息。 - POST:将数据放在请求体中进行传输,参数不会显示在 URL 中,相对来说更加安全,适合传递大量的数据和敏感信息。
数据大小限制
- GET:由于 URL 的长度有限制,不同的浏览器和服务器对 URL 长度的限制可能不同,一般来说 URL 的长度限制在 2048 个字符左右。因此,GET 方法传递的数据量不能太大。
- POST:理论上对数据大小没有限制,主要受服务器的配置和内存限制。所以可以用于传递大量的数据,如文件上传等。
安全性
- GET:因为参数在 URL 中可见,所以安全性较低。如果传递敏感信息,如用户名、密码等,可能会被他人窃取。
- POST:数据在请求体中,不会直接显示在 URL 中,相对来说更加安全。但这并不意味着 POST 方法就绝对安全,还需要结合其他安全措施来保护数据。
幂等性
- GET:是幂等的,多次请求相同的 URL,得到的结果应该是相同的,不会对服务器资源产生副作用。例如,多次查询同一篇文章的内容,结果应该是一样的。
- POST:通常不是幂等的,多次提交相同的 POST 请求可能会导致不同的结果,比如多次提交订单可能会产生多个订单。
缓存性
- GET:请求的结果可以被缓存,浏览器可以根据缓存策略缓存 GET 请求的响应结果,下次请求相同的 URL 时可以直接从缓存中获取数据,提高访问速度。
- POST:一般情况下,POST 请求的结果不会被缓存,因为 POST 请求通常用于提交数据,每次请求的结果可能不同。
5,HTTPS的底层实现原理是什么?
HTTPS(超文本传输安全协议)的底层实现原理主要涉及 SSL/TLS 协议以及加密技术,以下是其详细过程:
建立连接
- 客户端发起请求:客户端向服务器发送一个 HTTPS 请求,该请求包含客户端支持的 SSL/TLS 版本、加密算法、压缩算法等信息。
- 服务器响应:服务器收到请求后,选择一种双方都支持的 SSL/TLS 版本和加密算法,并向客户端发送服务器的数字证书,证书中包含服务器的公钥、证书颁发机构(CA)的信息等。
身份验证
- 客户端验证证书:客户端收到服务器的证书后,会使用内置的根证书对服务器证书进行验证。验证内容包括证书是否由受信任的 CA 颁发、证书是否过期、证书中的域名是否与请求的域名一致等。如果证书验证通过,客户端会从证书中提取出服务器的公钥。
密钥协商
- 生成随机数:客户端生成一个随机数,并用服务器的公钥对其加密,然后发送给服务器。这个随机数将用于后续的对称加密密钥的生成。
- 服务器解密:服务器使用自己的私钥解密客户端发送的加密随机数,得到原始的随机数。
数据加密传输
- 生成对称密钥:客户端和服务器根据之前协商的加密算法和共享的随机数,生成对称加密密钥。对称加密算法使用相同的密钥进行加密和解密,速度较快,适合大量数据的加密传输。
- 加密传输:客户端使用生成的对称密钥对要发送的数据进行加密,然后将加密后的数据发送给服务器。服务器接收到加密数据后,使用相同的对称密钥进行解密,得到原始数据。
连接关闭
- 数据传输完成后:客户端和服务器会按照一定的步骤关闭连接,释放相关资源
6,打开网页时,浏览器的具体工作流程是怎么样的?
打开网页时,浏览器的具体工作流程如下:
URL 解析
用户在浏览器地址栏中输入网址并按下回车键后,浏览器首先会对输入的 URL 进行解析,确定协议(如 HTTP、HTTPS)、主机名、端口号(如果有)、路径、查询参数等。
DNS 解析
浏览器需要将主机名转换为服务器的 IP 地址,这个过程称为 DNS 解析,通常包括以下步骤:
- 浏览器首先检查本地 DNS 缓存,看看是否有对应的 IP 地址。
- 如果本地缓存中没有,浏览器会向操作系统查询。
- 操作系统会检查自己的缓存,并可能向本地的 DNS 服务器发出请求。
- 本地 DNS 服务器可能会递归查询其他 DNS 服务器,直到找到对应的 IP 地址。
建立连接
一旦获得了 IP 地址,浏览器会通过 TCP/IP 协议与服务器建立连接。对于 HTTPS,浏览器还会进行 SSL/TLS 握手,以建立安全连接。
发送请求
连接建立后,浏览器会构建一个 HTTP 请求并发送给服务器。请求包括请求行(如GET /index.html HTTP/1.1
)、请求头(如 User-Agent、Accept 等)以及可能的请求体(对于 POST 请求)。
服务器处理请求
服务器接收到请求后,会根据请求的内容进行处理:
- 服务器解析请求,确定所需的资源(如 HTML 文件、图片、数据等)。
- 服务器可能需要与后端数据库或其他服务进行交互,以生成响应内容。
- 服务器构建 HTTP 响应,包括状态行(如
HTTP/1.1 200 OK
)、响应头(如 Content-Type、Content-Length 等)和响应体(实际的页面内容)。
接收响应
浏览器接收到服务器的响应后,会根据响应头的信息处理响应体:
- 如果响应是重定向(如 301 或 302),浏览器会根据 Location 头再次发起请求。
- 如果响应包含压缩内容(如 gzip),浏览器会解压缩。
- 浏览器会根据 Content-Type 头确定如何处理响应体(如 HTML、CSS、JavaScript、图片等)。
解析和加载资源
- 解析 HTML:浏览器开始解析 HTML 文档,构建 DOM 树。解析过程中,浏览器会处理各种 HTML 标签,并根据需要发起其他请求(如 CSS、JavaScript、图片等)2。
- 加载和执行资源
- CSS:浏览器解析 CSS 文件并构建 CSSOM 树,与 DOM 树结合形成渲染树。
- JavaScript:浏览器解析和执行 JavaScript 代码,可能会修改 DOM 树或 CSSOM 树。
- 图片和其他资源:浏览器会异步加载这些资源,并在加载完成后进行渲染。
渲染页面
浏览器根据渲染树计算每个元素的布局(位置和大小),并将页面绘制到屏幕上。这个过程可能会涉及多次重绘和重排(reflow/repaint),尤其是在 JavaScript 修改 DOM 或 CSS 的情况下2。
用户交互
页面加载完成后,用户可以与页面进行交互。浏览器会响应用户的操作(如点击、输入等),并可能通过 JavaScript 动态更新页面内容。
7,TCP 如何保证可靠性?
TCP 通过以下多种机制来保证数据传输的可靠性:
连接管理
- 三次握手:在正式传输数据之前,客户端和服务器通过三次握手来建立可靠的连接。客户端发送 SYN 报文,服务端收到后发送 SYN+ACK 报文,客户端再发送 ACK 报文,双方确认连接建立。
- 四次挥手:数据传输结束后,通过四次挥手来释放连接,确保数据传输的完整性和有序性2。
序列号与确认应答
- 序列号:TCP 给发送的每一个字节数据都进行编号,接收方可以根据序列号对收到的数据进行排序,确保数据的顺序性,即使网络中出现乱序现象也能正确重组原始信息3。
- 确认应答:接收方收到数据后,会向发送方发送确认应答(ACK)信号,告知发送方已经成功接收数据,并说明下一次期望接收的数据的序列号。如果发送方在一定时间内没有收到确认应答,就会认为数据传输出现问题,进而触发重传机制123。
重传机制
- 超时重传:发送方发送数据后,会启动一个定时器,如果在超时时间内没有收到接收方的确认应答,就会重新发送数据。超时时间一般是 2 倍的报文往返时间(RRT)加上一个偏差值13。
- 快速重传:如果接收方连续收到多个重复的确认应答,就会认为中间的数据段丢失了,发送方在收到一定数量(通常是 3 个)的相同确认应答后,会立即重传丢失的数据段,而不需要等待超时时间到期1。
流量控制
TCP 连接的双方都有固定大小的缓冲空间,接收方通过滑动窗口机制来通知发送方自己的接收能力。接收方只允许发送方发送自己缓冲区能接纳的数据。当接收方来不及处理发送方的数据时,会提示发送方降低发送速率,防止丢包34。
拥塞控制
当网络出现拥堵时,TCP 会通过拥塞控制机制来避免进一步加剧网络拥塞。具体包括慢开始、拥塞避免、快重传和快恢复等算法。发送方根据网络的拥塞情况动态调整发送速率,以确保数据能够在网络中顺利传输23。
校验和
TCP 会对首部和数据计算校验和,这是一个端到端的校验和,目的是检测数据在传输过程中的任何变化。如果接收端计算出的校验和与接收到的校验和不一致,则说明数据在传输过程中发生了错误,接收方会丢弃这个报文段并不确认收到该报文段34。
8,Java内存模型是什么?为什么会有并发问题?
Java 内存模型(Java Memory Model,JMM)是 Java 虚拟机(JVM)中定义的一种抽象的内存架构,用于规范 Java 程序在多线程环境下的内存访问行为。它定义了线程和主内存之间的交互规则,以及不同线程之间如何通过主内存进行数据共享和通信。
Java 内存模型的结构
- 主内存:所有线程共享的内存区域,存储了 Java 对象的实例变量、类变量等数据。
- 工作内存:每个线程都有自己独立的工作内存,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。线程工作内存中保存了该线程使用到的变量的主内存副本拷贝。
Java 内存模型中的操作
- read(读取):将主内存中的变量值读取到工作内存中。
- load(加载):将 read 操作读取到的变量值放入工作内存中的变量副本中。
- use(使用):将工作内存中变量副本的值传递给执行引擎,供程序使用。
- assign(赋值):将执行引擎接收到的值赋给工作内存中的变量副本。
- store(存储):将工作内存中变量副本的值存储到主内存中。
- write(写入):将 store 操作存储的值写入主内存中的变量。
导致并发问题的原因
- 可见性问题:当多个线程访问共享变量时,一个线程修改了共享变量的值,其他线程可能无法立即看到这个修改。这是因为线程在工作内存中操作变量副本,而不是直接操作主内存中的变量。只有当线程将工作内存中的变量副本的值写回主内存时,其他线程才能看到这个修改。
- 原子性问题:在 Java 中,有些操作看似是原子的,但实际上在多线程环境下可能不是原子的。例如,对一个整数变量的自增操作(i++),它实际上包含了读取、修改和写入三个步骤,在多线程环境下,可能会出现一个线程在读取变量值后,还未进行写入操作时,另一个线程也进行了读取操作,导致最终结果与预期不符。
- 有序性问题:Java 编译器和处理器为了提高程序的执行效率,可能会对指令进行重排序。在单线程环境下,指令重排序不会影响程序的执行结果,但在多线程环境下,可能会导致程序出现错误。例如,一个线程先对一个共享变量进行赋值操作,然后再对另一个共享变量进行赋值操作,但在执行时,这两个操作的顺序可能会被重排序,导致其他线程看到的结果与预期不符。
9,Java 中 volatile 关键字的作用是什么?
在 Java 中,volatile
关键字主要有以下两个作用:
保证变量的可见性
当一个变量被声明为volatile
时,任何线程对该变量的修改都会立即同步到主内存中,其他线程在读取该变量时,会直接从主内存中获取最新的值,而不是使用自己工作内存中的缓存副本。这就确保了多个线程之间对该变量的操作具有可见性,避免了由于线程缓存导致的可见性问题。例如:
public class VolatileExample {
// 声明一个volatile变量
private volatile int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
在上述代码中,count
变量被声明为volatile
,当一个线程调用increment
方法修改count
的值时,其他线程能够立即看到这个修改。
禁止指令重排序
Java 编译器和处理器为了提高程序的执行效率,可能会对指令进行重排序。但是,对于volatile
变量,会禁止某些特定的指令重排序优化,从而保证程序的执行顺序与代码的编写顺序一致。例如:
public class VolatileReorderExample {
private volatile boolean flag = false;
private int data = 0;
public void write() {
data = 10;
// 当执行到这里时,保证前面的data=10已经执行完成,不会被重排序到flag=true之后
flag = true;
}
public void read() {
if (flag) {
// 这里可以保证读取到的data是最新的值10
System.out.println(data);
}
}
}
在上述代码中,flag
变量被声明为volatile
,这就保证了在write
方法中,data = 10
的赋值操作不会被重排序到flag = true
之后。因此,在read
方法中,当flag
为true
时,能够保证读取到的data
是最新的值10
。
10,MySQL最左前缀匹配原则是什么?其底层实现原理是什么?
MySQL 最左前缀匹配原则是指在使用联合索引时,查询条件只有从索引的最左边开始连续使用索引列,才能充分利用索引的优化效果。例如,有一个联合索引(a, b, c)
,那么以下查询可以使用该索引:
SELECT * FROM table WHERE a = value1 AND b = value2 AND c = value3;
SELECT * FROM table WHERE a = value1 AND b = value2;
SELECT * FROM table WHERE a = value1;
而以下查询不能完全使用该索引:
SELECT * FROM table WHERE b = value2 AND c = value3;
SELECT * FROM table WHERE c = value3;
其底层实现原理与 B + 树索引的数据结构有关。B + 树是一种多路平衡查找树,MySQL 中的索引通常采用 B + 树结构来存储。在 B + 树中,节点中的数据是按照索引列的值进行排序的。对于联合索引(a, b, c)
,B + 树首先按照a
列的值进行排序,当a
列的值相同时,再按照b
列的值进行排序,以此类推。
当执行查询时,如果查询条件从最左边的列开始,即按照a
、b
、c
的顺序使用索引列,MySQL 可以利用 B + 树的排序特性,快速定位到满足条件的数据。例如,对于查询SELECT * FROM table WHERE a = value1 AND b = value2 AND c = value3;
,MySQL 首先在 B + 树中找到a = value1
的节点,然后在这些节点中继续查找b = value2
的节点,最后在这些节点中查找c = value3
的节点,从而快速定位到满足条件的数据。
如果查询条件不是从最左边的列开始,例如SELECT * FROM table WHERE b = value2 AND c = value3;
,MySQL 无法直接利用 B + 树的排序特性快速定位到满足条件的数据,需要进行全表扫描或者部分索引扫描,导致查询效率降低。
原文地址:https://blog.csdn.net/weixin_62941961/article/details/145131677
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!