一、java
1.JVM的内存管理机制:
(1)内存区域
- 程序计数器:通过改变计数器的值来选择下一条所需执行的字节码指令
- 虚拟机栈:Java方法执行的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表:存放编译期可知的基本数据类型(boolean、byte、char、int等)、对象引用(reference类型)和 returnAddress类型(指向一条字节码指令的地址)
- 本地方法栈:本地方法执行的栈帧
- 堆:存放对象实例和数组
- 方法区:存储被虚拟机加载的Class类信息、final常量、static静态变量、即时编译器编译后的代码等数据,运行时常量池
jdk1.8仍然保留方法区的概念,只不过实现方式不同。取消永久代,方法存放于元空间,元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中。
(2)类加载
class即为二进制文件。将二进制文件的静态存储结构转化为方法区的运行时数据结构,并利用二进制流文件创建一个对象,存储在 Java 堆中用于对方法区的数据结构引用的入口。
双亲委派机制:一种层级结构,当一个类加载器需要加载一个类时,首先委派其父类加载器去尝试,只有父类无法加载时,子类加载器才尝试加载。保证了java类的唯一性,避免重复加载同一个类。
(3)垃圾回收
(1)哪些内存需要回收?
堆和方法区(元空间)
程序计数器、虚拟机栈、本地方法栈3个区域是随线程而生,随线程而灭的
(2)什么时候回收?
判断对象是否可以回收,有两种比较经典的判断策略。
- 引用计数算法,count+1-1,=0时失效
- 可达性分析算法,为起点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots 没有任何引用链相连时。
-
可以作为GC Root 引用点的是:
JavaStack中的引用的对象。
方法区中静态引用指向的对象。
方法区中常量引用指向的对象。
(3)如何回收?
- 标记-清除(Mark-Sweep)算法,大量不连续的内存碎片
- 复制(Copying)算法,(新生代)当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。触发Minor GC,当新生代无法为新生对象分配内存空间的时候。
- 标记-整理(Mark-Compact)算法,(老生代)让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。触发Major GC,老年代空间不足,方法区空间不足,Minor GC 引发(年轻代的对象在经历Minor GC 过后,部分对象存活对象或全部存活对象会进入老年代)。
(4)G1回收器(代替cms)
G1的目标是在满足用户指定的停顿时间目标的同时,实现高吞吐量的垃圾收集。
工作流程:
-
初始标记:暂停所有应用线程,标记根对象及其直接关联的对象。
-
并发标记:尽可能地与应用程序并发执行,标记整个堆中的对象。
-
最终标记:暂停应用线程,执行最终的标记操作,标记从并发标记阶段开始到当前时刻有可能被修改的对象。
-
筛选回收:在最终标记之后,G1回收器会对堆中的存活对象进行计数,并根据各个区域的回收价值执行部分收集操作。
特点:
- G1回收器的特点包括
- 高效的垃圾收集
- 可预测的停顿时间、每次垃圾收集时,只处理部分区域,以达到更均匀的停顿时间
- 并行与并发执行、既可以在并发阶段与应用程序并发执行,又可以在需要暂停应用程序的阶段并行地处理垃圾收集
- 支持大堆和非规则内存布局。
(5)调优
大多数是代码问题,比如过长的递归或者什么,少数情况确实需要,频繁GC等可以考虑升级垃圾回收器等,调整各个代使用内存的大小,花费在垃圾收集上的时间调整为执行时间的一定比例(5%)以内。
2.JMM
(1)什么是JMM
java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。
(2)保证原子性、内存可见性和有序性
用锁
(3)happens-before 规则
为了保证前一个操作的结果对后续操作时可见的
-
程序顺序规则:在单线程中,程序按照代码顺序执行,操作间的顺序关系由程序的顺序来确定。
-
锁定规则:一个线程在释放锁之后,另一个线程在获取该锁之前,释放锁的操作先于获取锁的操作。
-
volatile 变量规则:一个线程对 volatile 变量的写操作对另一个线程对该变量的读操作具有可见性。
-
线程启动规则:一个线程的启动操作先于该线程的任何操作。
-
线程终止规则:一个线程的所有操作先于其他线程检测到该线程已经终止。
-
传递性规则:如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。
(3)八种内存交互操作
3.内存泄漏
指程序中已经不再需要的对象或者资源仍然被保留在内存中,导致内存占用不断增加
-
对象持有引用未释放:当一个对象持有对另一个对象的引用时,如果这个引用未能及时释放,就会导致被引用对象无法被垃圾回收。例如,单例模式中的静态变量持有了对象的引用,但在程序执行过程中未能正确释放。
-
集合类引起的泄漏:如果集合类持有对对象的引用,并且在程序中未及时清理这些引用,就可能导致内存泄漏。例如,HashMap、ArrayList等集合类持有了对象的引用,如果没有正确地从集合中移除对象,就会导致对象无法被释放。
-
线程引起的泄漏:如果程序中创建了大量的线程,但这些线程没有正确地终止或者释放资源,就可能导致内存泄漏。例如,线程创建后未能正确地销毁,或者线程持有了对对象的引用,但在执行完任务后未能释放。
-
资源未关闭:在Java中打开的一些资源,如文件流、数据库连接等,如果在使用完后未能正确关闭,就会导致内存泄漏。例如,在使用完数据库连接后未能正确关闭连接,就可能导致连接未能被释放。
4.线程池
多线程run里面写具体方法,然后start运行。
是一种管理和重用线程的机制,它可以在应用程序中创建一组线程,并在需要执行任务时分配这些线程,从而避免了频繁创建和销毁线程的开销
(1)工作流程
-
线程池创建: 线程池通常由一个线程池管理器和一组工作线程组成。
-
任务提交: 当应用程序需要执行一个任务时,可以将任务提交给线程池。任务可以是一个
Runnable
对象或者是一个Callable
对象。 -
任务队列: 线程池通常包含一个任务队列,用于存储提交给线程池的任务。当有任务提交时,线程池会将任务添加到任务队列中。
-
线程调度: 线程池管理器会根据任务队列中的任务情况和线程池的策略来调度工作线程执行任务。线程池的策略可以是先进先出(FIFO)、后进先出(LIFO)或者优先级调度等。
-
线程执行: 当工作线程空闲时,线程池会从任务队列中取出一个任务分配给空闲线程执行。工作线程执行任务的过程包括调用任务的
run()
方法(对于Runnable
任务)或者call()
方法(对于Callable
任务),执行任务的代码逻辑。 -
线程池维护: 线程池会周期性地检查线程池中的工作线程情况,包括空闲线程数量、活动线程数量等,并根据需要动态调整工作线程的数量,以确保线程池的性能和资源利用率
(2)拒绝策略
定义了当线程池已满并且无法处理新提交的任务时应该采取的操作
-
AbortPolicy(默认策略): 默认的拒绝策略是抛出异常,表示拒绝新任务的提交。这是默认的拒绝策略,当线程池已满且队列已满时会抛出异常。
-
CallerRunsPolicy: 在调用者线程中直接执行被拒绝的任务。这意味着任务不会被丢弃,而是由调用者线程直接执行。这种策略通常用于保证任务的执行,但可能会降低性能。
-
DiscardPolicy: 直接丢弃被拒绝的任务,不做任何处理。
-
DiscardOldestPolicy: 丢弃队列中等待时间最长的任务,并尝试重新提交新的任务。这样可以保留较新的任务而丢弃较旧的任务,以便为新任务腾出空间。
(3)Runnable和Thread和Callable
Thread实现了Runnable接口,提供了更多的可用方法和成员。
Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
runnable没有返回值,而实现callable接口的任务线程能返回执行结果
callable接口实现类中的run方法允许异常向上抛出,可以在内部处理,try catch,但是runnable接口实现类中run方法的异常必须在内部处理,不能抛出
通常与ExecutorService
一起使用,可以提交Callable
任务给线程池执行,并且可以获得返回值。
(4)Executor框架(接口,线程池是一种实现)
提供了一种管理和执行线程任务的机制,核心是Executor
接口和其子接口ExecutorService
。
Executor
将任务的执行和任务的提交进行了解耦
ExecutorService
提供了更丰富的任务执行和线程管理功能。定义了一系列的方法,如提交任务、关闭线程池、管理线程等。
(5)ThreadLocal
是一个类,实现了将数据与线程绑定,使得多线程环境下可以方便地存取线程私有的数据,而不需要担心线程间的数据共享和竞争
-
ThreadLocalMap:每个线程都有一个私有的
ThreadLocalMap
对象,用于存储线程私有的变量副本。实际上是一个哈希表 -
ThreadLocal对象:每个
ThreadLocal
对象都对应着一个线程私有的变量副本。当一个线程通过ThreadLocal
对象的set
方法设置变量值时,实际上是将变量值存储到当前线程的ThreadLocalMap
中,并以ThreadLocal
对象作为键。当需要获取变量值时,线程可以通过ThreadLocal
对象的get
方法从当前线程的ThreadLocalMap
中获取对应的变量值。 -
Thread对象:每个线程对象都有一个
threadLocals
属性,用于存储线程私有的ThreadLocalMap
对象。当一个线程创建ThreadLocal
对象并调用set
方法时,会将ThreadLocal
对象和变量值存储到当前线程的threadLocals
属性中。
(6)CountDownLatch
一个页面上,显示很多信息,比如商品销量,总销售额,订单信息这些,可以开多线程,做完之后合并。通常习惯开的数量是cpu核心数-1
CountDownLatch可以使一个或多个线程等待其他线程各自执行完毕后再执行。
让主线程等所有的子线程都执行完之后再对每个线程统计的指标进行聚合
(7)sleep和wait
sleep()
用于暂时放弃 CPU 时间片,但不释放对象锁,而 wait()
用于释放对象锁,并进入等待状态,直到被其他线程唤醒。因此,sleep()
适用于需要暂时停止执行一段时间的场景,而 wait()
适用于线程之间的协作,等待特定条件满足后再继续执行。
sleep()
方法是 Thread 类的静态方法。
wait()
方法是 Object 类的实例方法。方法必须在同步块(synchronized block)中调用,并且当前线程必须持有该对象的锁才能调用。
(8)三个线程交替打印
public class Printer {
private int state;//当前状态
private int times;//打印次数
private static final Object LOCK = new Object();//一个静态的对象,用来进行线程同步。
public Printer(int times) {
this.times = times;
}
private void printLetter(String name, int targetState) {
for (int i = 0; i < times; i++) {
synchronized (LOCK) {
while (state % 3 != targetState) {
try {
LOCK.wait();//线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
state++;
System.out.print(name);
LOCK.notifyAll();//唤醒其他等待的线程
}
}
}
public static void main(String[] args) {
Printer printABC = new Printer(10);
new Thread(() -> {
printABC.printLetter("A", 0);
}).start();
new Thread(() -> {
printABC.printLetter("B", 1);
}).start();
new Thread(() -> {
printABC.printLetter("C", 2);
}).start();
}
}
5.锁(volatile,synchronized,Lock,ReentrantLock锁)
(1)使用volatile关键字:保证变量的读写操作都在主内存中进行,但不保证原子性。
- 保证线程间变量的可见性。
- 禁止CPU进行指令重排序。
(2)使用synchronized关键字:使用synchronized关键字可以确保同一时刻只有一个线程可以访问共享变量,从而避免了多个线程同时访问变量导致的可见性问题。
(3)使用Lock对象:使用Lock对象进行同步操作也可以保证内存可见性。
(4)juc的ReentrantLock(通过循环调用CAS操作来实现加锁。)等待可中断、公平锁
cas:对比初始值一样,则修改,不一样则修改失败
(5)乐观锁和悲观锁
-
获取时机:乐观锁在操作之前不会加锁,而是在提交操作时检查数据版本;悲观锁在操作之前会先获取锁,确保其他事务无法同时访问数据。
-
对并发冲突的处理方式:乐观锁通过版本号或者时间戳等方式标识数据版本,当发现版本冲突时,通常会采取重试、回滚等策略来处理;悲观锁通过加锁来阻止其他事务访问数据,等待锁的释放。
-
并发性能:乐观锁不会阻塞其他事务的访问,因此通常并发性能较高;悲观锁在锁定数据期间会阻塞其他事务的访问,可能会影响系统的并发性能。
6.代理的实现原理
(1)好处:
-
访问控制:代理可以控制对目标对象的访问权限。
-
远程调用:客户端可以像调用本地对象一样调用远程对象。
-
延迟加载:在真正需要使用目标对象时才进行加载。
-
分布式系统中的负载均衡:代理可以实现负载均衡策略,将请求分发到多个目标对象上,从而提高系统的并发处理能力。
(2)用法:
是一种结构型设计模式,用于控制对对象的访问。代理对象充当客户端和目标对象之间的中间人,可以拦截对目标对象的访问,并在访问前后执行额外的逻辑。
静态代理是代理类在代码运行前已经创建好,并生成class文件;动态代理类 是代理类在程序运行时创建的代理模式。
动态代理:基于接口的代理(JDK动态代理)和基于类的代理(CGLIB,code-generation-lib代理)。
JDK动态代理要求目标类实现接口,通过Proxy类的newProxyInstance方法创建代理对象
CGLIB代理则是通过继承目标类的方式创建代理对象,不要求目标类实现接口,由于是继承方式,如果是 static方法,private方法,final方法等描述的方法是不能被代理的。
7.抽象类和接口的区别
- 抽象类是一个类,可以包含抽象方法和具体方法,也可以包含成员变量和构造方法;接口是一种纯抽象的概念,只能包含方法的声明。
- 类只能继承一个抽象类,但可以实现多个接口。
- 子类继承抽象类时必须实现抽象类中的所有抽象方法;实现接口时,类必须实现接口中的所有方法。
- 抽象类的目的是为了共享子类之间的通用代码,而接口的目的是为了实现类的多态性和行为规范。
8.面向对象三大特性(封装、继承和多态):
-
封装:
封装是指将属性和方法封装在类中,并对外部隐藏对象的具体实现细节,只提供公共的接口供外部使用。封装可以有效地保护对象的内部状态,防止外部直接访问对象的私有数据,提高了代码的安全性和可维护性。通过封装,可以将对象抽象为一个黑盒子,用户只需要关注对象的接口,而不需要关心内部实现细节。
-
继承:
(1)继承是指子类可以继承父类的属性和方法,并且可以在此基础上进行扩展或修改。继承可以实现代码的重用,避免重复编写相似的代码,并且可以通过创建子类来实现不同类之间的关系,提高了代码的可扩展性和可维护性。通过继承,可以构建类的层次结构,实现代码的抽象和泛化。 Diamond继承问题的避免:多继承会引入Diamond继承问题,即当一个类继承了两个不同的类,而这两个父类又继承了同一个父类时,就会形成钻石继承结构。这种情况下,如果不加以处理,可能会引发歧义和冲突。通过只支持单继承,Java避免了这种复杂性。
-
多态:
多态是指同一个接口可以有多种实现方式,即同一个方法可以在不同的对象上表现出不同的行为。多态可以通过方法重载(Overloading)和方法重写(Overriding)来实现。方法重载是指在同一个类中定义多个同名但参数列表不同的方法,而方法重写是指子类重写(覆盖)父类的方法。多态提高了代码的灵活性和可扩展性,可以通过调用父类的引用来调用不同子类的方法,从而实现基于对象的行为差异化。
9.反射
是指在运行时检查和操作类、对象、方法和属性等程序结构的能力。通过反射,可以在运行时获取类的信息、调用类的方法、操作类的属性等。
Java 反射主要涉及以下几个核心类:
-
Class
类:Class
类是 Java 反射的核心类之一,它表示 Java 类的类对象。通过Class
类,可以获取类的信息,如类的名称、方法、属性等。可以通过类的全限定名或对象的getClass()
方法来获取类的Class
对象。 -
Constructor
类:Constructor
类表示类的构造方法,可以通过Constructor
类来实例化对象。 -
Method
类:Method
类表示类的方法,可以通过Method
类来调用类的方法。 -
Field
类:Field
类表示类的属性,可以通过Field
类来获取和设置类的属性值。
10.Object类
Object
类是所有类的根类,也是所有类的超类。
-
equals(Object obj): 方法用于比较两个对象是否相等。默认情况下,
equals()
方法比较的是对象的引用,即两个对象只有在引用地址相同时才被认为是相等的。子类可以重写equals()
方法来提供更具体的相等性比较逻辑。 -
hashCode(): 方法用于返回对象的哈希码值,它通常被用于散列算法和集合类中。对于相同的对象,
hashCode()
方法应该返回相同的值,但是对于不同的对象,返回相同值的哈希码并不一定意味着它们相等。 -
toString(): 方法用于返回对象的字符串表示。默认情况下,
toString()
方法返回对象的类名,后跟 '@' 符号和对象的哈希码值的十六进制字符串表示。通常,子类会重写toString()
方法来提供更有意义的字符串表示。 -
getClass(): 方法返回对象的运行时类对象,即
Class
类的实例。通过getClass()
方法可以获取对象的类型信息。 -
notify()、notifyAll() 和 wait():这组方法用于线程间的通信和同步。
notify()
方法唤醒等待该对象监视器的一个线程,notifyAll()
方法唤醒所有等待该对象监视器的线程,而wait()
方法使当前线程进入等待状态,直到其他线程调用相同对象的notify()
或notifyAll()
方法唤醒它。
11.深拷贝和浅拷贝
浅拷贝:浅拷贝只是对对象的顶层进行拷贝,不会递归拷贝对象的内部引用类型成员。可以使用 clone()
方法来实现浅拷贝。
深拷贝:深拷贝会复制对象的所有层级。使用序列化与反序列化的方式。
二、操作系统
1.死锁
(1)四个必要条件
互斥条件:资源是独占的且排他使用
不可剥夺条件:不被其他进程强行剥夺
请求和保持条件:在申请新的资源的同时,继续占用已分配到的资源
循环等待条件:进程等待队列形成一个进程等待环路
死锁预防:
(不可剥夺条件)不能获得所需要的全部资源时便处于等待状态,等待期间可以被剥夺
(请求与保持条件)每个进程在开始执行时就申请他所需要的全部资源
(循环等待)将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行
死锁避免:系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。
2.进程
(1)进程间通信
(1)管道pipe:管道是一种半双工的通信方式
(2)消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。
(3)共享内存:映射一段能被其他进程所访问的内存
(4)套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
(2)进程的三个状态及转换
(3)进程调度策略
先来先服务:可能导致长作业等待时间,不适合响应时间要求较高的场景。
最短作业优先:可以最小化平均等待时间,但需要预先知道每个进程的执行时间,不太适用于实时系统。
优先级调度:为每个进程分配一个优先级,优先级高的进程先执行。
轮转调度:按照时间片轮转的方式进行调度。这种方式可以确保公平性,但可能导致上下文切换频繁。
多级反馈队列调度:将进程划分为多个优先级队列,不同优先级的队列具有不同的时间片大小或调度算法。进程在不同队列之间移动,可以根据其执行情况动态地调整优先级。
(4)进程,线程,协程
(1)区别
进程是操作系统中的独立执行单位,拥有独立的地址空间和系统资源;线程是进程内的执行单位,共享进程的地址空间和资源;协程是用户态的轻量级线程,由程序员控制调度和执行。
(2)通信
进程之间通常通过进程间通信(IPC)来进行数据交换;线程之间可以直接访问共享变量和数据结构;协程通常通过消息传递或者共享状态来进行通信。
(3)为什么要有线程
并发执行,资源共享、轻量级
(4)为什么要有协程
不由操作系统管理,而是由用户空间管理,切换等过程不需要操作系统的介入,更快更小资源开销
(5)协程缺点
-
无法利用多核处理器的优势:由于协程通常运行在单个线程中,因此无法充分利用多核处理器的性能优势
-
阻塞操作影响整体性能:如果协程中存在阻塞操作(如IO操作),则整个协程都会被阻塞,线程可以异步IO
-
内存管理较为复杂:由于协程共享进程的地址空间,因此在协程中共享数据时需要考虑线程安全和同步的问题。
(6)判断线程消亡
getState()或者监控线程池
(5)并发和并行的区别
执行方式:并发是任务之间交替执行,共享资源,而并行是任务同时在独立的处理单元上执行,彼此之间互不干扰。
目标:并发的目标是提高系统的响应性和资源利用率,而并行的目标是加速计算,提高系统的整体性能。
应用场景:并发通常用于多任务之间的交互、同步和通信的场景,而并行通常用于大规模计算、科学计算等需要高性能的场景。
3.虚拟内存
主要i通过地址映射允许程序访问一个比实际物理内存更大的地址空间。
(1)页式存储
逻辑内存划分为页面,物理内存划分为页框
页表映射关系: 使用页表(Page Table)来记录逻辑页面与物理页框的映射关系。每个进程都有自己的页表。
优点:不需要连续的内存空间。更好地保护系统的核心内存区域,防止用户程序意外修改。
缺点:页表开销和访问开销
(2)页面置换算法
-
最佳页面置换算法(OPT):理论上最佳的置换算法,每次选择淘汰最长时间内不会被使用的页面。
-
先进先出页面置换算法(FIFO):选择最早进入内存的页面进行置换,可能导致Belady现象(即增加页面数目时,缺页率反而增加)。
-
最近最久未使用页面置换算法(LRU):选择最近最久未使用的页面进行置换,根据局部性原理,实现通常维护一个有序链表。
-
最近最不常用(LFU):如果数据过去被访问多次,那么将来被访问的频率也更高。解决了偶尔被访问一次之后,数据留存在缓存中很长一段时间的问题
-
时钟页面置换算法(最近未使用算法NUR):使用一个环形链表来组织页面,每个页面有一个位(也称为访问位),表示该页面是否被访问过。当需要置换页面时,时钟算法会顺序地查找页面,如果某个页面的位为0,则选择该页面进行置换;如果位为1,则将位设置为0,表示给该页面第二次机会。
(3)32位1G的物理内存可以申请2G内存吗
在一个32位系统中,一个进程的虚拟地址空间大小通常是4GB(2^32),可以申请,但物理内存限制了用不到这么大。
4.linux
(1)命令:
top:性能分析工具,能够实时显示系统中各个进程的资源占用状况。退出 top 的命令为 q (在 top 运行中敲 q 键一次)。
ps:ps命令用于报告当前系统的进程状态。ps命令是最基本同时也是非常强大的进程查看命令,使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。
free:可以显示当前系统未使用的和已使用的内存数目,还可以显示被内核使用的内存缓冲区
(2)端口
端口范围通常是从0到65535。这个范围中的端口被分为三类:
-
系统端口(Well-known Ports):范围从0到1023。这些端口通常用于系统服务和常见网络应用,例如HTTP(80端口)、SSH(22端口)、FTP(21端口)等。在Linux系统中,需要root权限才能使用这些端口。
-
注册端口(Registered Ports):范围从1024到49151。这些端口是注册给用户应用程序的,它们通常被用于自定义服务和应用程序。
-
动态端口(Dynamic or Private Ports):范围从49152到65535。这些端口通常由客户端程序使用,用于临时通信。
5.软连接硬链接
硬链接直接连在文件上,软连接是一个指向目标文件路径的符号链接,而不是指向目标文件的实际数据。
三、计网
1.输入网址到显示(Http请求的过程与原理)
1、输入网址
2、DNS解析获取域名对应的IP地址
3、建立TCP连接
4、web浏览器向web服务器发送HTTP请求
5、服务器的永久重定向响应
6、浏览器跟踪重定向地址
7、web服务器做出应答
8、浏览器显示 HTML
9、浏览器发送请求获取其他嵌入在 HTML 中的资源
10、web服务器关闭TCP连接
2.DNS
是互联网中用于将域名解析为 IP 地址的分布式数据库系统
- 查找本地缓存:首先查找本地缓存中是否有对应域名的解析记录,如果有则直接返回对应的 IP 地址。
- 向 DNS 服务器发送查询请求:如果本地缓存中没有对应的解析记录,域名解析器会向 DNS 服务器发送查询请求。
- 递归查询过程: DNS 服务器会依次向根域名服务器、顶级域名服务器、权威 DNS 服务器查询,直到找到对应的 IP 地址,并将结果返回给域名解析器。
- 返回结果:域名解析器将查询到的 IP 地址返回给用户。
3.TCP
(1)三次握手、四次挥手
(1)握手
A:在吗?上号吗? B:在的,上号 A:上号
(2)挥手
Client端等待2MSL的时间后依然没有收到回复,则证明Server端已正常关闭,那好,Client端也可以关闭连接了。
(3)为什么要等待2MSL?
第一点:保证TCP协议的全双工连接能够可靠关闭
第二点:保证这次连接的重复数据段从网络中消失
(4)解决TIME_WAIT过多的方法主要有以下几种:
-
调整操作系统参数:可以通过修改操作系统的参数来调整TIME_WAIT状态的持续时间。例如,调整操作系统的TCP连接超时参数或TCP连接重用参数。
-
重用端口:这样可以更快地重新使用相同的端口进行新的连接。
-
优化应用程序设计:优化应用程序的设计,尽量减少短暂连接的频率,避免频繁地打开和关闭连接。
-
负载均衡:使用负载均衡器将连接分发到多个服务器上,可以减少单个服务器上TIME_WAIT状态的数量。
-
TCP连接池
(5)为什么是三次和四次?
握手如果是两次:A:在吗?上号吗?B:在的,上号。此时A把B鸽了,B白白等了半天。
挥手四次是因为:多了一次,当Server端收到FIN报文并返回ACK报文段,表示它已经知道Client端没有数据发送了,但是Server端还是可以发送数据到Client端的,所以Server很可能并不会立即关闭SOCKET,直到Server端把数据也发送完毕。
(2)拥塞控制
(1)目标
- 避免网络拥塞:通过监控网络状态、调整数据流量等手段,尽可能地避免网络拥塞的发生,防止网络性能下降。
- 公平分配带宽:在网络拥塞的情况下,合理地分配带宽资源,确保各个数据流的公平性,防止某些流量占用过多带宽而导致其他流量受阻。
- 维持网络稳定性:通过调整发送速率、拥塞窗口大小等参数,使网络保持在合适的负载下运行,维持网络的稳定性和可靠性。
- 优化网络性能:在网络拥塞的情况下,通过合理的拥塞控制算法和策略,尽可能地优化网络性能,提高数据传输的效率和质量。
(2)算法
- 主要是调整滑动窗口(拥塞窗口)大小来进行控制。
- 慢启动:TCP连接刚开始时,发送方将初始拥塞窗口设置为一个很小的值,在每次接收到一个确认时,发送方将拥塞窗口的大小加倍,直到达到网络的容量上限或出现丢包。
- 拥塞避免:不像慢启动那样快速增加,发送方通过每个往返时间(RTT)内的确认数来调整拥塞窗口的大小
- 快速重传:用于快速检测丢失的报文段并尽快进行重传,发送方连续收到三个重复的确认时,就会认为某个报文段丢失,并立即重传该报文段。
- 快速恢复:收到三个重复确认时,不立即进行慢启动,而是将拥塞窗口减半,然后进入拥塞避免状态。能够更加平滑地调整拥塞窗口的大小
(3)UDP
tcp可靠,udp不可靠
基于UDP,加入确认机制,重传机制和缓存机制,UDP可以实现TCP
是两种不同的协议,不能用同一个端口
(4)为什么可靠
-
序列号与确认应答:TCP在每个数据包中使用序列号来标识数据的顺序,并且接收方会发送确认应答(ACK)来确认已收到的数据。如果发送方在一定时间内未收到确认应答,它会重新发送数据,以确保数据的可靠传输。
-
流量控制:TCP通过流量控制机制来确保发送方和接收方之间的数据传输速率匹配。接收方可以发送窗口大小(window size)来告知发送方它还能够接收多少数据,以防止发送方发送过多的数据导致接收方无法及时处理。
-
拥塞控制:TCP使用拥塞控制机制来防止网络拥塞并维持网络的稳定性。
-
顺序控制:TCP保证数据在传输过程中的顺序不会被打乱,即使网络中的数据包到达顺序与发送顺序不一致,接收方也会对数据进行重新排序。
4.HTTP(s)
(1)状态码:
-
1xx(信息性状态码):表示请求已被接受或正在进行处理
-
2xx(成功状态码):表示请求已成功处理
-
3xx(重定向状态码):表示需要客户端采取进一步的操作才能完成请求
-
4xx(客户端错误状态码):表示客户端的请求有错误,需要修正
-
5xx(服务器错误状态码):表示服务器在处理请求时发生了错误。
(2)http
-
建立连接:客户端通过TCP连接向服务器发起请求。默认情况下,HTTP使用TCP端口80。
-
发送请求:客户端向服务器发送HTTP请求,请求包括请求行(GET、POST等)、请求头(客户端)和请求体(信息)。
-
处理请求:
-
发送响应:服务器生成HTTP响应,包括响应行(200,502)、响应头(服务器信息)和响应体(数据)。
-
关闭连接:HTTP是一种无状态协议,即每个请求都是独立的,服务器不会保留客户端的状态信息。在响应完成后,客户端和服务器会断开连接。
(3)https
在HTTP基础上加入了安全套接字层(SSL/TLS)协议的通信协议。和http不是一个端口
- 客户端发送支持的SSL/TLS版本和加密算法列表
- 服务器选择SSL/TLS版本和加密算法
- 服务器发送数字证书:服务器将自己的数字证书发送给客户端,证书包含了服务器的公钥以及相关信息,证明服务器的身份。
- 客户端验证数字证书:客户端使用预置的证书颁发机构(CA)列表来验证服务器的数字证书是否有效。
- 客户端生成随机数和密钥:客户端生成一个随机数作为对称加密的密钥,并使用服务器的公钥加密该密钥,然后发送给服务器。
- 建立安全通道:
(4)http1.1、2.0、3.0
http1.1:
- 持久连接(长连接也是):引入了持久连接(Persistent Connections),允许在单个TCP连接上发送多个HTTP请求和响应,减少了连接的建立和关闭次数,提高了性能。
- 管道化:允许在一个TCP连接上同时发送多个请求,但请求必须按照顺序发送,且响应也必须按照顺序返回。
- 增加了一些头部字段:如Host头字段,允许一个服务器主机提供多个域名的服务。
- 引入了分块传输编码(Chunked Transfer Encoding):允许服务器将响应分成多个块,有助于支持动态内容和长连接。
http2.0:
-
多路复用: 可以在单个连接上同时发送多个请求和响应,而HTTP/1.0每次请求都需要建立新的连接。
-
头部压缩: HTTP/2.0使用头部压缩来减少数据传输的大小。在HTTP/1.0中,每个请求和响应的头部都需要重复传输,而HTTP/2.0使用了一种压缩算法,可以有效地减少头部的大小,从而降低了带宽的使用量。
-
二进制协议: HTTP/2.0是一个二进制协议,而HTTP/1.0是基于文本的协议。二进制协议的优势在于可以更高效地进行解析和处理,而且更容易实现更复杂的功能。
-
服务器推送: HTTP/2.0支持服务器推送功能,服务器可以在客户端请求之前将相关资源推送给客户端,从而减少了客户端需要等待的时间。
-
流控制: HTTP/2.0引入了流控制机制,可以更好地控制数据流量,避免了过多的数据拥塞导致的性能下降。
http3.0:
- 基于UDP:HTTP/3.0采用了基于UDP的传输协议(如QUIC),而不是TCP。这样可以减少连接建立的延迟,并通过错误恢复和拥塞控制提高了性能和可靠性。
- 多路复用:继承了HTTP/2.0的多路复用功能,但在基于UDP的传输协议上实现。
- 头部压缩:与HTTP/2.0类似,HTTP/3.0也使用了头部压缩技术来减少网络开销。
- 0-RTT握手:允许在没有完全验证服务器身份的情况下发送数据,主要适用于对连接建立时间敏感、对安全性要求不那么严格的场景,如移动应用、视频流等。减少连接建立的延迟。
(5)http缓存:cookie、session
(1)session在服务器,cookie在客户端。
(2)分布式session,(2台机器登录,如何保存信息)
一些常见的分布式Session管理方案包括:
-
基于数据库的Session管理:将Session数据存储在共享数据库中,不同服务器实例可以访问同一份Session数据。
-
基于缓存的Session管理:将Session数据存储在分布式缓存中,如Redis。不同服务器实例可以共享同一份Session数据,同时通过缓存的特性可以提高读写性能。
-
粘性Session:通过负载均衡器(如Nginx)的配置,将同一个用户的请求路由到同一台服务器实例上,从而保证用户的Session始终在同一台服务器上。这种方式简单,但可能会引入单点故障。
-
无状态Session管理:将Session数据存储在客户端,服务器端不保存Session状态,每个请求都包含完整的会话信息。这种方式避免了服务器端的状态管理,但需要在客户端存储Session数据,有一定的安全风险。
(3)如何保持会话状态
常见的保存会话状态的方式包括:
-
Cookies:存储容量有限,且存在安全性和隐私问题。
-
Session:数据可以存储在内存、数据库或缓存中,可以存储更多的信息,但需要服务器端维护,且需要处理分布式环境下的共享和同步问题。
-
数据库:可以将会话状态数据存储在数据库中,通过用户ID或Session ID来关联用户和会话数据。这种方式可以存储大量数据,并且可以实现跨服务器共享会话状态。
(6)RPC
是两种不同的通信协议
目的和应用场景:HTTP: 主要用于在客户端和服务器之间传输超文本数据。RPC: 用于远程过程调用,允许一个程序请求另一个程序中的函数,而不需要了解底层的通信细节。
通信模型:HTTP: 基于请求-响应模型,客户端发送HTTP请求到服务器,服务器处理请求并返回HTTP响应给客户端。HTTP通常是无状态的,每个请求都是独立的,服务器不会维护客户端的状态信息。RPC: 基于远程过程调用模型,客户端发送请求给远程服务,远程服务执行请求的过程或函数,并将结果返回给客户端。RPC可以是同步的或者异步的,客户端和服务端之间可以建立长期的连接来减少通信开销。
数据传输格式:HTTP: 通常使用文本格式,如HTML、JSON、XML等,用于在客户端和服务器之间传输超文本数据。RPC: 数据格式可以是任何类型,通常根据具体的应用场景和需要来选择,可以是二进制格式、文本格式、序列化格式等。
性能和效率:HTTP: 由于HTTP通常是基于文本的,因此在数据传输和解析方面可能存在一些额外的开销,尤其是在大量数据传输的情况下。RPC: 通常可以更加高效,因为RPC可以使用二进制协议来传输数据,减少了数据解析的开销。
5.ARP
ARP(地址解析协议)是一种用于将IP地址映射到物理机器地址(如以太网地址)的通信协议。当设备想要与本地网络上的另一个设备通信时,它需要知道目标设备的物理地址(MAC地址)。ARP通过在网络上广播请求来解决这个问题,询问哪个设备拥有特定的IP地址。拥有该IP地址的设备然后会回应其MAC地址,从而允许通信继续进行。
安全性:
-
ARP欺骗:攻击者可以发送虚假的ARP响应消息,欺骗网络中其他设备,使它们将通信发送到攻击者控制的设备上。这种攻击可以用来进行中间人攻击。
-
ARP洪泛:攻击者可以发送大量的虚假ARP请求或响应消息到网络上,使网络设备的ARP缓存溢出或混乱,导致网络服务中断或降级。
-
ARP缓存中毒:攻击者可以发送虚假的ARP响应消息到目标设备,将错误的MAC地址映射到正确的IP地址上,导致目标设备发送的数据被发送到错误的目的地。
-
ARP缓存投毒:攻击者可以通过向目标设备发送虚假的ARP响应消息,将目标设备的ARP缓存中的正确条目替换为错误的条目,从而将通信重定向到攻击者控制的设备。
6.CDN
CDN(内容分发网络)是通过位于全球各地的分布式服务器网络,将内容(如网页、图片、视频等静态或动态资源)快速传输给用户的网络架构。
CDN 的工作原理通常包括以下几个步骤:
-
内容存储和缓存:CDN 提供商在全球各地建立了分布式的数据中心,这些数据中心中存储着原始服务器上的内容副本。当用户请求访问某个资源时,CDN 会尝试从距离用户最近的数据中心获取该资源。
-
请求路由和负载均衡:当用户发起请求时,CDN 的负载均衡系统会根据用户的地理位置、网络状况等因素,选择最合适的数据中心来响应请求。
-
内容传输:如果请求的资源在缓存中已经存在,则直接从缓存中获取,否则会从原始服务器获取资源,并缓存到数据中心以备下次访问。
7.Nginx
高性能的开源反向代理服务器,也可以用作负载均衡器、HTTP 缓存、服务器以及 Web 服务器。
-
反向代理: Nginx 可以作为反向代理,接收客户端的请求,然后将请求转发给后端服务器,最后将后端服务器的响应返回给客户端。这种架构可以隐藏真实的后端服务器,并提供负载均衡和高可用性。
-
负载均衡: Nginx 可以将请求分发到多个后端服务器,从而实现负载均衡。它支持多种负载均衡算法,如轮询、加权轮询、IP 哈希等。
-
HTTP 缓存: Nginx 可以缓存静态文件和动态内容,减轻后端服务器的压力,并提高 Web 服务的性能。它支持多种缓存策略和缓存时间设置。
-
虚拟主机: Nginx 支持虚拟主机配置,可以在一台服务器上运行多个域名,每个域名可以配置独立的站点和设置。
8.IO模型
(1)磁盘IO
读操作:两次缓冲区copy,第一次是从磁盘的缓冲区到内核缓冲区,第二次是从内核缓冲区到用户缓冲区(或应用缓冲区),第一次是cpu的copy,第二次是DMA的copy
写操作:应用程序将数据从用户空间copy到内核空间的缓冲区中,除非应用程序显示地调用了sync命令,否则对用户程序来说写操作就已经完成,至于什么时候把数据再写到磁盘由DMA控制,不需要cpu参与。
延迟:
- 寻道时间:把磁头移动到指定磁道上所经历的时间
- 旋转延迟时间 :指定扇区移动到磁头下面所经历的时间
- 传输时间 :数据的传输时间(数据读出或写入的时间)
(2)网络IO
socket通信建立:
- 服务器端:创建一个监听Socket,用于接收客户端的连接请求。客户端创建一个Socket,用于与服务器建立连接。
- 服务器端将监听Socket绑定到特定的IP地址和端口上,以便客户端能够连接到服务器。
- 服务器端调用
listen
函数开始监听来自客户端的连接请求。 - 客户端连接到服务器的IP地址和端口,并发送连接请求。
- 服务器端:调用
accept
函数接受客户端的连接请求,并创建一个新的Socket用于与客户端通信。 - 客户端和服务器端通过各自的Socket进行通信,可以发送和接收数据。
- 通信结束后,客户端和服务器端分别调用
close
函数关闭Socket连接。
读操作:既可以从磁盘读也可以从socket(网卡)。磁盘的一样;socket的,应用程序需要等待客户端发送数据,如果客户端还没有发送数据,对应的应用程序将会被阻塞,直到客户端发送了数据,该应用程序才会被唤醒。
写操作:cpu会把用户缓冲区中的数据copy到内核缓冲区的Socket Buffer中。最后通过DMA方式将内核空间中的Socket Buffer拷贝到Socket协议栈(即网卡设备)中传输。
延迟:
服务器响应延时+带宽限制+网络延时+跳转路由延时+本地接收延时
(3)零拷贝IO
DMA方式将数据从磁盘拷贝到内核缓冲区,减少了从内核缓冲区到用户缓冲区。
由cpu控制,将内核缓冲区的数据直接拷贝到另外一个与 socket相关的内核缓冲区,即kernel socket buffer,后由DMA 把数据从kernel socket buffer直接拷贝给Socket协议栈。
(4)BIO、NIO、IO多路复用、AIO
网络IO流程:
1.read(),内核态
2.等待客户端,socket到内核缓冲区
3.内核缓冲区到用户缓冲区
4.用户进程获取数据。
BIO、NIO、多路复用、AIO的主要区别在于:步骤1和3时,用户进程状态如何。
BIO(同步阻塞IO):一个线程只能处理一个Socket IO连接,高并发时,服务端会启用大量的线程来处理多个Socket,由于是阻塞IO,会导致大量线程处于阻塞状态,导致cpu资源浪费,且大量线程会导致大量的上下文切换,造成过多的开销。
NIO(非阻塞IO):一个线程就可以处理多个Socket连接,当有1000个Socket连接时,用户进程会以轮询的方式执行1000次系统调用判断数据有没有准备好,即会发生1000次用户态到内核态的切换,成本几何上升。
多路复用:NIO会多次执行系统调用进行测试,大大浪费系统的资源,而多路复用IO把轮询多个Socket文件句柄的事情放在内核空间里执行,即让内核负责轮询所有socket(这样就不会有用户态和系统态的切换),大大提高了IO的处理速度,也减少了系统状态切换的开销。
多路复用模式包含三种,即select、poll和epoll,这几种模式主要区别在于获取可读Socket文件句柄的方式。
select:内核检测文件描述符集中有某个或某几个Socket文件句柄就绪(有writefds、readfds、或者有exceptfds),修改相应Socket文件句柄的状态位,表示该Socket已经就绪,以区别其它没有就绪的文件句柄。然后,就产生中断通知用户进程,并返回就绪的文件句柄的总数,只需遍历一遍文件描述符集合,就可以得到就绪的 Socket(可使用FD_ISSET宏函数来检测哪些文件句柄就绪)。
poll:poll使用pollfd结构体,select使用的是fd_set结构,其他的都差不多,管理多个文件描述符也都是采用轮询的方式,然后根据文件描述符的状态进行处理。由于poll使用的链表,故没有最大文件描述符数量的限制,而select监控文件描述符的最大默认数量为1024。
epoll:
- epoll同poll一样,也没有文件句柄的数量限制。
- select/poll每次系统调用时,都要传递所有监控的socket给内核缓冲区,而epoll在执行epoll_create时已经在内核建立了epoll句柄,每次调用epoll_ctl只是在往内核的数据结构里加入新的socket句柄,所以不需要每次都重新复制一次。
- select 和 poll 都是主动轮询,而epoll是被动触发方式。epoll_ctl执行add动作时除了将Socket文件句柄放到红黑树上之外,还向内核注册了该文件句柄的回调函数,当Socket就绪时,则调用该回调函数将文件句柄放到就绪链表,epoll_wait只监控就绪链表就可以判断有没有事件发生了。
AIO(异步非阻塞IO):执行系统调用后,会注册一个回调函数,内核在检测到某Socket文件句柄就绪,调用该回调函数执行真正的读操作,将数据从内核空间拷贝到用户空间,然后返回给用户使用
(5)同步异步概念
- 同步:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
- 异步:异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
- 阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上它们是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是当前函数没有返回而已。
- 非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
9.攻击
(1)中间人攻击
攻击者插入自己在通信的两端之间,以监视、篡改或干扰通信的过程。在中间人攻击中,攻击者通常能够截获通信双方之间传输的数据,并可能修改其中的内容而不被发现。
防范:
-
使用加密通信:通过使用加密协议(如HTTPS、SSH等),可以确保通信在传输过程中受到加密保护,从而使攻击者无法轻易窃取通信内容。
-
数字证书验证:在进行网络通信时,验证通信双方的数字证书可以防止中间人伪装成合法的通信终端。确保通信双方的身份是真实可信的。
-
使用安全连接:确保通过双向身份验证的安全连接进行通信,这可以防止中间人通过伪装成其中一方来截获或篡改通信。
-
公共密钥基础设施(PKI):PKI 可以提供数字证书的管理和分发,确保数字证书的真实性和完整性,从而增强通信的安全性。
(2)SYN攻击
在TCP协议中,当客户端向服务器发送连接请求时,服务器会回复一个SYN-ACK包作为响应,然后等待客户端发送ACK确认包来建立连接。而在SYN攻击中,攻击者发送大量伪造的SYN连接请求给目标服务器,但不发送ACK确认包,导致服务器在等待确认包时耗尽资源
解决SYN攻击的方法主要包括以下几点:
-
SYN Cookies:SYN Cookies会在服务器端生成一个加密的cookie作为响应,而不是实际为每个连接维护一个半开放连接队列。这样即使服务器收到大量的SYN请求,也不会占用过多的资源,因为服务器并不会真正为这些连接保留状态。
-
网络设备过滤:在网络边界设备上设置过滤规则,识别和丢弃来自可疑源地址的大量SYN请求,以减轻服务器的负载。
-
负载均衡:使用负载均衡器来分担服务器的负载,以便更好地应对大规模的SYN攻击。
四、数据库
1.范式
主属性: 所有候选码的属性称为主属性。不包含在任何候选码中的属性称为非主属性或非码属性。
1NF(原子性)-数据项不可分
2NF(唯一性)-每一个非主属性完全函数依赖于任何一个候选码
3NF(独立性)-任何非主属性不依赖于其他非主属性,即在第二范式的基础上,消除了传递依赖
BCNF-消除主属性对于码的部分函数依赖与传递函数依赖
2.事务
(1)四个特性
原子性:事务中的所有操作要么全部执行成功,要么全部失败回滚,不存在部分执行的情况。
一致性:事务的执行不会破坏数据库的完整性约束。事务的执行前后,数据库必须保持一致的状态,即使在事务执行过程中发生了错误或异常情况。
隔离性:多个事务并发执行时,每个事务的操作应该被隔离开来,互相之间不会产生影响。
持久性:持久性确保了一旦事务提交成功,其所做的修改将永久保存在数据库中,即使系统发生故障或崩溃也不会丢失。
(2)隔离级别
脏读:指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚
不可重复读(Update):指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的
幻读(Insert):假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用。
适用场景:(mysql默认可重复读)
读未提交:对一致性要求不高、并发度要求较高的场景,如一些查询较频繁但对数据一致性要求不严格的应用。
读提交:对数据一致性要求较高的场景,如电子商务系统中对库存的管理
可重复读:需要长时间读取数据并确保数据一致性的场景,如金融系统中的账户余额查询
串行化:对数据一致性要求非常严格的场景,如银行转账、股票交易等。
(3)怎么解决幻读
使用更高的事务隔离级别
使用行级锁:在需要读取范围内的数据时,可以使用行级锁来锁定符合条件的数据行,防止其他事务插入或删除这些数据,从而确保查询结果的一致性。
使用MVCC(多版本并发控制):MVCC是一种并发控制机制,用于在数据库系统中实现事务的隔离。通过保存数据的多个版本,MVCC可以确保同一事务在不同时间点读取的数据是一致的,从而避免幻读问题的发生。
使用乐观并发控制:使用版本号或时间戳来标识数据版本,并在事务提交时检查数据版本是否发生变化,如果发生变化则进行回滚或重试操作,从而确保数据的一致性。
(4)MVCC
(1)多版本并发控制
允许多个事务同时读取数据库中的数据,并且在某些情况下允许同时对数据库中的数据进行修改,而不会发生数据冲突或丢失的情况
核心思想是对每个事务创建数据的多个版本,并在事务中读取数据时选择合适的版本。每个事务在执行过程中看到的数据是一个特定版本的快照
(2)工作流程
-
当事务开始时,系统会为该事务创建一个独立的读视图,用于读取数据时的快照。
-
当事务需要读取数据时,选择合适的数据版本,并返回给事务。
-
当事务需要修改数据时,系统会为数据创建一个新版本,并将修改操作记录到Undo Log和Redo Log中。
-
当事务提交时,系统会将修改操作应用到数据库中,并更新数据的版本信息。
-
当事务需要回滚时,系统会根据Undo Log中的记录进行数据恢复。
(3)优点
- 提高了数据库的并发性:由于多个事务可以同时读取数据库中的数据,因此提高了数据库的并发性和吞吐量。
- 提高了事务的隔离性:每个事务看到的数据都是一个特定版本的快照,从而避免了读取数据时的并发冲突。
- 减少了锁冲突:MVCC通过多版本机制减少了锁的使用,避免了锁冲突对数据库性能的影响。
(5)如何保证分布式事务
-
两阶段提交:
- 一种分布式事务协议,通过协调器和参与者之间的消息交换来确保所有参与者都能同意提交或中止事务。
- 协调器向所有参与者发送事务准备请求,参与者执行事务准备操作并将准备好的消息反馈给协调器。
- 如果所有参与者都准备好提交事务,则协调器向所有参与者发送事务提交请求
- 可能存在阻塞和性能问题。
-
三阶段提交:
- 引入第三阶段:协调器根据参与者的响应决定是否提交或中止事务。
- 三阶段提交相比两阶段提交减少了阻塞时间,但仍然存在参与者崩溃等问题。
-
补偿事务:
- 补偿事务是一种通过执行逆操作来撤销已执行操作的方式,用于处理分布式事务中的异常情况。
- 当事务在某个参与者上发生故障或超时时,可以使用补偿事务来回滚或撤销之前的操作,以确保事务的一致性。
3.MySql存储(执行)引擎
InnoDB:
InnoDB是MySQL默认的事务型存储引擎,它支持事务、外键、行级锁、崩溃恢复等功能。
特点:
支持事务和行级锁定,适合于高并发的OLTP(联机事务处理)应用。
支持外键约束,保证数据的一致性和完整性。
具有较好的崩溃恢复能力,能够保证数据的持久性。
适用场景:
对事务完整性要求较高的应用,如电子商务网站、金融系统等。
MyISAM:
MyISAM是MySQL早期的存储引擎,在一些老版本中是默认的存储引擎,但在MySQL 5.5之后被InnoDB替代。
特点:
不支持事务和行级锁定,只支持表级锁定,因此并发性能较差。
支持全文索引,适合于需要全文搜索功能的应用。
数据和索引分开存储,对于只读的或者读操作远远多于写操作的场景,性能较好。
适用场景:
读操作频繁、写操作较少或者不需要事务支持的应用,如博客系统、新闻网站等。
4.mysql索引
是一种用于提高数据库查询效率的数据结构,它能够快速定位到满足特定条件的记录,从而加快查询速度。
(1)索引类型:
MySQL支持多种类型的索引,包括普通索引、唯一索引、主键索引、全文索引等。
- 普通索引是最基本的索引类型,它没有任何限制。
- 唯一索引确保索引列的值唯一。
- 主键索引是一种特殊的唯一索引,用于唯一标识表中的每一行。
- 全文索引用于全文搜索,可以快速搜索文本内容。
(2)索引设计原则:
- 选择适当的列作为索引,通常选择经常用于查询条件的列。
- 避免在索引列上进行过多的计算和转换,以确保索引能够被有效利用。
- 对于组合查询条件,考虑创建组合索引,以提高查询效率。
- 避免在高更新频率的列上创建索引,因为索引会增加写操作的开销。
- 定期分析和优化索引,以确保索引结构与数据分布的匹配程度。
(3)索引创建(具体语句)
CREATE UNIQUE INDEX idx_unique_email ON users(email);
- 在创建表时,可以通过在列上添加
INDEX
或UNIQUE
关键字来创建索引。 - 也可以使用
CREATE INDEX
语句单独创建索引。 - 对于InnoDB存储引擎,主键索引通常与主键列一起创建,而对于MyISAM存储引擎,主键索引需要单独创建。
- MySQL查询优化器会根据查询条件和可用索引选择合适的索引。
- 使用
EXPLAIN
语句可以查看MySQL查询执行计划,了解查询是如何使用索引的。
"a==x OR b==y":
CREATE INDEX index_name ON table_name (a, b);
(4)索引失效
函数或表达式: 如果在WHERE子句中使用函数或表达式,MySQL可能无法使用索引。
模糊搜索: 如果在查询中使用了模糊搜索(例如LIKE '%value%')
前缀搜索可以防止模糊搜索时索引失效: 如果模糊搜索的模式是从开头开始的,可以考虑使用前缀搜索。例如,如果要搜索以特定字符串开头的值,可以使用LIKE 'value%'
(5)最左匹配
最左匹配是指对于联合索引(复合索引),当使用索引进行查询时,索引的左边部分会被用于查找,而右边部分则可能不会被用到。
如果有一个复合索引 (col1, col2, col3),
会尽量利用索引的最左边的列(col1)来过滤数据,而不是右边的列(col2、col3)。这意味着如果你的查询条件不涉及到索引的左边列,那么这个索引就不会被用到。
(6)如何查看索引使用情况
explain后的key。key的值是实际使用的索引。如果为 NULL,则没有使用索引。
如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 forceindex、ignore index。
5.回表
指当MySQL执行查询时,如果需要获取查询结果中的某些字段数据不在索引中,就需要通过索引的值去主键索引或者聚簇索引中查找相应的数据记录,这个过程就称为"回表"。
如何减少回表:
-
覆盖索引:设计合适的索引,包含了查询语句中所需的所有字段,使得查询可以直接从索引中获取数据。
-
使用联合索引:如果查询中涉及到多个条件,可以使用联合索引来覆盖这些条件。
-
避免使用不必要的列:查询语句中只选择需要的列,避免选择不必要的列
-
利用查询缓存:对于频繁执行的查询,可以利用查询缓存。
6.聚簇索引和非聚簇索引
-
聚簇索引:
- 聚簇索引是将数据行直接存储在索引的叶子节点中的索引结构。
- 聚簇索引决定了数据在磁盘上的物理存储顺序,即数据的存储顺序与索引的顺序一致。
- 一个表只能有一个聚簇索引,通常是主键索引。
- 查询聚簇索引可以直接定位到数据行,因此适合范围查询和顺序扫描等操作。
-
非聚簇索引:
- 非聚簇索引是将索引中的键值映射到对应数据行的地址或指针,而数据行存储在独立的数据页中。
- 非聚簇索引的叶子节点包含了索引键值和指向数据行的指针。
- 一个表可以有多个非聚簇索引。
- 查询非聚簇索引需要先定位到索引键值,然后再通过指针查找对应的数据行,因此对于范围查询和顺序扫描等操作效率较低。
7.B+树(为什么选用)
-
磁盘IO优化:B+树具有多叉树的特性,每个节点存储的键值对较多,因此树的高度相对较低,减少了磁盘IO次数。这对于数据库系统来说是非常重要的,因为IO操作是数据库性能的瓶颈之一。
-
范围查询效率高:B+树的叶子节点构成有序链表,因此支持范围查询的效率较高。可以快速定位到符合条件的叶子节点,并进行顺序扫描。
-
适合于磁盘存储:由于B+树的每个节点存储的数据量较大,适合于磁盘存储,可以减少树的高度,提高检索效率。
和B树区别:
B+树内部节点不保存数据,只保存键值和子节点的指针,而数据都保存在叶子节点中。叶子节点通过链表连接,形成有序的叶子节点链表。
B树适用于需要随机访问数据的场景,例如文件系统索引、数据库索引等。由于B树的每个节点都包含数据指针,因此在查询过程中可以直接获取到数据
B+树主要用于范围查询和顺序访问数据的场景,例如数据库索引。由于B+树的叶子节点构成了有序链表,并且不包含数据,因此在查询过程中需要先定位到叶子节点,然后通过链表顺序访问数据。
8.undolog、redolog、binlog
undolog记录了写操作前的原始数据记录,redolog记录了数据的修改操作,而binlog记录了写操作的内容。
undolog保证了事务的原子性和一致性
redolog保证了事务的持久性
binlog包含了所有对数据库进行修改的SQL语句或者数据更改的事件信息
9.数据库分表
将一个大型的数据库分成多个较小的数据库(分库),将数据库中的表进一步分割成多个部分(分表)
具体策略:
-
数据范围分库分表:某个字段的范围进行划分,例如按照用户ID、时间范围等。
-
数据哈希分库分表:某个字段进行哈希计算,然后根据哈希值进行分库分表
-
数据模块分库分表:某个字段的模块进行划分,例如按照用户ID的奇偶性进行划分
-
业务模块分库分表
10.主从复制
数据同步和备份机制
复制过程:
- 日志复制:主数据库会将所有的更改操作记录到一个日志文件中(如MySQL中的二进制日志)。
- 日志传输:从数据库会定期从主数据库获取这个日志文件,或者主数据库主动推送这些日志文件到从数据库。
- 日志应用:从数据库根据日志文件中的操作记录,按顺序在自己的数据副本上重放这些操作,从而保持与主数据库的数据一致性。
11.成绩最高的5名学生姓名
select name
from students
order by score desc
limit 5
五、数据结构
1.String,StringBuilder,StringBuffer的区别,String为什么是不可变的?在循环内使用“+”进行字符串拼接的话会有什么问题?
String是字符串常量,StringBuffer和StringBuilder都是字符串变量。后两者的字符内容可变,而前者创建后内容不可变。
String不可变是因为在JDK中String类被声明为一个final类。
StringBuffer是线程安全的,而StringBuilder是非线程安全的。
2.Vector,ArrayList,LinkedList的区别和使用场景
Vector、ArrayList都是以类似数组的形式存储在内存中,LinkedList则以链表的形式进行存储。
List中的元素有序、允许有重复的元素,Set中的元素无序、不允许有重复元素。
Vector线程同步,ArrayList、LinkedList线程不同步。
LinkedList适合指定位置插入、删除操作,不适合查找;ArrayList、Vector适合查找,不适合指定位置的插入、删除操作。
ArrayList在元素填满容器时会自动扩充容器大小的约50%,而Vector则是100%,因此ArrayList更节省空间。
3.HashTable,HashMap,TreeMap,LinkedHashMap,juc的map
HashMap线程不安全,会乱,多个线程同时对其进行读写操作可能会导致数据不一致的问题
HashTable线程安全,方法加了sychronized,不可以多线程访问。
HashTable不允许<键,值>有空值,HashMap允许<键,值>有空值。
TreeMap能够把它保存的记录根据键排序,默认是按升序排序。
LinkedHashMap是有序的,根据顺序填入。
4.ConcurrentHashMap的原理
-
分段锁机制:
ConcurrentHashMap
内部使用了分段锁(Segment Locking)机制,将整个哈希表分成多个段(Segment),每个段拥有自己的锁。因此可以多线程访问。 -
线程安全的操作: 在
ConcurrentHashMap
中,大部分操作(如get()
、put()
、remove()
等)都是线程安全的,有synchronized
关键字 -
并发度调整:
ConcurrentHashMap
允许通过initialCapacity
和concurrencyLevel
参数来调整并发度,从而灵活控制锁的粒度。initialCapacity
参数表示初始容量,concurrencyLevel
参数表示并发级别,即哈希表的段数,默认16。 -
读写分离:
ConcurrentHashMap
支持读写分离,即读操作和写操作可以并发进行,读操作不会阻塞其他读操作,而写操作只会阻塞写操作。
5.一致性哈希
一致性哈希(Consistent Hashing)是一种分布式哈希算法。
核心思想是将数据和节点映射到一个固定大小的哈希环上,通过哈希函数将数据和节点映射到环上的位置,然后根据数据在环上的位置确定数据应该被分配到哪个节点。
在一致性哈希中,当新增或删除节点时,只会影响到少量数据的分布,而不会导致大量数据的重新分配,这样可以减少系统的负载和数据迁移的开销。
步骤:
-
确定哈希函数:选择合适的哈希函数,将节点和数据映射到哈希环上的位置。
-
确定节点位置:根据节点的唯一标识(如 IP 地址或节点名称)计算哈希值,并将其映射到哈希环上的位置。
-
确定数据位置:根据数据的唯一标识(如数据的键值或哈希值)计算哈希值,并将其映射到哈希环上的位置。
-
寻找数据的位置:根据数据的哈希值在哈希环上顺时针寻找第一个比它大的节点位置,将数据分配到该节点。
-
节点的增减:当新增或删除节点时,只需要重新计算受影响的数据,将其重新映射到新的节点上,而不需要重新分配所有数据。
6.二叉树
(1)满二叉树
满的
(2)完全二叉树
除了最后一层是满的,且最后一层都在左边
(3)平衡二叉树
对于树中的任意一个节点,它的左子树和右子树的高度差不超过1。
(4)红黑树
红黑树是一种自平衡的二叉查找树,它在每个节点上增加了一个额外的属性,即节点的颜色(红色或黑色),并根据一些规则来保持这些颜色的平衡。红黑树的性质包括:根节点是黑色的;每个叶子节点(NIL节点)都是黑色的;不能有两个相邻的红色节点等等。
(5)B+、B
B树和B+树是一种多路搜索树,它们是为了在磁盘等外存储设备上进行高效的查找而设计的。
7.大根堆小根堆
大根堆是一种特殊的二叉树,其中每个节点的值都大于或等于其子节点的值
大根堆的根节点是整个堆中的最大值。大根堆通常用于实现优先队列等数据结构。
8.并查集
主要用于解决一些元素分组的问题,例如连通性问题、最小生成树、图的路径压缩等
最常见的操作有两个:合并(Union)和查找(Find)。合并操作是将两个集合合并成一个集合,而查找操作则是找到某个元素所属的集合。
六、Spring(设计模式)
1.AOP面向切面编程
(1)原理
主要将程序中的横切关注点从主要业务逻辑中分离出来,通过在特定的连接点上应用特定的行为来实现对这些关注点的管理和控制。
(2)设计模式
-
切面(Aspect):切面是横切关注点的模块化单元,包含了横切逻辑和相关的通知(advice)和切点(pointcut)。通知定义了在何时、如何执行横切逻辑,定义了在连接点处执行的横切逻辑。常见的通知包括前置通知、后置通知、返回通知、异常通知和环绕通知。而切点定义了何处应该执行横切逻辑,通常使用表达式或者规则来描述切点。
-
连接点(Join Point):连接点是在程序执行过程中能够插入切面的点,它可以是方法调用、方法执行、异常抛出等事件。AOP 框架通过拦截连接点来执行相应的横切逻辑。
-
织入(Weaving):织入是将切面的横切逻辑与应用程序的主要业务逻辑结合起来的过程。织入可以在编译期、类加载期或者运行期进行,以实现不同粒度的横切。
-
目标对象(Target Object):目标对象是应用程序中的原始对象,它包含了主要业务逻辑。
2.IOC控制反转
(1)容器的加载过程
配置加载(XML、Java注解等)-实例化Bean-依赖注入(构造函数注入、Setter方法注入或者字段注入)-初始化Bean-容器准备就绪
IoC容器负责管理组件的生命周期和依赖关系
(2)设计模式
- bean是单例模式
-
依赖注入(Dependency Injection,DI):依赖注入是 IoC 的一种具体实现方式,它通过将对象的依赖关系从代码中硬编码的方式解耦,并将这些依赖关系通过外部容器进行注入。依赖注入可以通过构造函数、属性或者方法来实现。
-
控制反转容器(IoC Container):IoC 容器是负责管理对象的创建、生命周期和依赖注入的容器。在 Java 中,Spring 框架是一个典型的 IoC 容器。通过配置文件或者注解,Spring 容器可以根据配置信息自动创建对象并注入相应的依赖关系,从而实现控制反转和依赖注入。
-
模板方法模式(Template Method Pattern): IoC 中,容器可以使用模板方法模式来定义对象的生命周期,例如初始化、销毁等步骤,并允许用户根据需要扩展或者修改这些步骤的实现。
-
观察者模式(Observer Pattern):观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,当被观察对象的状态发生改变时,所有观察者对象都会收到通知并作出相应的响应。在 IoC 中,容器可以充当观察者,当某个对象的状态发生改变时,容器可以通知所有相关的观察者并执行相应的操作,例如重新创建对象、更新依赖关系等。
3.循环依赖如何解决
指两个或多个Bean之间相互依赖,形成了一个闭环的情况。
-
通过构造函数注入解决循环依赖:Spring在进行Bean初始化时,会先调用Bean的构造函数来创建Bean实例,然后再进行属性注入。因此,如果使用构造函数注入,Spring可以保证在创建Bean实例时,所依赖的其他Bean已经创建完成。
-
通过Setter方法注入解决循环依赖:与构造函数注入类似。如果使用Setter方法注入,Spring可以保证所依赖的其他Bean已经创建完成。
-
通过代理对象解决循环依赖:Spring容器可以通过代理对象来解决循环依赖的问题。当两个Bean之间存在循环依赖时,Spring会先创建一个代理对象作为Bean的实例,然后再注入其他Bean的引用。当第一个Bean需要引用第二个Bean时,实际上是引用了代理对象,代理对象再去获取第二个Bean的实例。这样就避免了循环依赖的问题。
4.autowried和resourse
- @Autowired:
@Autowired
是 Spring 提供的,它通过类型(Type)进行自动装配。- 当 Spring 容器中有多个类型匹配的 bean 时,
@Autowired
可能会产生歧义,此时可以结合@Qualifier
注解来指定具体要注入的 bean。 - 除了字段注入,
@Autowired
还可以用于构造函数注入、Setter 方法注入等场景。
- @Resource:
@Resource
是 JavaEE 提供的,它通过名称(Name)进行自动装配。@Resource
注解默认按照名称查找对应的 bean,如果指定了name
或type
属性,则会按照指定的属性进行匹配。- 在使用
@Resource
注解时,如果没有指定name
或type
属性,则默认按照字段名或属性名查找对应的 bean。
5.单例模式
1.饿汉式,线程安全,JVM 加载过程中会由类加载器保证线程安全
public class Singleton {
// 在类加载时就创建实例
private static final Singleton instance = new Singleton();
// 将构造函数私有化,防止外部实例化
private Singleton() {}
// 提供一个静态方法用于获取实例
public static Singleton getInstance() {
return instance;
}
}
1.懒汉式(Lazy Initialization): 在第一次获取实例时创建实例。synchronized 关键字
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.双重检查锁定(Double-Checked Locking): 在懒加载的基础上增加了一次检查,提高性能(因为减少了同步块的使用)。
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;
}
}
七、分布式
1.cap
CAP 定理(CAP theorem),指出在分布式计算中,一个分布式数据存储系统不可能同时保证以下三个特性:
-
一致性(Consistency):
- 每次读取操作总是能够返回最新的写入结果,保证所有节点的数据一致。
-
可用性(Availability):
- 每次请求总是能够获得一个非错误的响应,即使有部分节点失败。
-
分区容忍性(Partition Tolerance):
- 系统在出现网络分区(即节点之间的网络通信故障)时,仍然能够继续运行。
根据 CAP 定理,一个分布式系统只能在上述三个特性中同时满足两个,而不可能同时满足全部三个特性。这就产生了 CP、CA 和 AP 三种不同的系统设计选择:
-
CP(Consistency and Partition Tolerance):
- 在 CP 系统中,数据的一致性和网络分区容忍性得到保证,但在网络分区期间,可用性可能会受到影响。例如,系统可能会选择拒绝请求或者延迟响应,直到分区问题解决,以保证数据一致性。
- 适用场景:需要强一致性的场景,如银行转账等关键金融操作。
-
CA(Consistency and Availability):
- 在 CA 系统中,数据的一致性和可用性得到保证,但系统无法在网络分区时继续正常运行。换句话说,如果出现网络分区,系统可能会停止提供服务。
- 适用场景:网络分区较少且容忍短暂不可用的场景,但在实际的分布式系统中,实现 CA 的难度非常大。
-
AP(Availability and Partition Tolerance):
- 在 AP 系统中,可用性和网络分区容忍性得到保证,但可能会牺牲数据的一致性。例如,在网络分区期间,不同节点可能会有不一致的数据,最终通过一致性协议达到最终一致性。
- 适用场景:对一致性要求不高的场景,如社交媒体、内容分发网络等。