一、实习
1、健康险核心 batch 自动查询和一键重启
2、后端如何实现免密登录
-
Spring Boot与Spring Security:
- 如果你使用的是Spring框架,Spring Security可以为你提供大量的安全功能。
- 创建一个基于Spring Boot的新项目,并添加Spring Security依赖。
-
使用JWT:
- 使用JSON Web Tokens (JWT)为用户生成令牌。这些令牌在第一次验证后,可以为用户提供有限的访问时间,而不需要再次输入密码。
- 为Spring Security配置一个JWT过滤器,用于检查和验证请求头中的令牌。
-
存储和管理令牌:
- 当用户首次登录时,生成一个JWT并将其发送给客户端。
- 客户端应将此令牌保存在cookie或localStorage中,并在随后的每次请求中附带它。
- 服务器端应验证这个令牌,以确定用户是否已经验证,并且令牌是否仍然有效。
-
安全性:
- 使用HTTPS来加密客户端和服务器之间的所有通信。
- 定期旋转你的JWT签名密钥以增加安全性。
- 将令牌的有效期设置为相对较短,例如15分钟或1小时,但提供刷新机制。
-
日志和监控:
- 使用Java的日志库(如SLF4J或Logback)记录所有的登录尝试、令牌生成和验证失败。
- 这将帮助你在发生安全事件时进行调查和审计。
3、JWT登录流程?包含哪些结构?摘要算法用的是什么?
-
JWT登录流程:
- 用户登录:用户使用他们的凭据(如用户名和密码)来请求访问。
- 验证凭据:服务器验证用户提交的凭据。如果凭据是有效的,服务器将生成一个JWT。
- 生成JWT:一旦验证成功,服务器将使用密钥(通常只有服务器知道)来生成JWT,并将其发送回客户端。
- 客户端存储JWT:客户端接收JWT并存储在本地,常见的存储位置是Cookie或LocalStorage。
- 发送请求携带JWT:之后,每当客户端向服务器发送请求(尤其是对受保护资源的请求),它都会在其请求头中附带JWT。
- 服务器验证JWT:服务器接收到请求后,会验证请求头中的JWT。如果JWT有效,服务器会处理该请求;如果无效,服务器可能会返回一个错误。
- 到期/注销:JWT有一个到期时间,过了这个时间JWT就会失效。此外,如果用户注销,客户端通常会丢弃保存的JWT。
-
JWT结构包括三部分:
-
头(Header):它通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,如HMAC SHA256或RSA。
-
有效载荷(Payload):包含声明,这是关于实体(通常是用户)以及其他一些元数据的语句。这些声明被称为“claims”。
-
签名(Signature):为了获得签名部分,您必须获取编码的header,编码的payload,一个秘密,然后使用header中指定的算法进行签名。
-
摘要算法:JWT支持多种摘要算法,但最常见的算法是:
- HS256 (HMAC with SHA-256)
- RS256 (RSA signature with SHA-256)
其中,HS256使用共享密钥(客户端和服务器都知道的),而RS256使用私钥/公钥对,只有服务器知道私钥。这使得RS256更适合于公开的、可扩展的环境,因为只有生成JWT的服务器才能验证和接受令牌。
4、个人保险凭证打印,利用 Java 的 PDF 生成库 iText 为用户提供凭证的 PDF 生成功能
-
添加iText库依赖:
- 如果你使用的是Maven,可以在
pom.xml
中添加iText的依赖。不同版本的iText可能有所不同,所以要确保选择一个适合的版本。
- 如果你使用的是Maven,可以在
-
创建PDF文档:
- 使用
Document
类来创建一个新的PDF文档。 - 使用
PdfWriter.getInstance()
方法将这个Document
对象与一个文件输出流关联起来。
- 使用
-
开始编写内容:
- 打开文档:
document.open()
- 使用
document.add()
方法添加内容。iText提供了多种元素,如Paragraph
,Chapter
,Section
,List
,PdfPTable
等,你可以根据需要加入。 - 在为保险凭证填写具体信息时(如保单号、购买日期、姓名等),可以用这些基础元素组装。
- 打开文档:
-
格式化内容:
- 使用iText的字体和样式类来调整文本的外观。
- 使用
BaseFont
和Font
类来创建和应用不同的字体和样式。
-
创建表格:
- 如果需要整齐地显示某些信息,例如保险明细,可以使用
PdfPTable
类来创建表格。 - 表格可以有标题、多个行和列,并可以定义边框、背景等样式。
- 如果需要整齐地显示某些信息,例如保险明细,可以使用
-
添加图片或公司标志:
- 使用
Image
类来添加图片到文档。 - 图片可以是外部文件,也可以是内部资源。
- 使用
-
页眉和页脚:
- 使用
HeaderFooter
类或事件处理来添加页眉和页脚。这样可以为每一页自动添加页码、日期、公司标志等信息。
- 使用
-
完成文档:
- 一旦你添加了所有需要的内容,使用
document.close()
来关闭并保存文档。
- 一旦你添加了所有需要的内容,使用
-
提供给用户:
- 根据你的应用环境,你可以将生成的PDF文件保存到服务器、数据库,或直接作为HTTP响应的一部分发送给用户以供下载或在线预览。
5、JWT的优缺点
JWT 的优点:
-
简洁和自包含:JWT 可以包含所有必要的信息,避免了每次都需要查询数据库来检索用户信息。
-
跨域认证:由于 JWT 是自包含的,它适合跨域认证场景,特别是在移动应用中。
-
无状态性:JWT 使得应用服务器可以完全无状态,从而简化了扩展应用服务器的复杂性。
-
性能:JWT 提供了一种避免每次请求都访问数据库的方法,从而提高性能。
-
适用于移动设备:由于其大小通常较小并且编码为字符串,JWT 非常适合 HTTP 头部传输,尤其在移动网络环境中。
-
安全:使用强加密算法(例如 RS256)可以验证 JWT 的发送者、接收者和内容的完整性。
JWT 的缺点:
-
大小:与简单的令牌或 cookie 相比,JWT 通常较大。当在 HTTP 头部中使用它时,这可能会增加所有请求的大小。
-
加密复杂性:虽然 JWT 可以加密数据,但实现和维护加密需要额外的复杂性。
-
无法从服务器端废除:由于 JWT 是无状态的,一旦颁发了一个 JWT,它会在其过期时间之前一直有效,除非客户端删除它。这意味着撤销或更改 JWT 的权限在其过期之前可能会更具挑战性。
-
存储安全问题:JWT 在客户端存储可能遭受跨站点脚本攻击 (XSS)。攻击者可能会尝试获取存储在客户端的 JWT。
-
过期策略:JWT 的有效性完全依赖于过期策略。如果你设置了一个很长的过期时间,攻击者可能有足够的时间利用一个窃取的令牌;如果设置得太短,用户体验可能会受到影响。
-
依赖于签名算法:JWT 的安全性完全依赖于其使用的签名算法。一些算法,如 "none" 或弱加密算法,可能被攻击。
二、项目(RPC)
1、RPC的原理图和调用过程
图中服务端启动时将自己的服务节点信息注册到注册中心,客户端调用远程方法时会订阅注册中心中的可用服务节点信息,拿到可用服务节点之后远程调用方法,当注册中心中的可用服务节点发生变化时会通知客户端,避免客户端继续调用已经失效的节点。那客户端是如何调用远程方法的呢,来看一下远程调用示意图:
- 客户端模块代理所有远程方法的调用
- 将目标服务、目标方法、调用目标方法的参数等必要信息序列化
- 序列化之后的数据包进一步压缩,压缩后的数据包通过网络通信传输到目标服务节点
- 服务节点将接受到的数据包进行解压
- 解压后的数据包反序列化成目标服务、目标方法、目标方法的调用参数
- 通过服务端代理调用目标方法获取结果,结果同样需要序列化、压缩然后回传给客户端
2、RPC的重要组成有哪些?
-
客户端(Client):发起RPC请求的部分。客户端包含代表远程过程的存根(stub),它提供与本地过程相同的接口。
-
服务器(Server):接受RPC请求并执行服务的部分。服务器同样包含一个存根,负责接受请求、解码参数、执行请求并返回结果。
-
传输层:RPC需要一种通信方式来在客户端和服务器之间传输数据。这通常通过网络完成,例如使用TCP/IP或UDP。
-
消息格式/序列化:由于网络传输层通常只能传输字节流,RPC需要将数据(如过程参数和返回值)转换为这种格式。这个转换过程叫做序列化(将数据转换为字节流)和反序列化(将字节流转回原始数据)。
-
请求与响应:客户端发起的是请求,服务器返回的是响应。每个请求都与一个响应匹配。
-
服务注册与发现:在某些RPC系统中(如gRPC、Apache Thrift等),服务器可以注册其提供的服务,并且客户端可以发现这些服务。这可以使得客户端和服务器的连接更加动态和灵活。
-
错误处理:如果远程调用中发生错误(如网络问题、服务不可用等),RPC框架应该能够捕获并处理这些错误。
-
身份验证和授权:为了确保只有合法的客户端可以访问服务,RPC系统可能会包含身份验证和授权机制。
-
负载均衡:在多个服务器实例中,RPC系统可能会提供负载均衡功能,使得客户端的请求可以均匀地分配到不同的服务器。
3、注册中心怎么选?CP更重要还是AP?ZooKeeper是CP还是AP?
(1)选择CP还是AP取决于你的系统需求:
- 如果系统需要确保数据的一致性,并且可以承受某些请求失败或延迟,那么CP可能更合适。
- 如果系统需要确保高可用性,即使这意味着在某些情况下返回的数据可能是过时的,那么AP可能更合适。
(2)ZooKeeper 是一个CP系统。当网络分区发生时,为了维护一致性,ZooKeeper可能会牺牲可用性。这意味着在某些情况下,ZooKeeper可能不会响应客户端的请求,以确保数据的一致性。
4、序列化的作用是什么?Serializable的原理?
(1)在RPC(远程过程调用)中,序列化的主要作用是将数据或对象转化为可传输的格式,使其能够在网络上进行传输,从而实现不同节点或服务之间的通信。
具体作用如下:
-
数据交换:通过序列化,客户端可以将请求参数转化为字节流,在网络上发送到服务器;服务器接收到字节流后,再通过反序列化恢复为原始的请求参数。
-
保证数据完整性:序列化过程中可以将数据结构完整地转化为字节流,确保数据在传输过程中不丢失任何信息。
-
兼容性:有些序列化协议(如Protocol Buffers, Avro等)提供了版本控制和兼容性管理,使得数据格式可以随着时间演进而不影响已有的客户端和服务器之间的通信。
(2)Serializable的原理
Serializable
是Java中的一个标记性接口,用于指示一个类的对象可以被序列化。当一个类实现了Serializable
接口时,Java的对象序列化机制可以将其转换为字节流,反之也可以从字节流中重构对象。
5、服务启动的时候服务基本信息被注册到注册中心,如果服务提供者挂了,注册中心如何知道服务不可用了呢?
服务掉线分为主动下线和心跳检测。
比如服务由于发版时,在重启之前先主动通知注册中心:我要重启了,有流量进来先不要分给我,让别的机器服务,等我重启成功后在放流量进来,或者是在管理后台手动直接摘掉机器,这个是主动下线。
增加 Netty 心跳机制 : 保证客户端和服务端的连接不被断掉,避免重连。
心跳检测是处理服务非正常下线(如断电断网)的情况,这个时候如果注册中心不知道该服务已经掉线,一旦被其调用就会带来问题。为了避免出现这样的情况,注册中心增加一个心跳检测功能,它会对服务提供者(Provider)进行心跳检测,比如每隔 30s 发送一个心跳,如果三次心跳结果都没有返回值,就认为该服务已下线,赶紧更新 Consumer 的服务列表,告诉 Consumer 调用别的机器
6、如果注册中心挂了,比如你用的是 Zookeeper,如果 Zookeeper 挂了,那服务之间还能相互调用吗?
首先注册中心挂掉也要分两种情况,如果数据库挂了,ZK 还是能用的,因为 ZK 会缓存注册机列表在缓存里。其次 ZK 本身就是一个集群的,一台机器挂了,ZK 会选举出集群中的其他机器作为 Master 继续提供服务,如果整个集群都挂了也没问题,因为调用者本地会缓存注册中心获取的服务列表。省略和注册中心的交互,Consumer 和 Provider 采用直连方式,这些策略都是可配置的。
7、在RPC框架中Netty 如何实现 Client 端与 Server 端的异步通信
在 Netty 中,RPC 框架的实现是基于 Netty 的异步通信机制的。RPC 框架中,客户端与服务端的异步通信是通过 Channel 和 EventLoop 实现的。Channel 是一个连接到网络套接字的组件,而 EventLoop 是处理 Channel 事件的线程。在 Netty 中,每个 Channel 都有一个与之相关联的 EventLoop,它会处理所有的 I/O 事件和请求。
具体步骤如下:
- 客户端通过 Channel 向服务端发送请求。
- 服务端通过 Channel 接收请求。
- 服务端将请求交给 EventLoop 处理。
- EventLoop 处理请求并返回结果。
- 服务端通过 Channel 将结果返回给客户端。
- 客户端通过 Channel 接收结果。
8、为什么用序列化机制 Protostuff 替代 JDK 自带的序列化机制
- 使用简单。Protobuf 每次要编写接口定义文件,然后还要编译,操作太繁琐。
- 高性能。相对 JSON 等文本序列化库,protostuff 是二进制的,因此性能比 JSON 等方式高。
- 序列化后的数据更小,性能更高。protostuff 占用内存最少,protobuf 其次,XML 最后。
- 序列化速度快。protostuff 比 protobuf 快 3 倍左右,protobuf 比 XML 快 4-5 倍
9.ZooKeeper有什么特点,选举机制说一下,什么时候会出现选举问题,他是AP的还是CP的,为什么
(1)ZooKeeper的特点有以下几点
- 高可用性:ZooKeeper集群中只要有一台机器存活,就能对外提供服务。
- 严格顺序访问:所有的更新请求都会按照其发送顺序被逐个执行。
- 数据一致性:ZooKeeper保证数据的最终一致性,即在一定时间内,所有客户端能够读取到同样的数据。
- 可靠性:ZooKeeper保证在分布式环境下数据的可靠性。
(2)ZooKeeper的选举机制是基于Paxos算法。当集群中的Leader节点挂掉时,ZooKeeper会自动进行Leader选举。选举过程分为两个阶段:选举和投票。选举阶段是为了选出一个唯一的Leader,投票阶段是为了让其他节点知道谁是Leader。在选举过程中,每个节点都可以成为候选人,然后通过投票来决定哪个候选人成为Leader。
当出现网络分区或者节点故障时,就会出现选举问题。如果出现网络分区,那么可能会出现多个Leader,这时需要手动干预解决。如果出现节点故障,那么ZooKeeper会自动进行Leader选举。
ZooKeeper是CP系统。它保证了数据的一致性和分区容错性,但不保证可用性。因此,在网络分区或者节点故障时,可能会导致部分客户端无法访问。
10.ZooKeeper做分布式锁说一下
ZooKeeper分布式锁的实现方式是:首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型),然后每个要获得锁的线程,都在这个节点下创建个临时顺序节点。当一个线程需要获得锁时,它会在父节点下创建一个临时顺序节点,然后获取父节点下所有子节点的列表,判断自己创建的节点是否是最小的那个。如果是,则表示该线程获得了锁;否则,该线程就需要监听比自己小的那个节点的删除事件,当该节点被删除时,该线程再次判断自己创建的节点是否是最小的那个。如果是,则表示该线程获得了锁。
11、Duboo的四种负载均衡策略
客户端调用远程服务的时候进行负载均衡 :调用服务的时候,从很多服务地址中根据相应的负载均衡算法选取一个服务地址。
(1)RandomLoadBalance:根据权重随机选择(对加权随机算法的实现)。这是Dubbo默认采用的一种负载均衡策略。
(2)LeastActiveLoadBalance:最小活跃数负载均衡。
Dubbo 就认为谁的活跃数越少,谁的处理速度就越快,性能也越好,这样的话,我就优先把请求给活跃数少的服务提供者处理。
(3)ConsistentHashLoadBalance:一致性Hash负载均衡策略。
ConsistentHashLoadBalance
中没有权重的概念,具体是哪个服务提供者处理请求是由你的请求的参数决定的,也就是说相同参数的请求总是发到同一个服务提供者。另外,Dubbo 为了避免数据倾斜问题(节点不够分散,大量请求落到同一节点),还引入了虚拟节点的概念。通过虚拟节点可以让节点更加分散,有效均衡各个节点的请求量。
(4)RoundRobinLoadBalance:加权轮询负载均衡。
轮询就是把请求依次分配给每个服务提供者。加权轮询就是在轮询的基础上,让更多的请求落到权重更大的服务提供者上。
12.为什么学习RPC
RPC(远程过程调用,Remote Procedure Call)是一种计算机通信协议,它允许程序在一个地址空间中请求服务,而不需要明确提供该服务的详细知识。在分布式系统和微服务架构中,RPC经常被用作通信机制。学习RPC的原因有很多:
-
分布式系统设计:随着业务规模的增长,很多企业都会从单体应用转向分布式系统。在分布式系统中,不同的服务或组件可能部署在不同的机器或数据中心上。RPC为这些服务或组件之间提供了一种快速、高效的通信方式。
-
微服务架构:微服务是近年来非常热门的软件架构模式,每个服务通常负责执行单一的、小的功能。这些服务之间需要通过某种方式进行通信,而RPC是其中之一。
-
性能和优化:与其他通信机制相比,如HTTP RESTful API,RPC通常能提供更好的性能和更少的开销。学习如何优化RPC可以帮助你构建更高效的系统。
-
多语言支持:很多RPC框架,如gRPC,支持多种编程语言,这意味着你可以在不同的语言中编写服务,然后使用RPC进行交互。
-
跨平台通信:RPC允许不同的系统、应用或设备之间进行通信,这为构建跨平台应用提供了可能性。
-
抽象和封装:RPC隐藏了网络通信的复杂性,开发者只需像调用本地函数一样调用远程函数,而不需要关心底层的网络细节。
-
拓展知识和技能:作为软件工程师,了解不同的技术和方法可以帮助你在面对各种问题时更具备选择权和判断力。
三、Java八股
1、Java中的多态实现
(1)编译时多态(静态多态):主要是通过方法重载实现的。
(2)运行时多态(动态多态):主要是通过方法重写(覆盖)和继承实现的。
运行时多态是Java多态性的核心特性,它是如何实现的呢?
-
基于继承与重写:子类可以继承父类的方法,并可以重写(覆盖)父类的方法。因此,当子类对象调用这个方法时,会执行子类中的版本,而不是父类中的版本。
-
引用变量的双重性质:一个引用变量是可以指向它声明的类型,也可以指向它声明类型的任何子类型的实例。例如,如果
Dog
是Animal
的子类,那么Animal
类型的引用变量可以指向Dog
类型的对象。 -
使用了Java的动态绑定技术:在执行期间(而不是在编译期间),JVM(Java虚拟机)使用对象的实际类型(即存储在内存中的对象的实际类型,而不是引用变量的类型)来决定执行哪个版本的方法。
2、ArrayList 和 LinkedList 的区别?
- 是否保证线程安全:
ArrayList
和LinkedList
都是不同步的,也就是不保证线程安全; - 底层数据结构:
ArrayList
底层使用的是Object
数组;LinkedList
底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!) - 插入和删除是否受元素位置的影响:
ArrayList
采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)
方法的时候,ArrayList
会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element)
)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。LinkedList
采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响(add(E e)
、addFirst(E e)
、addLast(E e)
、removeFirst()
、removeLast()
),时间复杂度为 O(1),如果是要在指定位置i
插入和删除元素的话(add(int index, E element)
,remove(Object o)
), 时间复杂度为 O(n) ,因为需要先移动到指定位置再插入。
- 是否支持快速随机访问:
LinkedList
不支持高效的随机元素访问,而ArrayList
(实现了 RandomAccess 接口) 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)
方法)。 - 内存空间占用:
ArrayList
的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
3、类重载和重写的区别
重载是指在同一个类中定义多个方法,它们的方法名相同,但参数列表不同。而重写是指子类重新定义了父类中已有的方法,方法名、参数列表和返回值类型都相同。
这两个概念的区别主要有以下几点:
- 重载发生在本类,重写发生在父类与子类之间。
- 重载的方法名必须相同,重写的方法名相同且返回值类型必须相同。
- 重载的参数列表不同,重写的参数列表必须相同
4、JVM垃圾回收机制
JVM的垃圾回收机制是指在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行。垃圾回收的过程主要包括以下几个步骤:
-
判定垃圾回收的对象。回收垃圾之前,首先要找到需要被当作垃圾而回收的对象。JVM分为五个区域——程序计数器、虚拟机栈、本地方法栈、堆、方法区。我们知道程序计数器与栈均是线程私有的,其生命周期取决于线程的生命周期。
-
标记存活对象。可达性算法是为了标记存活的对象,知道哪些是可回收对象。
-
垃圾回收算法进行回收。常见的几种垃圾回收算法有标记清除、复制算法、标记整理和分代收集算法。
-
压缩内存空间。在进行完垃圾回收之后,可能会出现内存空间不连续的情况,需要进行内存压缩。
5、Java8新增哪些新特性
- Lambda 表达式 (Lambda Expressions):为Java添加了一个新的语法元素,允许您直接在代码中表示函数式编程片段。
(a, b) -> a + b
- Streams API:为集合数据处理提供了一个新的抽象,可以很方便地进行数据操作和计算,支持并行和串行模式。
- 函数式接口 (Functional Interfaces):只包含一个抽象方法的接口,可以与Lambda表达式互相转换。
@FunctionalInterface
是一个新的注解,用来表示函数式接口。 - Default 方法 (Default Methods):允许在接口中为方法提供默认实现,这意味着新方法可以添加到接口中,而不会破坏实现该接口的类。
- java.util.Optional<T> 类:一个可以为空的容器对象,减少空指针异常,并鼓励程序员更加明确地处理可能为空的情况。
- CompletableFuture<T> 类:为Java添加了异步编程的功能,提供了一个简单、强大的API来处理异步计算。
- 静态方法在接口中 (Static Methods in Interfaces):除了默认方法之外,现在还可以在接口中定义静态方法。
6、乐观锁和悲观锁
悲观锁:悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。像 Java 中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
乐观锁:乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)
7、抽象类和接口的区别
抽象类(Abstract Class)和接口(Interface)都是面向对象编程中用于实现抽象性和多态性的高级结构。尽管它们在某些方面有相似之处,但在设计和使用上存在几个关键的差异。以下是它们之间的主要区别:
-
基本定义:
- 抽象类:是一种不能被实例化的类,它可能包含一些抽象方法(没有具体实现的方法)和一些具体的方法。
- 接口:是一个完全抽象的结构,它只包含抽象的方法(在某些编程语言中,如 Java 8 之后,接口可以有默认方法和静态方法)。
-
继承和实现:
- 一个类可以继承一个抽象类,并需要提供抽象方法的具体实现。
- 一个类可以实现多个接口,必须为每个接口提供所有抽象方法的具体实现。
-
访问修饰符:
- 在抽象类中,可以有公共、受保护和私有方法。
- 接口中的方法默认都是公共的,且不能被修改。
-
成员变量:
- 抽象类可以包含数据成员,并可以有构造方法。
- 接口不能包含数据成员(除非是静态和最终的常量)。
-
多继承:
- 大多数 OOP 语言(如 Java)不支持多继承,也就是说,一个类不能继承多个类,但可以实现多个接口。
-
添加新方法:
- 在抽象类中添加新方法可能会破坏所有继承该抽象类的子类。
- 在接口中添加新方法可能会破坏实现该接口的所有类,除非提供了默认实现或该方法是静态的。
-
构造函数和静态块:
- 抽象类可以有构造函数和静态代码块。
- 接口不能有构造函数或静态代码块。
-
状态和行为:
- 抽象类除了行为(方法)之外,还可以维护状态(变量)。
- 接口只描述行为,不描述状态。
8、值传递和引用传递Java中的值传递和引用传递的区别
- 值传递: 函数接收参数值的一个副本。原始数据不会被改变。
- 引用传递: 函数接收一个引用,指向原始数据的内存地址。通过这个引用,你可以修改原始数据。
- 对于基本数据类型(如 int, double, char 等),传递的是真正的值。
- 对于对象,传递的是对象引用的值,即内存地址。因此,如果你在方法中改变对象的状态,原始对象的状态也会被改变。但是,如果你在方法中将引用指向另一个对象,原始引用不会改变。
9、Java实现线程的三种方式
(1) 继承 Thread
类:
创建一个新类,该类继承自Thread
类,并重写run
方法。然后创建该类的实例,并调用它的start
方法来启动线程。
public class MyThread extends Thread {
public void run() {
System.out.println("Thread using Thread class");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
(2) 实现 Runnable
接口:
创建一个新类,该类实现Runnable
接口,并重写run
方法。然后创建该类的实例,并将它传递给一个Thread
对象,然后调用Thread
对象的start
方法来启动线程。
public class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread using Runnable interface");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
(3) 实现 Callable
接口:
创建一个新类,该类实现Callable
接口,并重写call
方法。然后可以使用FutureTask
类来包装Callable
对象,并将FutureTask
对象传递给一个Thread
对象来启动线程。这种方式的优点是可以获取线程的返回值和异常。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<String> {
public String call() throws Exception {
return "Thread using Callable interface";
}
}
public class Main {
public static void main(String[] args) {
MyCallable callable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
10、Java线程池
(1)线程池参数:
(2)线程池执行流程
11、单例模式和工厂模式实现
(1)单例模式(懒汉式,线程安全)
public class Singleton {
private static volatile Singleton instance;
// 私有化构造器,防止外部实例化
private Singleton() {}
// 提供一个全局的访问点
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
运用场景:
- 配置管理:在一个系统中,配置信息是唯一的,我们可以使用单例模式来保证配置对象的唯一性,避免频繁地创建和销毁。
- 连接池:例如数据库连接池,线程池等,它们管理的资源是有限的,通过单例模式来确保整个应用中有一个统一的资源访问入口。
- 日志记录器:日志记录器通常也会是单例的,以保证日志的连贯性和性能。
- 缓存系统:在一个应用中,缓存对象通常也是唯一的,可以使用单例模式来保证缓存系统的一致性。
- Spring框架中的Bean:在Spring框架中,Bean默认是单例的,这样可以节省资源和提高效率。
(2) 工厂模式
public interface Product {
void create();
}
public class ProductA implements Product {
@Override
public void create() {
System.out.println("Product A created");
}
}
public class ProductB implements Product {
@Override
public void create() {
System.out.println("Product B created");
}
}
public class ProductFactory {
public static Product createProduct(String type) {
if ("A".equals(type)) {
return new ProductA();
} else if ("B".equals(type)) {
return new ProductB();
} else {
throw new IllegalArgumentException("Unknown product type");
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Product productA = ProductFactory.createProduct("A");
productA.create();
Product productB = ProductFactory.createProduct("B");
productB.create();
}
}
运用场景:
-
创建库和框架:当创建一个库或框架时,你可能希望提供一种方法给使用者创建某个接口的实例,但不想让他们知道具体的实现类。
-
UI库:许多UI库使用工厂模式来创建控件。例如,在一个跨平台的UI库中,你可能有一个
Button
接口和多个具体的实现类(如WindowsButton
,MacButton
等)。使用工厂模式可以根据运行的操作系统创建正确的按钮类型。 -
支持多种支付方法:例如,如果你正在开发一个电商平台,你可能有一个
PaymentProcessor
接口和多个具体的实现类(如CreditCardProcessor
,PaypalProcessor
等)。使用工厂模式可以根据用户选择创建正确的支付处理器。 -
加载和注册插件或驱动程序:应用程序可能使用工厂模式动态地加载和注册插件或驱动程序。
-
数据库访问:应用程序可能需要与多种数据库进行交互。使用工厂模式,可以为不同的数据库创建适当的数据库连接和查询对象
12、GC垃圾回收算法
-
标记-清除算法(Mark-Sweep)
- 标记阶段:标记所有从根节点可达的对象。
- 清除阶段:清除所有未被标记的对象。
-
标记-整理算法(Mark-Compact)
- 标记阶段:标记所有从根节点可达的对象。
- 整理阶段:将所有活动对象移动到内存的一端,然后清除剩余的内存空间。
-
分代收集算法(Generational Collection)
- 将内存分为几个代(如年轻代和老年代)并根据对象的年龄对其进行不同的垃圾收集策略。
-
引用计数算法(Reference Counting)
- 这种算法在每个对象中维护一个引用计数,当计数为零时,对象被视为垃圾
13、Java异常
-
NullPointerException:这是一个运行时异常,通常发生当你试图访问一个 null 对象的成员时。
-
ArrayIndexOutOfBoundsException:这也是一个运行时异常,发生于尝试访问数组的一个不存在的索引时。
-
ClassCastException:这是一个运行时异常,发生于尝试将一个对象强制转换为不兼容的类型时。
-
IOException:这是一个检查异常,通常发生在 I/O 操作失败或被中断时。需要用 try-catch 语句或者 throws 关键字来处理。
-
FileNotFoundException:这是 IOException 的一个子类,是一个检查异常,通常发生在尝试访问一个不存在的文件时。
-
NumberFormatException:这是一个运行时异常,发生在尝试将一个字符串转换为数字,但字符串的格式不正确时
14、Java 中的几种基本数据类型
15、Exception 和 Error 有什么区别
所有的异常都有一个共同的祖先 java.lang
包中的 Throwable
类。Throwable
类有两个重要的子类:
Exception
:程序本身可以处理的异常,可以通过catch
来进行捕获。Exception
又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。Error
:Error
属于程序无法处理的错误 ,不建议通过catch
捕获 。例如 Java 虚拟机运行错误(Virtual MachineError
)、虚拟机内存不够错误(OutOfMemoryError
)、类定义错误(NoClassDefFoundError
)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
16、HashMap 和 Hashtable 的区别
17、String、StringBuffer、StringBuilder 的区别
四、Spring八股
1.Spring中的IOC和AOP介绍一下
Sring拥有两大特性:IoC和AOP。IoC,英文全称Inversion of Control,意为控制反转(或者叫依赖注入)。AOP,英文全称Aspect-Oriented Programming,意为面向切面编程。
Spring核心容器的主要组件是Bean工厂(BeanFactory),Bean工厂使用控制反转(IoC)模式来降低程序代码之间的耦合度,并提供了面向切面编程(AOP)的实现。
2.IOC要解决什么问题,AOP要解决什么问题
- IoC解决对象之间的耦合问题,例如当service层调用dao层时,传统方式下我们需要在service中new出dao层的具体实现类,这时当我们实现类需要改变时,service层也需要做相应的改变,这就造成了service层和dao层的强耦合。而使用IOC实例化对像时,我们只需要关注调用的dao层的接口,在service中声明接口属性,具体的实现类在IOC容器中进行切换,因此也不会产生对象中强耦合的情况。
- AOP是OOP的延续,opp思想是一种垂直纵向的继承体系,解决了代码开发中的大多数代码重复问题。AOP提出了横向抽取机制,将横切逻辑代码和业务逻辑代码分离,在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
3.AOP动态代理实现的方式
Spring AOP中动态代理的两种实现方式分别是JDK动态代理和CGLIB动态代理。 JDK动态代理是通过反射机制来实现的, CGLIB动态代理则是通过继承目标类来实现的,它不要求目标类实现接口,代理类继承了目标类并在代理类中重写了目标类的方法。
4.Bean的作用域
-
singleton(单例):这是默认的作用域。在Spring IoC容器的上下文中,每个Bean定义对应的实例只有一个。无论多少次请求该Bean,都会返回该容器中的同一个Bean实例。它确保Bean在Spring上下文中是一个单例,但如果有多个Spring上下文,则每个上下文都会有一个Bean的实例。
-
prototype(原型):每次请求都将创建一个新的Bean实例。当你获取Bean时,Spring IoC容器都会返回一个新的实例,这意味着prototype作用域的Bean不会被重用。
-
request:这是一个Web-specific作用域,用于Web应用。每次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP请求内有效。
-
session:这也是一个Web-specific作用域。在Web应用中,为每一个HTTP Session创建一个Bean实例。这意味着该Bean的状态会保持在整个用户会话中。
-
application:这是另一个Web-specific作用域。为每一个ServletContext创建一个Bean实例(通常是Web应用的全局作用域)。这个Bean对所有的HTTP Session是可见的。
-
websocket:在WebSocket生命周期内,为每个WebSocket创建一个Bean实例。
当你定义一个Bean时,你可以指定其作用域。如果没有明确指定,那么Bean的默认作用域是singleton。
5、Spring 框架是怎么实现IOC?
在Spring框架中,IoC(Inversion of Control)是通过以下几种方式实现的:
-
Bean工厂:Spring有一个基本的IoC容器称为Bean工厂,负责创建和管理Bean的生命周期。
-
ApplicationContext:这是Bean工厂的扩展,提供了更多的企业级特性,如事件传播,声明式方式的服务等。
-
XML或注解配置:你可以通过XML文件或注解来配置Bean以及Bean之间的依赖关系,Spring IoC容器将使用这些信息来创建和管理Bean的生命周期。
-
依赖注入:依赖注入是IoC的一种实现方法,它允许你将对象的依赖作为构造函数参数或属性来提供,而不是在对象内部创建依赖。
6、Spring循环依赖,为什么需要三级缓存,两级缓存不行吗
Spring的三级缓存是为了解决循环依赖问题而引入的。在Spring容器中,如果两个Bean相互依赖,那么在创建Bean时就会出现循环依赖问题。为了解决这个问题,Spring使用了三级缓存1。
三级缓存包括:
- singletonObjects: 一级缓存,存储单例对象,Bean已经实例化并初始化完成。
- earlySingletonObjects: 二级缓存,存储singletonObject,这个Bean实例化了,但还没有初始化。
- singletonFactories: 三级缓存,存储singletonFactory。
当一个Bean被创建时,Spring会首先从一级缓存中获取Bean实例。如果一级缓存中不存在该Bean实例,则Spring会从二级缓存中获取该Bean实例。如果二级缓存中也不存在该Bean实例,则Spring会从三级缓存中获取该Bean实例的工厂对象,并调用工厂方法创建该Bean实例。
五、数据库八股
1、MySQL的事务隔离级别?
-
读未提交 (READ UNCOMMITTED)
- 在这个隔离级别中,一个事务可以读取另一个未提交事务的修改。
- 问题:这种级别可能会导致"脏读",即一个事务读取到另一个事务还未提交的数据,如果那个事务最终回滚,那么读取的数据就是无效的。
- 这是最低的隔离级别,锁定的需求最少。
-
读提交 (READ COMMITTED)
- 在这个隔离级别中,一个事务只能读取其他事务已经提交的修改。
- 问题:尽管可以避免"脏读",但是可能会发生"不可重复读",即在同一个事务中,后续的查询可能会看到前一个查询中未看到的行或列的不同值。
- 这是大多数数据库系统的默认隔离级别。
-
可重复读 (REPEATABLE READ)
- 在这个隔离级别中,其他事务不能在事务执行期间插入新行,从而防止了"幻读"。
- 问题:该隔离级别可以避免"脏读"和"不可重复读",但是可能会导致"幻读"。"幻读"是指当某个事务在读取某个范围内的所有行时,另一个事务又在该范围内插入了新行,导致前一个事务再次读取时看到了额外的、早先不存在的行。
- 在MySQL的InnoDB存储引擎中,这是默认的隔离级别。
-
串行化 (SERIALIZABLE)
- 这是最严格的隔离级别。当一个事务选择了该隔离级别后,其他事务就不能并发执行,它们必须等待该事务完成。
- 问题:这种级别可以避免"脏读"、"不可重复读"和"幻读",但是性能开销最大,因为事务之间完全是串行执行的。
- 这个隔离级别很少在实际应用中使用,除非是非常关键的操作
2、MySQL 索引
(1)MySQL索引结构
索引底层数据结构存在很多种类型,常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam,都使用了 B+树作为索引结构。在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景建议选择BTree索引。
(2)索引的优缺点
优点 :
- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
缺点 :
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
但是,使用索引一定能提高查询性能吗?
大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。
(3)索引的底层数据结构
1) Hash 表
哈希算法有个 Hash 冲突 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 链地址法。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 HashMap
就是通过链地址法来解决哈希冲突的。不过,JDK1.8 以后HashMap
为了减少链表过长的时候搜索时间过长引入了红黑树。
为了减少 Hash 冲突的发生,一个好的哈希函数应该“均匀地”将数据分布在整个可能的哈希值集合中。既然哈希表这么快,为什么 MySQL 没有使用其作为索引的数据结构呢? 主要是因为 Hash 索引不支持顺序和范围查询。假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。并且,每次 IO 只能取一个。
2) B 树& B+树
B 树也称 B-树,全称为 多路平衡查找树 ,B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 Balanced
(平衡)的意思。
目前大部分数据库系统及文件系统都采用 B-Tree 或其变种 B+Tree 作为索引结构。
B 树& B+树两者有何异同呢?
- B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
- B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
(3) MySQL索引类型总结
1)按照数据结构维度划分:
- BTree 索引:MySQL 里默认和最常用的索引类型。只有叶子节点存储 value,非叶子节点只有指针和 key。存储引擎 MyISAM 和 InnoDB 实现 BTree 索引都是使用 B+Tree,但二者实现方式不一样。
- 哈希索引:类似键值对的形式,一次即可定位。
- RTree 索引:一般不会使用,仅支持 geometry 数据类型,优势在于范围查找,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有
CHAR
、VARCHAR
,TEXT
列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
2)按照底层存储方式角度划分:
- 聚簇索引(聚集索引):索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
3)按照应用维度划分:
- 主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。
- 普通索引:仅加速查询。
- 唯一索引:加速查询 + 列值唯一(可以有 NULL)。
- 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
- 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有
CHAR
、VARCHAR
,TEXT
列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
(4)二级索引
二级索引(Secondary Index)又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。
- 唯一索引(Unique Key) :唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
- 普通索引(Index) :普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
- 前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, 因为只取前几个字符。
- 全文索引(Full Text) :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。
(5) B+树作为索引的优点
- B+树的查询效率更高,因为B+树的非叶子节点只存储索引信息,而不存储数据信息,这样可以使得每个节点能够存储更多的索引信息,从而使得树的高度更低,查询效率更高。
- B+树的插入和删除效率更高,因为B+树的叶子节点之间是通过链表相连的,所以插入和删除操作只需要修改相邻两个叶子节点之间的指针即可,不需要移动其他节点。
- B+树支持范围查询,因为B+树的叶子节点之间是通过链表相连的,所以可以很方便地进行范围查询。
(6)联合索引
使用表中的多个字段创建索引,就是 联合索引,也叫 组合索引 或 复合索引。
最左前缀匹配原则:
最左前缀匹配原则指的是,在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询(如 >
、<
)才会停止匹配。对于 >=
、<=
、BETWEEN
、like
前缀匹配的范围查询,并不会停止匹配。所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
3、MySQL 事务ACID
(1)那数据库事务有什么作用
简单来说,数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:要么全部执行成功,要么全部不执行。
(2)关系型数据库(例如:MySQL
、SQL Server
、Oracle
等)事务都有 ACID 特性:
- 原子性(
Atomicity
) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; - 一致性(
Consistency
): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的; - 隔离性(
Isolation
): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的; - 持久性(
Durability
): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障
5、并发事务的控制方式有哪些
MySQL 中并发事务的控制方式无非就两种:锁 和 MVCC。锁可以看作是悲观控制的模式,多版本并发控制(MVCC,Multiversion concurrency control)可以看作是乐观控制的模式。锁 控制方式下会通过锁来显示控制共享资源而不是通过调度手段,MySQL 中主要是通过 读写锁 来实现并发控制。
MVCC 是多版本并发控制方法,即对一份数据会存储多个版本,通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号,版本号是唯一的。
MVCC 在 MySQL 中实现所依赖的手段主要是: 隐藏字段、read view、undo log。
- undo log : undo log 用于记录某行数据的多个版本的数据。
- read view 和 隐藏字段 : 用来判断当前版本数据的可见性。
6、Redis 常用的数据结构有哪些
- 5 种基础数据结构 :String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
- 3 种特殊数据结构 :HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。
7、Redis 生产问题(重要)
(1)缓存穿透
缓存穿透说简单点就是大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中 。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
有哪些解决办法?
1)缓存无效 key
如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
2)布隆过滤器
具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
(2)缓存击穿
缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
有哪些解决办法?
- 设置热点数据永不过期或者过期时间比较长。
- 针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
- 请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求会落到数据库上,减少数据库的压力。
缓存穿透和缓存击穿有什么区别?
- 缓存穿透中,请求的 key 既不存在于缓存中,也不存在于数据库中。
- 缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。
(3)缓存雪崩
实际上,缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。 这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。另外,缓存服务宕机也会导致缓存雪崩现象,导致所有的请求都落到了数据库上。
有哪些解决办法?
针对 Redis 服务不可用的情况:
- 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
- 限流,避免同时处理大量的请求。
针对热点缓存失效的情况:
- 设置不同的失效时间比如随机设置缓存的失效时间。
- 缓存永不失效(不太推荐,实用性太差)。
- 设置二级缓存。
缓存雪崩和缓存击穿有什么区别?
缓存雪崩和缓存击穿比较像,但缓存雪崩导致的原因是缓存中的大量或者所有数据失效,缓存击穿导致的原因主要是某个热点数据不存在与缓存中(通常是因为缓存中的那份数据已经过期)
8、Redis 持久化机制(重要)
(1)Redis 支持持久化,而且支持 3 种持久化方式
- 快照(snapshotting,RDB)
- 只追加文件(append-only file, AOF)
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
(2)快照(RDB)
Redis 可以通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
(3) 只追加文件(append-only file, AOF)
AOF 持久化功能的实现可以简单分为 5 步:
- 命令追加(append) :所有的写命令会追加到 AOF 缓冲区中。
- 文件写入(write) :将 AOF 缓冲区的数据写入到 AOF 文件中。这一步需要调用
write
函数(系统调用),write
将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。 - 文件同步(fsync) :AOF 缓冲区根据对应的持久化方式(
fsync
策略)向硬盘做同步操作。这一步需要调用fsync
函数(系统调用),fsync
针对单个文件操作,对其进行强制硬盘同步,fsync
将阻塞直到写入磁盘完成后返回,保证了数据持久化。 - 文件重写(rewrite) :随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 重启加载(load) :当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
9、Redis执行速度为什么这么快
-
基于内存操作:Redis的数据是存储在内存中的,内存的访问速度远远超过硬盘。因此,与传统的基于磁盘的数据库相比,Redis可以提供非常快的读写速度。
-
简单的数据结构:Redis支持的数据结构相对简单,如字符串、列表、集合、哈希等。这些数据结构的操作非常直接,减少了复杂查询处理的开销。
-
单线程模型:Redis使用单线程模型来处理命令,避免了常见的多线程上下文切换和资源竞争的开销。虽然它是单线程的,但由于内存存储和高效的数据结构,Redis仍然能够处理上万到几十万的QPS(每秒查询数)。
-
持久化策略:虽然Redis主要是一个内存数据库,但它提供了几种灵活的持久化方法,如RDB快照和AOF日志文件。这些方法可以根据需要配置,以平衡性能和数据安全性。
-
优化的网络协议:Redis使用了一种简单的文本协议RESP(Redis Serialization Protocol)。该协议设计得很简单,因此客户端和服务器之间的数据交换非常快速。
-
事件驱动模型:Redis使用了事件驱动模型来处理并发连接,这使得Redis能够高效地处理大量并发客户端。
六、网络八股
1.URL输入到网页展示
-
地址解析:
- 浏览器检查URL是否在本地缓存中有对应的IP地址。
- 如果没有,浏览器会向系统的默认DNS服务器请求解析该URL对应的IP地址。
- DNS服务器响应请求,返回对应的IP地址。
-
建立TCP连接:
- 浏览器与远程服务器通过三次握手建立TCP连接。
-
发送HTTP请求:
- 浏览器发送HTTP请求到服务器。
-
服务器处理请求并返回HTTP响应:
- 服务器处理接收到的请求。
- 服务器返回一个HTTP响应给浏览器。
-
浏览器解析并渲染页面:
- 浏览器首先解析HTML来构建DOM树。
- 浏览器解析CSS样式信息,与DOM树结合,形成渲染树。
- 浏览器布局渲染树(进行布局计算)。
- 浏览器绘制渲染树,展示页面内容。
-
加载嵌套的资源:
- HTML页面中可能包含嵌套的资源,如图片、CSS、JavaScript等。浏览器会对这些资源进行上述相同的过程:解析地址、建立连接、请求资源、获取响应、处理和渲染。
-
执行JavaScript:
- 浏览器解析和执行JavaScript代码。这可能会修改页面内容。
-
关闭连接:
- 如果HTTP/1.1的“Keep-Alive”参数没有被使用,浏览器会关闭TCP连接。若使用了HTTP/2,则连接在多个请求中可能被重用。
2.TCP三次握手和四次挥手
(1)三次握手的步骤如下:
- 客户端向服务器发送一个SYN包,表示请求建立连接。
- 服务器接收到客户端发来的SYN包后,对该包进行确认后结束LISTEN阶段,并返回一段TCP报文,表示确认客户端的报文序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接。
- 客户端接收到服务器发来的TCP报文后,再向服务器发送一段确认报文,表示客户端已经准备好发送数据。
(2)四次挥手的步骤如下:
- 客户端向服务器发送一个FIN包,表示请求断开连接。
- 服务器接收到客户端发来的FIN包后,对该包进行确认后进入CLOSE_WAIT状态。
- 服务器向客户端发送一个ACK包,表示已经准备好断开连接。
- 客户端接收到服务器发来的ACK包后,进入TIME_WAIT状态,并向服务器发送一个FIN包,表示已经准备好断开连接。
(3) 三次握手原因:
-
防止已失效的连接请求报文段突然传到了服务端:考虑一个场景,客户端发送了第一个连接请求,但是由于网络原因这个请求被延迟了,于是TCP又发送了一个连接请求。当网络好转时,两个连接请求几乎同时到达服务端,如果此时是两次握手,服务端就会建立两个连接,但客户端只建立了一个连接,这就造成了服务端资源的浪费。
-
更为可靠地确认双方的接收与发送能力:三次握手可以确保双方都有接收和发送消息的能力。两次握手无法保证这一点。
-
设定序列号:三次握手还可以使得双方都能为TCP连接初始的序列号达成一致
3、TCP 与 UDP 的区别
4、为什么第四次挥手客户端需要等待 2*MSL(报文段最长寿命)时间后才进入 CLOSED 状态?
5、HTTP和HTTPS的区别,HTTP请求的构成?
(1)HTTP与HTTPS的区别
-
安全性:
- HTTP:超文本传输协议,信息是明文传输,存在安全风险。
- HTTPS:即HTTP加入SSL层,超文本传输安全协议。信息是经过加密的,更加安全。
-
端口:
- HTTP:使用端口80。
- HTTPS:使用端口443。
-
性能:
- HTTP:因为没有加密,所以HTTP的速度比较快。
- HTTPS:需要进行加密处理,因此相对较慢(但随着现代技术的发展,这种差异已经被最小化)。
-
证书:
- HTTP:不需要证书。
- HTTPS:需要SSL证书。如果网站使用的是自签名的SSL证书,浏览器会提示访问者。
(2) HTTP请求结构
一个HTTP请求主要包含以下部分:
- 请求行:包括请求方法(如GET, POST, PUT, DELETE等)、请求URI以及HTTP版本。
- 请求头(Headers):描述请求的元数据或其他信息,如User-Agent(浏览器类型)、Accept(可接受的回复类型)、Host(请求的服务器)等。
- 空行:请求头和请求体之间的分隔符。
- 请求体(Body):POST或PUT请求中传送的数据。
(3) 请求头的作用
请求头在HTTP请求中扮演了重要的角色,它为服务器提供了关于客户端请求的一些信息。以下是请求头的一些常见用途:
- 内容类型:通过
Content-Type
头部,客户端可以告诉服务器发送的数据是什么格式,如application/json
或text/html
。 - 内容长度:通过
Content-Length
头部,指示请求或响应体的大小。 - 认证:例如,
Authorization
头部用于包含凭据,通常用于API认证。 - 缓存控制:
Cache-Control
和其他相关的头部可以控制如何缓存响应内容。 - 用户代理:
User-Agent
头部描述了发出请求的客户端类型,如浏览器或其他客户端应用。 - 接受的内容类型:通过
Accept
头部,客户端可以告诉服务器它希望收到哪种类型的响应。 - Cookies:
Cookie
头部可以包含服务器设置的任何cookie,它们在每个请求中发送回服务器。 - 跨域请求:
Origin
头部表示请求来自哪个源,与CORS(跨来源资源共享)策略相关。
6、IOS七层和TCP四层协议
(1)OSI 七层模型是一个标准化的网络协议族层次划分,每一层都有特定的功能和责任。从上到下,这些层次是:
-
应用层(Application Layer)
- 负责提供网络服务与最终用户的接口。
- 常见协议:HTTP, HTTPS, FTP, SMTP, POP3, IMAP等。
-
表示层(Presentation Layer)
- 负责数据格式转换、数据加密等。
- 例子:ASCII, UTF-8, JPEG, MPEG等。
-
会话层(Session Layer)
- 负责建立、管理和终止会话。
- 功能包括:对话控制、同步等。
-
传输层(Transport Layer)
- 负责端对端的通信和流控制。
- 常见协议:TCP, UDP。
-
网络层(Network Layer)
- 负责数据包的路由和转发。
- 常见协议:IP, ICMP, OSPF, BGP等。
-
数据链路层(Data Link Layer)
- 负责在相邻网络节点间的数据传输。
- 分为两个子层:逻辑链路控制(LLC)和媒体访问控制(MAC)。
- 常见协议:ARP, PPP, Ethernet等。
-
物理层(Physical Layer)
- 负责比特流在物理媒介(如电缆、光纤)上的传输。
- 包括定义物理媒体的特性、比特编码等。
(2)TCP/IP 模型是实际使用最为广泛的网络协议族结构模型,它简化了 OSI 模型层次划分,主要包括以下几层:
-
应用层(Application Layer)
- 对应 OSI 的应用层、表示层和会话层。
- 负责提供网络服务与最终用户的接口。
- 常见协议:HTTP, SMTP, FTP等。
-
传输层(Transport Layer)
- 对应 OSI 的传输层。
- 负责提供端到端的通信服务。
- 常见协议:TCP, UDP。
-
网络层(Network Layer)
- 对应 OSI 的网络层。
- 负责路由和转发数据包。
- 常见协议:IP, ICMP。
-
链路层(Link Layer)
- 对应 OSI 的数据链路层和物理层。
- 负责在相邻网络节点间的数据传输。
- 常见协议:Ethernet, PPP, ARP等.