Spring Boot与Spring Security:
使用JWT:
存储和管理令牌:
安全性:
日志和监控:
JWT登录流程:
JWT结构包括三部分:
头(Header):它通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,如HMAC SHA256或RSA。
有效载荷(Payload):包含声明,这是关于实体(通常是用户)以及其他一些元数据的语句。这些声明被称为“claims”。
签名(Signature):为了获得签名部分,您必须获取编码的header,编码的payload,一个秘密,然后使用header中指定的算法进行签名。
摘要算法:JWT支持多种摘要算法,但最常见的算法是:
其中,HS256使用共享密钥(客户端和服务器都知道的),而RS256使用私钥/公钥对,只有服务器知道私钥。这使得RS256更适合于公开的、可扩展的环境,因为只有生成JWT的服务器才能验证和接受令牌。
添加iText库依赖:
pom.xml中添加iText的依赖。不同版本的iText可能有所不同,所以要确保选择一个适合的版本。创建PDF文档:
Document类来创建一个新的PDF文档。PdfWriter.getInstance()方法将这个Document对象与一个文件输出流关联起来。开始编写内容:
document.open()document.add()方法添加内容。iText提供了多种元素,如Paragraph, Chapter, Section, List, PdfPTable等,你可以根据需要加入。格式化内容:
BaseFont和Font类来创建和应用不同的字体和样式。创建表格:
PdfPTable类来创建表格。添加图片或公司标志:
Image类来添加图片到文档。页眉和页脚:
HeaderFooter类或事件处理来添加页眉和页脚。这样可以为每一页自动添加页码、日期、公司标志等信息。完成文档:
document.close()来关闭并保存文档。提供给用户:
JWT 的优点:
简洁和自包含:JWT 可以包含所有必要的信息,避免了每次都需要查询数据库来检索用户信息。
跨域认证:由于 JWT 是自包含的,它适合跨域认证场景,特别是在移动应用中。
无状态性:JWT 使得应用服务器可以完全无状态,从而简化了扩展应用服务器的复杂性。
性能:JWT 提供了一种避免每次请求都访问数据库的方法,从而提高性能。
适用于移动设备:由于其大小通常较小并且编码为字符串,JWT 非常适合 HTTP 头部传输,尤其在移动网络环境中。
安全:使用强加密算法(例如 RS256)可以验证 JWT 的发送者、接收者和内容的完整性。
JWT 的缺点:
大小:与简单的令牌或 cookie 相比,JWT 通常较大。当在 HTTP 头部中使用它时,这可能会增加所有请求的大小。
加密复杂性:虽然 JWT 可以加密数据,但实现和维护加密需要额外的复杂性。
无法从服务器端废除:由于 JWT 是无状态的,一旦颁发了一个 JWT,它会在其过期时间之前一直有效,除非客户端删除它。这意味着撤销或更改 JWT 的权限在其过期之前可能会更具挑战性。
存储安全问题:JWT 在客户端存储可能遭受跨站点脚本攻击 (XSS)。攻击者可能会尝试获取存储在客户端的 JWT。
过期策略:JWT 的有效性完全依赖于过期策略。如果你设置了一个很长的过期时间,攻击者可能有足够的时间利用一个窃取的令牌;如果设置得太短,用户体验可能会受到影响。
依赖于签名算法:JWT 的安全性完全依赖于其使用的签名算法。一些算法,如 "none" 或弱加密算法,可能被攻击。

图中服务端启动时将自己的服务节点信息注册到注册中心,客户端调用远程方法时会订阅注册中心中的可用服务节点信息,拿到可用服务节点之后远程调用方法,当注册中心中的可用服务节点发生变化时会通知客户端,避免客户端继续调用已经失效的节点。那客户端是如何调用远程方法的呢,来看一下远程调用示意图:

客户端(Client):发起RPC请求的部分。客户端包含代表远程过程的存根(stub),它提供与本地过程相同的接口。
服务器(Server):接受RPC请求并执行服务的部分。服务器同样包含一个存根,负责接受请求、解码参数、执行请求并返回结果。
传输层:RPC需要一种通信方式来在客户端和服务器之间传输数据。这通常通过网络完成,例如使用TCP/IP或UDP。
消息格式/序列化:由于网络传输层通常只能传输字节流,RPC需要将数据(如过程参数和返回值)转换为这种格式。这个转换过程叫做序列化(将数据转换为字节流)和反序列化(将字节流转回原始数据)。
请求与响应:客户端发起的是请求,服务器返回的是响应。每个请求都与一个响应匹配。
服务注册与发现:在某些RPC系统中(如gRPC、Apache Thrift等),服务器可以注册其提供的服务,并且客户端可以发现这些服务。这可以使得客户端和服务器的连接更加动态和灵活。
错误处理:如果远程调用中发生错误(如网络问题、服务不可用等),RPC框架应该能够捕获并处理这些错误。
身份验证和授权:为了确保只有合法的客户端可以访问服务,RPC系统可能会包含身份验证和授权机制。
负载均衡:在多个服务器实例中,RPC系统可能会提供负载均衡功能,使得客户端的请求可以均匀地分配到不同的服务器。
(1)选择CP还是AP取决于你的系统需求:
(2)ZooKeeper 是一个CP系统。当网络分区发生时,为了维护一致性,ZooKeeper可能会牺牲可用性。这意味着在某些情况下,ZooKeeper可能不会响应客户端的请求,以确保数据的一致性。
(1)在RPC(远程过程调用)中,序列化的主要作用是将数据或对象转化为可传输的格式,使其能够在网络上进行传输,从而实现不同节点或服务之间的通信。
具体作用如下:
数据交换:通过序列化,客户端可以将请求参数转化为字节流,在网络上发送到服务器;服务器接收到字节流后,再通过反序列化恢复为原始的请求参数。
保证数据完整性:序列化过程中可以将数据结构完整地转化为字节流,确保数据在传输过程中不丢失任何信息。
兼容性:有些序列化协议(如Protocol Buffers, Avro等)提供了版本控制和兼容性管理,使得数据格式可以随着时间演进而不影响已有的客户端和服务器之间的通信。
(2)Serializable的原理
Serializable 是Java中的一个标记性接口,用于指示一个类的对象可以被序列化。当一个类实现了Serializable接口时,Java的对象序列化机制可以将其转换为字节流,反之也可以从字节流中重构对象。
服务掉线分为主动下线和心跳检测。
比如服务由于发版时,在重启之前先主动通知注册中心:我要重启了,有流量进来先不要分给我,让别的机器服务,等我重启成功后在放流量进来,或者是在管理后台手动直接摘掉机器,这个是主动下线。
增加 Netty 心跳机制 : 保证客户端和服务端的连接不被断掉,避免重连。
心跳检测是处理服务非正常下线(如断电断网)的情况,这个时候如果注册中心不知道该服务已经掉线,一旦被其调用就会带来问题。为了避免出现这样的情况,注册中心增加一个心跳检测功能,它会对服务提供者(Provider)进行心跳检测,比如每隔 30s 发送一个心跳,如果三次心跳结果都没有返回值,就认为该服务已下线,赶紧更新 Consumer 的服务列表,告诉 Consumer 调用别的机器
首先注册中心挂掉也要分两种情况,如果数据库挂了,ZK 还是能用的,因为 ZK 会缓存注册机列表在缓存里。其次 ZK 本身就是一个集群的,一台机器挂了,ZK 会选举出集群中的其他机器作为 Master 继续提供服务,如果整个集群都挂了也没问题,因为调用者本地会缓存注册中心获取的服务列表。省略和注册中心的交互,Consumer 和 Provider 采用直连方式,这些策略都是可配置的。
在 Netty 中,RPC 框架的实现是基于 Netty 的异步通信机制的。RPC 框架中,客户端与服务端的异步通信是通过 Channel 和 EventLoop 实现的。Channel 是一个连接到网络套接字的组件,而 EventLoop 是处理 Channel 事件的线程。在 Netty 中,每个 Channel 都有一个与之相关联的 EventLoop,它会处理所有的 I/O 事件和请求。
具体步骤如下:
(1)ZooKeeper的特点有以下几点
(2)ZooKeeper的选举机制是基于Paxos算法。当集群中的Leader节点挂掉时,ZooKeeper会自动进行Leader选举。选举过程分为两个阶段:选举和投票。选举阶段是为了选出一个唯一的Leader,投票阶段是为了让其他节点知道谁是Leader。在选举过程中,每个节点都可以成为候选人,然后通过投票来决定哪个候选人成为Leader。
当出现网络分区或者节点故障时,就会出现选举问题。如果出现网络分区,那么可能会出现多个Leader,这时需要手动干预解决。如果出现节点故障,那么ZooKeeper会自动进行Leader选举。
ZooKeeper是CP系统。它保证了数据的一致性和分区容错性,但不保证可用性。因此,在网络分区或者节点故障时,可能会导致部分客户端无法访问。
ZooKeeper分布式锁的实现方式是:首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型),然后每个要获得锁的线程,都在这个节点下创建个临时顺序节点。当一个线程需要获得锁时,它会在父节点下创建一个临时顺序节点,然后获取父节点下所有子节点的列表,判断自己创建的节点是否是最小的那个。如果是,则表示该线程获得了锁;否则,该线程就需要监听比自己小的那个节点的删除事件,当该节点被删除时,该线程再次判断自己创建的节点是否是最小的那个。如果是,则表示该线程获得了锁。
客户端调用远程服务的时候进行负载均衡 :调用服务的时候,从很多服务地址中根据相应的负载均衡算法选取一个服务地址。
(1)RandomLoadBalance:根据权重随机选择(对加权随机算法的实现)。这是Dubbo默认采用的一种负载均衡策略。
(2)LeastActiveLoadBalance:最小活跃数负载均衡。
Dubbo 就认为谁的活跃数越少,谁的处理速度就越快,性能也越好,这样的话,我就优先把请求给活跃数少的服务提供者处理。
(3)ConsistentHashLoadBalance:一致性Hash负载均衡策略。
ConsistentHashLoadBalance 中没有权重的概念,具体是哪个服务提供者处理请求是由你的请求的参数决定的,也就是说相同参数的请求总是发到同一个服务提供者。另外,Dubbo 为了避免数据倾斜问题(节点不够分散,大量请求落到同一节点),还引入了虚拟节点的概念。通过虚拟节点可以让节点更加分散,有效均衡各个节点的请求量。
(4)RoundRobinLoadBalance:加权轮询负载均衡。
轮询就是把请求依次分配给每个服务提供者。加权轮询就是在轮询的基础上,让更多的请求落到权重更大的服务提供者上。
RPC(远程过程调用,Remote Procedure Call)是一种计算机通信协议,它允许程序在一个地址空间中请求服务,而不需要明确提供该服务的详细知识。在分布式系统和微服务架构中,RPC经常被用作通信机制。学习RPC的原因有很多:
分布式系统设计:随着业务规模的增长,很多企业都会从单体应用转向分布式系统。在分布式系统中,不同的服务或组件可能部署在不同的机器或数据中心上。RPC为这些服务或组件之间提供了一种快速、高效的通信方式。
微服务架构:微服务是近年来非常热门的软件架构模式,每个服务通常负责执行单一的、小的功能。这些服务之间需要通过某种方式进行通信,而RPC是其中之一。
性能和优化:与其他通信机制相比,如HTTP RESTful API,RPC通常能提供更好的性能和更少的开销。学习如何优化RPC可以帮助你构建更高效的系统。
多语言支持:很多RPC框架,如gRPC,支持多种编程语言,这意味着你可以在不同的语言中编写服务,然后使用RPC进行交互。
跨平台通信:RPC允许不同的系统、应用或设备之间进行通信,这为构建跨平台应用提供了可能性。
抽象和封装:RPC隐藏了网络通信的复杂性,开发者只需像调用本地函数一样调用远程函数,而不需要关心底层的网络细节。
拓展知识和技能:作为软件工程师,了解不同的技术和方法可以帮助你在面对各种问题时更具备选择权和判断力。
(1)编译时多态(静态多态):主要是通过方法重载实现的。
(2)运行时多态(动态多态):主要是通过方法重写(覆盖)和继承实现的。
运行时多态是Java多态性的核心特性,它是如何实现的呢?
基于继承与重写:子类可以继承父类的方法,并可以重写(覆盖)父类的方法。因此,当子类对象调用这个方法时,会执行子类中的版本,而不是父类中的版本。
引用变量的双重性质:一个引用变量是可以指向它声明的类型,也可以指向它声明类型的任何子类型的实例。例如,如果Dog是Animal的子类,那么Animal类型的引用变量可以指向Dog类型的对象。
使用了Java的动态绑定技术:在执行期间(而不是在编译期间),JVM(Java虚拟机)使用对象的实际类型(即存储在内存中的对象的实际类型,而不是引用变量的类型)来决定执行哪个版本的方法。
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 更多的空间(因为要存放直接后继和直接前驱以及数据)。重载是指在同一个类中定义多个方法,它们的方法名相同,但参数列表不同。而重写是指子类重新定义了父类中已有的方法,方法名、参数列表和返回值类型都相同。
这两个概念的区别主要有以下几点:
JVM的垃圾回收机制是指在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行。垃圾回收的过程主要包括以下几个步骤:
判定垃圾回收的对象。回收垃圾之前,首先要找到需要被当作垃圾而回收的对象。JVM分为五个区域——程序计数器、虚拟机栈、本地方法栈、堆、方法区。我们知道程序计数器与栈均是线程私有的,其生命周期取决于线程的生命周期。
标记存活对象。可达性算法是为了标记存活的对象,知道哪些是可回收对象。
垃圾回收算法进行回收。常见的几种垃圾回收算法有标记清除、复制算法、标记整理和分代收集算法。
压缩内存空间。在进行完垃圾回收之后,可能会出现内存空间不连续的情况,需要进行内存压缩。
(a, b) -> a + b
@FunctionalInterface是一个新的注解,用来表示函数式接口。悲观锁:悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。像 Java 中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁:乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)
抽象类(Abstract Class)和接口(Interface)都是面向对象编程中用于实现抽象性和多态性的高级结构。尽管它们在某些方面有相似之处,但在设计和使用上存在几个关键的差异。以下是它们之间的主要区别:
基本定义:
继承和实现:
访问修饰符:
成员变量:
多继承:
添加新方法:
构造函数和静态块:
状态和行为:
(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
{ - public String call() throws Exception {
- return "Thread using Callable interface";
- }
- }
-
- public class Main {
- public static void main(String[] args) {
- MyCallable callable = new MyCallable();
- FutureTask
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();
- }
- }
- }
(1)线程池参数:

(2)线程池执行流程

(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;
- }
- }
运用场景:
(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等)。使用工厂模式可以根据用户选择创建正确的支付处理器。
加载和注册插件或驱动程序:应用程序可能使用工厂模式动态地加载和注册插件或驱动程序。
数据库访问:应用程序可能需要与多种数据库进行交互。使用工厂模式,可以为不同的数据库创建适当的数据库连接和查询对象
标记-清除算法(Mark-Sweep)
标记-整理算法(Mark-Compact)
分代收集算法(Generational Collection)
引用计数算法(Reference Counting)
NullPointerException:这是一个运行时异常,通常发生当你试图访问一个 null 对象的成员时。
ArrayIndexOutOfBoundsException:这也是一个运行时异常,发生于尝试访问数组的一个不存在的索引时。
ClassCastException:这是一个运行时异常,发生于尝试将一个对象强制转换为不兼容的类型时。
IOException:这是一个检查异常,通常发生在 I/O 操作失败或被中断时。需要用 try-catch 语句或者 throws 关键字来处理。
FileNotFoundException:这是 IOException 的一个子类,是一个检查异常,通常发生在尝试访问一个不存在的文件时。
NumberFormatException:这是一个运行时异常,发生在尝试将一个字符串转换为数字,但字符串的格式不正确时

所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:
Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。Error:Error 属于程序无法处理的错误 ,不建议通过catch捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

Sring拥有两大特性:IoC和AOP。IoC,英文全称Inversion of Control,意为控制反转(或者叫依赖注入)。AOP,英文全称Aspect-Oriented Programming,意为面向切面编程。
Spring核心容器的主要组件是Bean工厂(BeanFactory),Bean工厂使用控制反转(IoC)模式来降低程序代码之间的耦合度,并提供了面向切面编程(AOP)的实现。
Spring AOP中动态代理的两种实现方式分别是JDK动态代理和CGLIB动态代理。 JDK动态代理是通过反射机制来实现的, CGLIB动态代理则是通过继承目标类来实现的,它不要求目标类实现接口,代理类继承了目标类并在代理类中重写了目标类的方法。
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。
在Spring框架中,IoC(Inversion of Control)是通过以下几种方式实现的:
Bean工厂:Spring有一个基本的IoC容器称为Bean工厂,负责创建和管理Bean的生命周期。
ApplicationContext:这是Bean工厂的扩展,提供了更多的企业级特性,如事件传播,声明式方式的服务等。
XML或注解配置:你可以通过XML文件或注解来配置Bean以及Bean之间的依赖关系,Spring IoC容器将使用这些信息来创建和管理Bean的生命周期。
依赖注入:依赖注入是IoC的一种实现方法,它允许你将对象的依赖作为构造函数参数或属性来提供,而不是在对象内部创建依赖。
Spring的三级缓存是为了解决循环依赖问题而引入的。在Spring容器中,如果两个Bean相互依赖,那么在创建Bean时就会出现循环依赖问题。为了解决这个问题,Spring使用了三级缓存1。
三级缓存包括:
当一个Bean被创建时,Spring会首先从一级缓存中获取Bean实例。如果一级缓存中不存在该Bean实例,则Spring会从二级缓存中获取该Bean实例。如果二级缓存中也不存在该Bean实例,则Spring会从三级缓存中获取该Bean实例的工厂对象,并调用工厂方法创建该Bean实例。
读未提交 (READ UNCOMMITTED)
读提交 (READ COMMITTED)
可重复读 (REPEATABLE READ)
串行化 (SERIALIZABLE)
(1)MySQL索引结构
索引底层数据结构存在很多种类型,常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam,都使用了 B+树作为索引结构。在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景建议选择BTree索引。
(2)索引的优缺点
优点 :
缺点 :
但是,使用索引一定能提高查询性能吗?
大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。
(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+树两者有何异同呢?
(3) MySQL索引类型总结
1)按照数据结构维度划分:
CHAR、VARCHAR ,TEXT 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。2)按照底层存储方式角度划分:
3)按照应用维度划分:
CHAR、VARCHAR ,TEXT 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。(4)二级索引
二级索引(Secondary Index)又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。
(5) B+树作为索引的优点
(6)联合索引
使用表中的多个字段创建索引,就是 联合索引,也叫 组合索引 或 复合索引。
最左前缀匹配原则:
最左前缀匹配原则指的是,在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询(如 >、<)才会停止匹配。对于 >=、<=、BETWEEN、like 前缀匹配的范围查询,并不会停止匹配。所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
(1)那数据库事务有什么作用
简单来说,数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:要么全部执行成功,要么全部不执行。
(2)关系型数据库(例如:MySQL、SQL Server、Oracle 等)事务都有 ACID 特性:
Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;Consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障
MySQL 中并发事务的控制方式无非就两种:锁 和 MVCC。锁可以看作是悲观控制的模式,多版本并发控制(MVCC,Multiversion concurrency control)可以看作是乐观控制的模式。锁 控制方式下会通过锁来显示控制共享资源而不是通过调度手段,MySQL 中主要是通过 读写锁 来实现并发控制。
MVCC 是多版本并发控制方法,即对一份数据会存储多个版本,通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号,版本号是唯一的。
MVCC 在 MySQL 中实现所依赖的手段主要是: 隐藏字段、read view、undo log。
缓存穿透说简单点就是大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中 。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
有哪些解决办法?
1)缓存无效 key
如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
2)布隆过滤器
具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
有哪些解决办法?
缓存穿透和缓存击穿有什么区别?
实际上,缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。 这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。另外,缓存服务宕机也会导致缓存雪崩现象,导致所有的请求都落到了数据库上。
有哪些解决办法?
针对 Redis 服务不可用的情况:
针对热点缓存失效的情况:
缓存雪崩和缓存击穿有什么区别?
缓存雪崩和缓存击穿比较像,但缓存雪崩导致的原因是缓存中的大量或者所有数据失效,缓存击穿导致的原因主要是某个热点数据不存在与缓存中(通常是因为缓存中的那份数据已经过期)
(1)Redis 支持持久化,而且支持 3 种持久化方式
(2)快照(RDB)
Redis 可以通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
(3) 只追加文件(append-only file, AOF)
AOF 持久化功能的实现可以简单分为 5 步:
write函数(系统调用),write将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。fsync 策略)向硬盘做同步操作。这一步需要调用 fsync 函数(系统调用), fsync 针对单个文件操作,对其进行强制硬盘同步,fsync 将阻塞直到写入磁盘完成后返回,保证了数据持久化。基于内存操作:Redis的数据是存储在内存中的,内存的访问速度远远超过硬盘。因此,与传统的基于磁盘的数据库相比,Redis可以提供非常快的读写速度。
简单的数据结构:Redis支持的数据结构相对简单,如字符串、列表、集合、哈希等。这些数据结构的操作非常直接,减少了复杂查询处理的开销。
单线程模型:Redis使用单线程模型来处理命令,避免了常见的多线程上下文切换和资源竞争的开销。虽然它是单线程的,但由于内存存储和高效的数据结构,Redis仍然能够处理上万到几十万的QPS(每秒查询数)。
持久化策略:虽然Redis主要是一个内存数据库,但它提供了几种灵活的持久化方法,如RDB快照和AOF日志文件。这些方法可以根据需要配置,以平衡性能和数据安全性。
优化的网络协议:Redis使用了一种简单的文本协议RESP(Redis Serialization Protocol)。该协议设计得很简单,因此客户端和服务器之间的数据交换非常快速。
事件驱动模型:Redis使用了事件驱动模型来处理并发连接,这使得Redis能够高效地处理大量并发客户端。
地址解析:
建立TCP连接:
发送HTTP请求:
服务器处理请求并返回HTTP响应:
浏览器解析并渲染页面:
加载嵌套的资源:
执行JavaScript:
关闭连接:
(1)三次握手的步骤如下:
(2)四次挥手的步骤如下:
(3) 三次握手原因:
防止已失效的连接请求报文段突然传到了服务端:考虑一个场景,客户端发送了第一个连接请求,但是由于网络原因这个请求被延迟了,于是TCP又发送了一个连接请求。当网络好转时,两个连接请求几乎同时到达服务端,如果此时是两次握手,服务端就会建立两个连接,但客户端只建立了一个连接,这就造成了服务端资源的浪费。
更为可靠地确认双方的接收与发送能力:三次握手可以确保双方都有接收和发送消息的能力。两次握手无法保证这一点。
设定序列号:三次握手还可以使得双方都能为TCP连接初始的序列号达成一致


(1)HTTP与HTTPS的区别
安全性:
端口:
性能:
证书:
(2) HTTP请求结构
一个HTTP请求主要包含以下部分:
(3) 请求头的作用
请求头在HTTP请求中扮演了重要的角色,它为服务器提供了关于客户端请求的一些信息。以下是请求头的一些常见用途:
Content-Type头部,客户端可以告诉服务器发送的数据是什么格式,如application/json或text/html。Content-Length头部,指示请求或响应体的大小。Authorization头部用于包含凭据,通常用于API认证。Cache-Control和其他相关的头部可以控制如何缓存响应内容。User-Agent头部描述了发出请求的客户端类型,如浏览器或其他客户端应用。Accept头部,客户端可以告诉服务器它希望收到哪种类型的响应。Cookie头部可以包含服务器设置的任何cookie,它们在每个请求中发送回服务器。Origin头部表示请求来自哪个源,与CORS(跨来源资源共享)策略相关。(1)OSI 七层模型是一个标准化的网络协议族层次划分,每一层都有特定的功能和责任。从上到下,这些层次是:
应用层(Application Layer)
表示层(Presentation Layer)
会话层(Session Layer)
传输层(Transport Layer)
网络层(Network Layer)
数据链路层(Data Link Layer)
物理层(Physical Layer)
(2)TCP/IP 模型是实际使用最为广泛的网络协议族结构模型,它简化了 OSI 模型层次划分,主要包括以下几层:
应用层(Application Layer)
传输层(Transport Layer)
网络层(Network Layer)
链路层(Link Layer)