Error常见的有StackOverFlowError,OutOfMemoryError等等。
Exception可分为运行时异常和非运行时异常。对于运行时异常,可以利用try catch的方式进行处理,也
可以不处理。对于非运行时异常,必须处理,不处理的话程序无法通过编译。
ClassNotFoundException是在类加载第一个阶段,加载这个动作的时候,类加载器不能找到class文件,则会报ClassNotFoundException。比如在A中有段代码使用了Class.forName(B)去加载B,此时B的class文件不见了,则会报错ClassNotFoundException。它报这个错是说B的加载阶段出错了。(A类去加载B类,A类会报ClassNotFound B类)
NoClassDefError这个类是继承LinkageError(连接错误),所以也可以大概的猜出,NoClassDefError是在类加载的连接这个阶段报出来的。当A类中引用了B类,然后A类进行类的加载,加载成功后,然后进行接下来的连接阶段的时候,如果涉及到引用到B类却找不到B类的时候,就会报NoClassDefError。它报这个错是说A的连接阶段出错了。(A类去加载B类,A类会报B类NoClassDef)
Linux实现CAS的指令cmpxchgl
在 thread dump 中,要留意下面几种状态
二叉搜索树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树。搜索复杂度为o(logn)最差为o(n)
平衡二叉搜索树AVL树:本质上是带了平衡功能的二叉查找树,每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
红黑树:需要满足以下性质:红黑树能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点,即空结点(NIL)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。也就是说从每个叶子到根的所有路径上不能有两个连续的红色节点
5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
short a = 4; a += 5; //a = a + 3会报错 a = (short) (a + 3);
+=是一个运算符,编译器会自动转换类型
a = a+b
Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。
ArrayList 在顺序添加一个元素的时候非常方便。
删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
插入元素的时候,也需要做一次元素复制操作,缺点同上。
ArrayList 比较适合顺序添加、随机访问的场
阻塞IO
非阻塞NIO
堆排序
堆的数据结构
求hash时,先求出hashCode,再向右移16位,与原来hashcode做&运算(保留高低位的特征,增加散列性)
2的n次幂(1000),当求索引时,是hash&(length - 1),减一变成0111,能够保持hash值的散列性,减少碰撞
为了能够保持长度为2的n次幂
为了扩容时,快速求出元素扩容后所在的索引,只需要比较hash的前一位是1还是0,如果是0就不变,如果是1就原索引+oldCap。
如果 hashCode 分布良好,也就是 hash 计算的结果离散好的话,那么红黑树这种形式是很少会被用到的,因为各个值都均匀分布,很少出现链表很长的情况。在理想情况下,链表长度符合泊松分布,各个长度的命中概率依次递减,当长度为 8 的时候,概率仅为 0.00000006。这是一个小于千万分之一的概率,通常我们的 Map 里面是不会存储这么多的数据的,所以通常情况下,并不会发生从链表向红黑树的转换。
在空间占用与查询时间之间取得较好的权衡;大于这个值,空间节省了,但链表就会比较长影响性能;小于这个值,冲突减少了,但扩容就会更频繁,空间占用也更多
主要是一个过渡,避免链表和红黑树之间频繁的转换。如果阈值是7的话,删除一个元素红黑树就必须退化为链表,增加一个元素就必须树化,来回不断的转换结构无疑会降低性能,所以阈值才不设置的那么临界
相同点
(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点
(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为 public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的,1.8 可以有static方法,可以有default方法,子类可以不实现。
抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;可以有非抽象方法,抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。抽象类中可以有非抽象方法,有抽象方法的一定是抽象类
TEXT类型一般分为 TINYTEXT(255长度)、TEXT(65535)、 MEDIUMTEXT(int最大值16M),和LONGTEXT(long最大值4G)这四种,对于text列,插入时MySQL不会对它进行填充,并且select时不会删除任何末尾的字节。
text的最大限制也是64k个字节,但是本质是溢出存储,innodb默认只会存放前768字节在数据页中,而剩余的数据则会存储在溢出段中。text类型的数据,将被存储在元数据表之外地方,但是varchar/char将和其他列一起存储在表数据文件中,值得注意的是,varchar列在溢出的时候会自动转换为text类型。text数据类型实际上将会大幅度增加数据库表文件尺寸。
除此之外,二者还有以下的区别
1、当text作为索引的时候,必须 制定索引的长度,而当varchar充当索引的时候,可以不用指明。
2、text列不允许拥有默认值。
3、当text列的内容很多的时候,text列的内容会保留一个指针在记录中,这个指针指向了磁盘中的一块区域,当对这个表进行select *的时候,会从磁盘中读取text的值,影响查询的性能,而varchar不会存在这个问题。
public final class Singleton { private Singleton() { } // 问题1:解释为什么要加 volatile?为了保证赋值和构造器不要发送重排序,否则第二个线程可能拿到不完整的对象。 private static volatile Singleton INSTANCE = null; // 问题2:对比实现3, 说出这样做的意义 public static Singleton getInstance() { if (INSTANCE != null) { return INSTANCE; } synchronized (Singleton.class) { // 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗?为了防止第二个线程等第一个线程释放锁后,再次创建新对象 if (INSTANCE != null) { // t2 return INSTANCE; } INSTANCE = new Singleton(); return INSTANCE; } } }
// 问题1:枚举单例是如何限制实例个数的 ==static final 变量 // 问题2:枚举单例在创建时是否有并发问题 ==没有并发,jvm保证 // 问题3:枚举单例能否被反射破坏单例 ==不能 // 问题4:枚举单例能否被反序列化破坏单例 不能,枚举类实现了序列化 // 问题5:枚举单例属于懒汉式还是饿汉式 ==饿汉式 // 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做== 加构造器 enum Singleton { INSTANCE; }
public final class Singleton { private Singleton() { } // 问题1:属于懒汉式还是饿汉式 ==懒汉式,不调用方法不加载静态类 private static class LazyHolder { static final Singleton INSTANCE = new Singleton(); } // 问题2:在创建时是否有并发问题 ==没有,静态成员变量,在类加载的时候完成,jvm保证 public static Singleton getInstance() { return LazyHolder.INSTANCE; } }
单体架构的优缺点如下:
**优点:**架构简单、部署成本低 **缺点:**耦合度高(维护困难、升级困难)
分布式架构的优缺点:
**优点:**降低服务耦合、有利于服务升级和拓展 **缺点:**服务调用关系错综复杂
当我们需要封禁一个账号时,只需要将其账号的status值修改为0即可,对方再次登录系统时,我们便可以检测到status值不为1禁止登录。由于我们只在登录时检测status值,这也就代表:如果对方不主动注销账号,他的会话还是会一直存在且有效。
那怎么才可以做到在封禁账号后立即生效?
你可能会想到使用拦截器,拦截用户的所有请求检测账号状态:status=0时禁止访问,status=1时再对请求放行。
可以用户数据库+status状态、登录时判断、拦截器请求时判断、redis维护黑名单。
使用Sa-Token框架
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,我们需要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。
只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:
单一职责原则的优点
单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。如果遵循单一职责原则将有以下优点。
Unicode就是一种编码:它包含了世界上所有的符号,并且每一个符号都是独一无二的
UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有两条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码
GBK/GB2312/GB18030
GBK和GB2312都是针对简体字的编码,只是GB2312只支持六千多个汉字的编码,而GBK支持1万多个汉字编码。而GB18030是用于繁体字的编码。汉字存储时都使用两个字节来储存
修饰普通方法:对象锁
修饰静态方法:类锁
修饰代码块:类锁synchronized(A.class)、对象锁synchronized(this)
为啥先进阻塞队列再创建最大线程
线程池创建线程需要获取mainlock这个全局锁,会影响并发效率,所以使用阻塞队列把第一步创建核心线程与第三步创建最大线程隔离开来,起一个缓冲的作用。引入阻塞队列,是为了在执行execute()方法时,尽可能的避免获取全局锁。
有些任务可能会永远等待某些资源或来自用户的输入,而这些资源又不能保证变得可用,用户可能也已经回家了,诸如此类的任务会永久停止,而这些停止的任务也会引起和线程泄漏同样的问题。如果某个线程被这样一个任务永久地消耗着,那么它实际上就被从池除去了。对于这样的任务,应该要么只给予它们自己的线程,要么只让它们等待有限的时间。
如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。
经验公式如下 线程数 = 核数 _ 期望 CPU 利用率 _ 总时间(CPU计算时间+等待时间) / CPU 计算时间
例如 4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 被 100% 利用,套用公式 4 _ 100% _ 100% / 50% = 8
例如 4 核 CPU 计算时间是 10% ,其它等待时间是 90%,期望 cpu 被 100% 利用,套用公式 4 _ 100% _ 100% / 10% = 40
核心线程数 corePoolSize
任务队列长度 workingQueue
最大线程数 maximumPoolSize
最大空闲时间
状态 | 描述 |
---|---|
RUNNING | 能接受新提交的任务,并且也能处理阻塞队列中的任务 |
SHUTDOWN | 关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态) |
STOP | 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态 |
TIDYING | 如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态 |
TERMINATED | 在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做 |
线程池原理
第一次握手丢失:客户端重传SYN
第二次握手丢失:客户端重传SYN、服务器都重传SYN-ACK
第三次握手丢失:服务端重传SYN-ACK,客户端的ACK不会重传
序列号可以用来解决网络包乱序的问题,确认号可以⽤来解决网络包丢失的问题
一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
客户端连续发送多次 SYN 建立连接的报文,在网络拥堵情况下:
MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文
建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接
大量TCP连接处在close_wait状态,形成原因和解决办法
客户端发送FIN客户端收到后,客户端自动ACK后进入close_wait状态,socket.close因为阻塞关闭不及时。
Server端在某些异常情况时,没有关闭Socket。关闭socket不及时:例如I/O线程被意外阻塞,或者I/O线程执行的用户自定义Task比例过高,导致I/O操作处理不及时,链路不能被及时释放。
浏览器–>本地域名服务器缓存–>根域名服务器(全球有13个)–>com顶级域名服务器–>baidu.com主域名(权威)服务器–>找到ip地址 缓存到本地域名服务器
DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。
DNS使用UDP还是TCP:区域传送时使用TCP,域名解析时使用UDP协议
DNS在进行区域传输的时候使用TCP协议,其它时候则使用UDP协议;
DNS的规范规定了2种类型的DNS服务器,**一个叫主DNS服务器,一个叫辅助DNS服务器。**在一个区中主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息,而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时,它需要与主DNS服务器通信,并加载数据信息,这就叫做区传送(zone transfer)。
为什么既使用TCP又使用UDP?
首先了解一下TCP与UDP传送字节的长度限制:
UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。通常传统的UDP报文一般不会大于512字节。
区域传送时使用TCP,主要有一下两点考虑:
1.辅域名服务器会定时(一般时3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,则会执行一次区域传送,进行数据同步。区域传送将使用TCP而不是UDP,因为数据同步传送的数据量比一个请求和应答的数据量要多得多。
2.TCP是一种可靠的连接,保证了数据的准确性。
域名解析时使用UDP协议:
客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。
http请求一定要访问80端口吗
不一定。80端口只是客户端(浏览器)发出的http请求默认去访问的服务器端口,只要请求的端口和服务器程序运行的端口保持一致就可以。我们日常开发使用的tomcat就运行在8080端口,在浏览器输入http://localhost:8080也可以访问tomcat的。只是一般网站服务器程序都运行在80端口上,我们只需要输入网址即可,不用再输入类似ww.google.com:80这样带有端口号的地址,比较方便。
简单的说,当你登陆一个网站的时候,如果web服务器端使用的是session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话sessionid,服务器根据当前sessionid判断相应的用户数据标志,以确定用户是否登陆或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造
最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)
假如客户端禁用了cookie,或者不支持cookie,则会话跟踪会失效。关于WAP上的应用,常规的cookie就派不上用场了。运用session需要使用URL地址重写的方式。一切用到session程序的URL都要进行URL地址重写,否则session会话跟踪还会失效。
cookie什么情况下会丢失
1、 Cookie 的Domain设置不正确 ;
2、 Cookie 超时 ;
3、 Cookie 中含有一些非法字符,致使浏览器丢弃 Cookie
4、程序源码可能有多处重复设置或取消 Cookie — Domain的值设置为 ".hi scr.cn"时,表示hi scr.cn下的所有二级…
Json web token (JWT):
由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session。典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。 服务端在HTTP协议中告诉客户端 在cookie中存放session的 id 下次发送请求的时候都会带上id,这样服务端就知道是哪个session了。
步骤:当拥塞窗口cwnd小于慢开始门限ssthresh时,使用慢开始算法(1 2 4 8指数),如果大于ssthresh时就使用拥塞避免算法(1 2 3线性),如果报文段的超时计数器超时发生重传,就把ssthresh变为原来的一半,cwnd变为1,再开始慢开始算法;如果收到3个连续的重复确认,就开始**快恢复,**就是将cwnd和ssthresh设置为当前值的一半,开始拥塞避免算法。
要求接收方不要等待自己发送数据时才捎带确认,而是要立即发送确认;
即使收到了失序的报文段也要立即发送对已收到的报文段的重复确认;
发送方一旦收到连续3个的重复确认,就将相应的报文段立即重传,而不是等超时重传计时器超时在重传。
首先接受方会发送ACK(按序到达的最大序列号)和接收窗口rwnd给接收方,接收方收到确认后可以将窗口向前移动,如果超时重传计时器超时就会重传。
ARQ 协议(Automatic Repeat-reQuest) 是是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。
优点: 简单
缺点: 信道利用率低,等待时间长
优点: 信道利用率高,容易实现,即使确认丢失,也不必重传。
缺点: 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息,中间第三条丢失(3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退N),表示需要退回来重传已经发送过的 N 个消息。
根据 IP 地址,查到MAC地址
操作系统通常会把第一次通过 ARP 获取的 MAC 地址缓存起来,以便下次直接从缓存中找到对应 IP 地址的 MAC 地址。
TCP:面向连接、传输可靠、字节流、效率慢、所需资源多、仅支持一对一两点通信–文件传输 发送、接收邮件、远程登陆
UDP:无连接、传输不可靠、数据报文段、效率快、所需资源少、支持一对一、一对多、多对多的交互通信–QQ电话 视频、直播等
1. 连接
2. 服务对象
3. 可靠性
4. 拥塞控制、流量控制
5. 首部开销
6. 传输方式
7. 分片不同
HTTPS 采⽤的是对称加密和非对称加密结合的混合加密⽅式: 在通信建立前采⽤非对称加密的⽅式交换会话秘钥,后续就不再使⽤非对称加密。 在通信过程中全部使⽤对称加密的会话秘钥的⽅式加密明⽂数据。
HTTPS首先要解决的是:认证的问题
客户端发送Client Hello(TLS版本、支持的加密套件、第一随机数)给服务端;
服务端发送(TLS版本、选择一个客户端支持的版本、第二随机数)给客户端;发送证书和公钥给客户端;
客户端用公钥加密生成的预主密钥,发给服务端,并通过第一第二随机数预主密钥生成会话密钥,告诉服务端以后用会话密钥通信;
服务端用私钥解密加密后的预主密钥,然后用第一第二随机数预主密钥生成会话密钥。
HTTP 1.1 较于1.0的改进:长连接、管道传输、断点续传
HTTP/1.1 还是有性能瓶颈:
HTTP/2 相比 HTTP/1.1 性能上的改进:头部压缩、二进制分帧层、多路复用、服务端推送
在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了
客户端还可以指定数据流的优先级。优先级高的请求,服务器就先响应该请求。
移除了 HTTP/1.1 中的串行请求,不需要排队等待,也就不会再出现「队头阻塞」问题,降低了延迟,大幅度提高了连接的利用率
举例来说,在浏览器刚请求 HTML 的时候,就提前把可能会用到的 JS、CSS 文件等静态资源主动发给客户端,减少延时的等待,也就是服务器推送(Server Push,也叫 Cache Push)
HTTP 1.1:可以一下发多个请求,但是服务器必须按顺序处理,队头阻塞
HTTP 2.0:服务器可以不按顺序处理,一定程度解决了队头阻塞
HTTP 3 就将传输层从 TCP 替换成了 UDP,并在 UDP 协议上开发了 QUIC 协议,来保证数据的可靠传输。
QUIC 协议的特点:
动态表是具有时序性的,如果首次出现的请求发生了丢包,后续的收到请求,对方就无法解码出 HPACK 头部,因为对方还没建立好动态表,因此后续的请求解码会阻塞到首次请求中丢失的数据包重传过来。
基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接,那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接,而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
而 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。
总结:HTTP 1.1 较于1.0的改进,默认使用长连接、断点续传(利用HTTP消息头使用分块传输编码,将实体主体分块进行传输)、增加了更多的缓存控制策略。
HTTP 2.0新增特性:**多路复用、二进制分帧层、头部压缩、服务端推送**。
HTTP 2.0不再以文本的方式传输,采用二进制分帧层,对头部进行了压缩,支持流控,最主要就是HTTP 2是支持多路复用的(通过单一的TCP连接并行发起多个的请求和响应消息)。
HTTP1.1提出的管线化只能串行(一个响应必须完全返回后,下一个请求才会开始传输)。
HTTP 2.0多路复用则是利用分帧数据流,把HTTP协议分解为互不依赖的帧(为每个帧标序发送,接收回来的时候按序重组),进而可以乱序发送避免一定程度上的队头阻塞问题。但是,无论是HTTP1.1还是HTTP 2.0,response响应的处理顺序总是需要跟request请求顺序保持一致的。假如某个请求的response响应慢了,还是同样会有阻塞的问题。这受限于HTTP底层的传输协议是TCP,没办法完全解决队头阻塞的问题
HTTP 2.0协议的多路复用机制解决了HTTP层的队头阻塞问题,但是在TCP层****仍然存在队头阻塞问题。
response响应的「处理顺序」总是需要跟request请求顺序保持一致的
HTTP1.1 较于1.0的改进:
总结:即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
但是服务器还是按照顺序,先回应 A 请求,完成后再回应 B 请求。要是 前面的回应特别慢,后面就会有许多请求排队等着。这称为「队头堵塞」。
HTTP2.0优点:
HTTP2 复用TCP连接,在一个连接里,客户端和浏览器都可以「同时」发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了队头堵塞。
多路复用允许同时通过单一的 HTTP 2 连接发起多重的请求-响应消息。在 HTTP 1.1 协议中浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞。 HTTP 2 的多路复用(Multiplexing) 则允许同时通过单一的 HTTP 2 连接发起多重的请求-响应消息。因此 HTTP 2 可以很容易的去实现多流并行而不用依赖建立多个 TCP 连接,HTTP 2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息。
解决了HTTP1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量。在二进制分帧层中, HTTP 2 会将所有传输的信息分割为更小的信息和帧(frame),并对它们采用二进制格式的编码
HTTP 2 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接,让高带宽也能真正的服务于 HTTP 的性能提升。
HTTP 2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的分。可以提高速度
HTTP 1.1并不支持 HTTP 首部压缩,为此 SPDY 和 HTTP/2 应运而生, SPDY 使用的是通用的DEFLATE 算法,而 HTTP/2 则使用了专门为首部压缩而设计的 HPACK 算法。
在 HTTP 2 中,服务器可以对客户端的一个请求发送多个响应。
TCP/IP ⽹络模型共有 4 层,分别是应⽤层、传输层、⽹络层和⽹络接⼝层,每⼀层负责的职能如下:
应⽤层,负责向⽤户提供⼀组应⽤程序,⽐如 HTTP、DNS、FTP 等;
传输层,负责端到端的通信,⽐如 TCP、UDP 等;
⽹络层,负责⽹络包的封装、分⽚、路由、转发,⽐如 IP、ICMP 等;
⽹络接⼝层,负责⽹络包在物理⽹络中的传输,⽐如⽹络包的封帧、 MAC 寻址、差错检测,以及通 过⽹卡传输⽹络帧等;
HTTPS 也就是在 HTTP 与 TCP 层之间增加了 SSL/TLS 安全传输层,HTTP/3 甚至把 TCP 层换成了基于 UDP 的 QUIC。
路由器和交换机是有区别的。
重定向:
请求转发:
1)无论本次请求涉及到多少个Servlet,用户只需要手动通过浏览器发送一次请求
2)Servlet之间调用发生在服务端计算机上,节省服务端与浏览器之间往返次数增加处理服务速度
进程的上下⽂切换不仅包含了虚拟内存、栈、全局变量等⽤户空间的资源,还包括了内核堆栈、寄存器等 内核空间的资源。
Java线程的通信方式
总线锁是锁总线,对共享变量的修改在相同的时刻只允许一个CPU操作。
MESI协议:修改、互斥、共享、无效
读取数据时,其他CPU如果修改了该缓存行的数据,要先把修改的数据写回主内存中。
修改数据时,要使其他CPU中该缓存行状态变为无效。
小结:其实MESI协议做的就是判断对象状态,根据对象状态做不同的策略。关键就在于某个CPU在对数据进行修改时,需要同步通知其他CPU,表示这个数据被我修改了,你们不能用了。比较于「总线锁」,MESI协议的**”锁粒度”更小**了,性能那肯定会更高了。
问题:即便CPU2收到了invalid(无效)通知,但CPU1的值还没写到主存,那CPU2再次向主存读取的时候,还是旧值…
BIO 同步阻塞式 IO:适用于连接数目比较小且固定的结构。它对服务器资源要求比较高,并发局限于应用中,JDK1.4之前唯一选择,但程序直观简单易理解,如之前在 Apache 中使用。每个连接创建一个线程,阻塞等待文件描述符主备好
NIO 同步非阻塞 IO:适用于连接数目多且连接比较短的架构,比如聊天服务器,并发局限于应用中,变成比较复杂。JDK1.4开始支持,如在 Nginx、Netty 中使用。同步就是要等文件准备好,用轮询的方式询问,阻塞就是不用一直等
AIO 异步非堵塞 IO:适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持,在成长中,Netty 曾经使用过,后来放弃。异步就是准备好了就会告诉你准备好了
BIO 同步阻塞式 IO:当用户程序执行 read ,线程会被阻塞,一直等到内核数据准备好,并把数据从内核缓冲区拷贝到应用程序的缓冲区中,当拷贝过程完成,read 才会返回。
NIO 同步非阻塞 IO:非阻塞的 read 请求在数据未准备好的情况下立即返回,可以继续往下执行,此时应用程序不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲区,read 调用才可以获取到结果。数据准备过程不需要等待,要轮询,但是数据拷贝到应用程序缓冲区时,需要阻塞等待。
AIO 异步非堵塞 IO:「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程都不用等待。当我们发起 aio_read (异步 I/O) 之后,就立即返回,内核自动将数据从内核空间拷贝到用户空间,这个拷贝过程同样是异步的,内核自动完成的,和前面的同步操作不一样,应用程序并不需要主动发起拷贝动作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMW6S11D-1683542094239)(计网操作系统等面经.assets/image-20220520162101586.png#id=rzLcb&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0smoi5B-1683542094239)(计网操作系统等面经.assets/image-20220519211347393.png#id=c3XwZ&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_ZFCB4GDQWFFE94JG.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1iZ0IQJk-1683542094241)(计网操作系统等面经.assets/image-20220520110000455.png#id=Sdobi&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_7KFCKJC8NGECAPKQ.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sdPHa7U0-1683542094242)(计网操作系统等面经.assets/image-20220520115357876.png#id=RBNtg&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_P7XED82JNDD7QNN2.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tPTu5w1g-1683542094244)(计网操作系统等面经.assets/image-20220520141807810.png#id=HyPCA&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_JV3FWJPE4CY6D8KH.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a3jowlpr-1683542094244)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/image-20220521154339928.png#id=rnoA4&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合(以比特位来标记是哪个文件描述符),然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。
所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。
select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。
poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。
但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长
很明显发现,select 和 poll 的缺陷在于,当客户端越多,也就是 Socket 集合越大,Socket 集合的遍历和拷贝会带来很大的开销,因此也很难应对 C10K。
epoll 通过两个方面,很好解决了 select/poll 的问题。
第一点,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl() 函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删查一般时间复杂度是 O(logn),通过对这棵黑红树进行操作,这样就不需要像 select/poll 每次操作时都传入整个 socket 集合,只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。
第二点, epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。
文件就绪基于回调,什么场景需要遍历红黑树呢?插入新的FD时,需要先判断是否存在,基于红黑树查询效率高,判断效率自然高
epoll 支持两种事件触发模式,分别是边缘触发(edge-triggered,ET)和水平触发(level-triggered,LT)。
边缘触发模式一般和非阻塞 I/O 搭配使用,程序会一直执行 I/O 操作,直到系统调用(如 read 和 write)返回错误,错误类型为 EAGAIN 或 EWOULDBLOCK。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3jVn7BaL-1683542094245)(计网操作系统等面经/image-20220520145124177.png#id=cpy6I&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_QVT7KV2RPQXPFZUT.jpeg" alt="image.png" />
常见的 Reactor 实现方案有三种。
第一种方案单 Reactor 单进程 / 线程,不用考虑进程间通信以及数据同步的问题,因此实现起来比较简单,这种方案的缺陷在于无法充分利用多核 CPU,而且处理业务逻辑的时间不能太长,否则会延迟响应,所以不适用于计算机密集型的场景,适用于业务处理快速的场景,比如 Redis 采用的是单 Reactor 单进程的方案。
第二种方案单 Reactor 多线程,通过多线程的方式解决了方案一的缺陷,但它离高并发还差一点距离,差在只有一个 Reactor 对象来承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。
第三种方案多 Reactor 多进程 / 线程,通过多个 Reactor 来解决了方案二的缺陷,主 Reactor 只负责监听事件,响应事件的工作交给了从 Reactor,Netty 和 Memcache 都采用了「多 Reactor 多线程」的方案,Nginx 则采用了类似于 「多 Reactor 多进程」的方案。
Reactor 可以理解为「来了事件操作系统通知应用进程,让应用进程来处理」,而 Proactor 可以理解为「来了事件操作系统来处理,处理完再通知应用进程」。
因此,真正的大杀器还是 Proactor,它是采用异步 I/O 实现的异步网络模型,感知的是已完成的读写事件,而不需要像 Reactor 感知到事件后,还需要调用 read 来从内核中获取数据。
不过,无论是 Reactor,还是 Proactor,都是一种基于「事件分发」的网络编程模式,区别在于 Reactor 模式是基于「待完成」的 I/O 事件,而 Proactor 模式则是基于「已完成」的 I/O 事件
每次调用select,都需要把文件描述符集合从用户态拷贝到内核态,固定长度的 BitsMap
对socket进行扫描时是线性扫描,即采用轮询的方法,不管哪个Socket是活跃的,都遍历一遍,效率较低。
select支持的文件描述符数量太小了,32位机默认是1024个,64位机默认是2048,即能监听端口的大小有限。
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
但是它没有最大连接数的限制,原因是它是基于链表来存储的。
poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。
没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)
内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
epoll有EPOLLLT和EPOLLET两种触发模式
LT模式(水平触发,默认的模式)
只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作。
ET模式(边缘触发) “高速”模式
它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。
ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者遇到EAGAIN错误。
什么是 DMA 技术?简单理解就是,在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KWw7dVKz-1683542094247)(计网操作系统等面经.assets/DRM I_O 过程.png#id=Qlx6j&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
如果服务端要提供文件传输的功能,我们能想到的最简单的方式是:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IoVZavUB-1683542094248)(计网操作系统等面经.assets/传统文件传输.png#id=iEaUE&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
首先,期间共**发生了 4 次用户态与内核态的上下文切换**,因为发生了两次系统调用,一次是 `read()` ,一次是 `write()`,每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。 其次,还**发生了 4 次数据拷贝**,其中两次是 DMA 的拷贝,另外两次则是通过 CPU 拷贝.
零拷贝技术实现的方式通常有 2 种:
mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。
具体过程如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NOaMKjoN-1683542094249)(计网操作系统等面经.assets/mmap + write 零拷贝.png#id=B647I&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_HD5QJPZYCZQTFR27.jpeg" alt="image.png" />
在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile(),函数形式如下:
#includessize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。
首先,它可以替代前面的 read() 和 write() 这两个系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就可以减少一次系统调用,即只有 2 次上下文切换的开销,和 3 次数据拷贝。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0xAYejYl-1683542094250)(计网操作系统等面经.assets/senfile-3次拷贝.png#id=QQiLk&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
于是,从 Linux 内核 2.4 版本开始起,对于支持网卡支持 SG-DMA 技术的情况下, sendfile() 系统调用的过程发生了点变化,具体过程如下:
所以,这个过程之中,只进行了 2 次数据拷贝,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RLA98I7C-1683542094251)(计网操作系统等面经.assets/senfile-零拷贝.png#id=pzE0f&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
这就是所谓的零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过DMA 来进行传输的。
零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。
所以,总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上。
事实上,Kafka 这个开源项目,就利用了「零拷贝」技术,从而大幅提升了 I/O 的吞吐率,这也是 Kafka 在处理海量数据为什么这么快的原因之一。如果你追溯 Kafka 文件传输的代码,你会发现,最终它调用了 Java NIO 库里的 transferTo 方法:
另外,Nginx 也支持零拷贝技术,一般默认是开启零拷贝技术,这样有利于提高文件传输的效率
零拷贝技术是基于 PageCache 的,PageCache 会缓存最近访问的数据,提升了访问缓存数据的性能,同时,为了解决机械硬盘寻址慢的问题,它还协助 I/O 调度算法实现了 IO 合并与预读,这也是顺序读比随机读性能好的原因。这些优势,进一步提升了零拷贝的性能。
需要注意的是,零拷贝技术是不允许进程对文件内容作进一步的加工的,比如压缩数据再发送。
另外,当传输大文件时,不能使用零拷贝,因为可能由于 PageCache 被大文件占据,而导致「热点」小文件无法利用到PageCache,并且大文件的缓存命中率不高,这时就需要使用**「异步 IO (直接 IO)」的方式。
在 Nginx 里,可以通过配置,设定一个文件大小**阈值,针对大文件使用异步 IO 和直接 IO,而对小文件使用零拷贝。
绕开 PageCache 的 I/O 叫直接 I/O,使用 PageCache 的 I/O 则叫缓存 I/O。通常,对于磁盘,异步 I/O 只支持直接 I/O。
所以,传输文件的时候,我们要根据文件的大小来使用不同的方式:
Nginx io多路复用(多 Reactor 多进程)、零拷贝技术(阈值,大小文件)
Redis io多路复用(单 Reactor 单进程)、
进程的优先级可以分为,静态优先级和动态优先级:
该算法也有两种处理优先级高的方法,非抢占式和抢占式:
但是依然有缺点,可能会导致低优先级的进程永远不会运行。
产生的四个条件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IHscapxx-1683542094252)(计网操作系统等面经.assets/image-20220328155120636.png#id=UCCPn&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
系统总共资源数,当前剩余的资源数,每个线程最大资源需求数
每个线程有一个最大资源需求数,已经分配了一些资源,如果有线程还想申请一些资源,判断申请的资源数是否大于剩余的资源数,如果大于就试着分配,然后用安全性算法检测此次分配是否会导致系统进入不安全状态,如果进入就不分配,等待。
安全性算法:检测当前剩余的资源数是否满足某个进程的最大需求,如果满足就将其加入安全序列,并将该进程持有的资源回收,继续查看下一个,最终看是否能让所有进程都加入安全序列。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xCJmQyyl-1683542094253)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/image-20220328192152564.png#id=sMTBX&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
定义一个资源分配图:
两种节点:进程节点、资源节点;
两种边:进程节点–>资源节点 进程正在请求资源的边;资源节点–>进程节点 已经分配给进程的资源的边
检测死锁:一次消除与不阻塞进程相连的边,直到无边可消,若资源分配图不可完全简化(不可以消除所有的边)就说明发生了死锁。
解除死锁:资源剥夺法(将进程挂起,剥夺资源)、终止进程法、进程回退法
杀死哪种进程:杀死进程优先级低的、杀死运行时间短的、杀死使用资源多的(为了解除死锁)、杀死批处理式的保留交互式的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ixSXSo8j-1683542094254)(计网操作系统等面经.assets/image-20220328194442014.png#id=ASnWo&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_P9W3RHWG426J8BW7.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VzaWGQbe-1683542094255)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkxNDYwNA==,size_16,color_FFFFFF,t_70.png#id=syvmK&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
定义一个资源分配图:
两种节点:进程节点、资源节点;
两种边:进程节点–>资源节点 进程正在请求资源的边;资源节点–>进程节点 已经分配给进程的资源的边
检测死锁:一次消除与不阻塞进程相连的边,直到无边可消,若资源分配图不可完全简化(不可以消除所有的边)就说明发生了死锁。
解除死锁:资源剥夺法(将进程挂起,剥夺资源)、终止进程法、进程回退法
杀死哪种进程:杀死进程优先级低的、杀死运行时间短的、杀死使用资源多的(为了解除死锁)、杀死批处理式的保留交互式的
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-caJlDuaO-1683542094257)(计网操作系统等面经.assets/image-20220520162452027.png#id=QZDhu&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tsy0MGWG-1683542094258)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/image-20220511153428956.png#id=EH3ez&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_GMSXMP59R9GT9YBP.jpeg" alt="image.png" />
总的来说,Redis 的 SDS 结构在原本字符数组之上,增加了三个元数据:len、alloc、flags,用来解决 C 语言字符串的缺陷
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCdN5Tub-1683542094259)(计网操作系统等面经.assets/516738c4058cdf9109e40a7812ef4239.png#id=lutg4&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_PFUNKMUAZS8GT2ZW.jpeg" alt="image.png" />
压缩列表在表头有三个字段:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QI736MVS-1683542094261)(计网操作系统等面经.assets/a3b1f6235cf0587115b21312fe60289c.png#id=Bb2s3&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
压缩列表节点包含三部分内容:
Redis 对象(List 对象、Hash 对象、Zset 对象)包含的元素数量较少,或者元素值不大的情况才会使用压缩列表作为底层数据结构。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cszsU6Ys-1683542094262)(计网操作系统等面经.assets/2fedbc9cd4cb7236c302d695686dd478.png#id=kFPY2&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_YJUYKJ2PT2S4RNR2.jpeg" alt="image.png" />
如果在一次迁移过程中,再进行rehash咋办?
typedef struct intset { //编码方式 uint32_t encoding; //集合包含的元素数量 uint32_t length; //保存元素的数组 int8_t contents[]; } intset;
假设有一个整数集合里有 3 个类型为 int16_t 的元素。如果再添加一个65535需要用int32_t 类型来保存,所以整数集合要进行升级操作,首先需要为 contents 数组扩容,在原本空间的大小之上再扩容多 80 位(4x32-3x16=80),这样就能保存下 4 个类型为 int32_t 的元素。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u25yBWaG-1683542094263)(计网操作系统等面经.assets/5dbdfa7cfbdd1d12a4d9458c6c90d472.png#id=HvJFC&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
扩容完 contents 数组空间大小后,需要将之前的三个元素转换为 int32_t 类型,并将转换后的元素放置到正确的位上面,并且需要维持底层数组的有序性不变。从后向前,将之前的元素转换为32位。
整数集合是 Set 对象的底层实现之一。当一个 Set 对象只包含整数值元素,并且元素数量不大时,就会使用整数集这个数据结构作为底层实现。
Redis 只有在 Zset 对象的底层实现用到了跳表,跳表的优势是能支持平均 O(logN) 复杂度的节点查找。
每个 listpack 节点结构包含三个方面:
listpack 没有压缩列表中记录前一个节点长度的字段了,listpack 只记录当前节点的长度,当我们向 listpack 加入一个新元素的时候,不会影响其他节点的长度字段的变化,从而避免了压缩列表的连锁更新问题。
string(字符串):常规key-value缓存应用。常规计数: 微博数, 粉丝数。
hash(哈希):string可以存储对象时是将对象序列化为Json字符串存储,当需要修改对象某个字段时不方便,Hash可以将每个对象中字段独立存储,可以针对单个字段做CRUD,key为用户ID,value为name:Tom,age:18
list(列表):关注列表,粉丝列表,简单的消息队列
set(集合):好友关系,利用集合Set的一些命令,比如求交集、并集、差集等1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐
zsetsorted(有序集合):排行榜:在使用关系型数据库(mysql )来做,非常的麻烦,而利用Redis的SortSet(有序集合)数据结构能够简单的搞定。能够记录每个玩家的分数;能够对玩家的分数进行更新;能够查询每个玩家的分数和名次
Redis 对象(List 对象、Hash 对象、Zset 对象)包含的元素数量较少,或者元素值不大的情况才会使用压缩列表作为底层数据结构。
整数集合是 Set 对象的底层实现之一。当一个 Set 对象只包含整数值元素,并且元素数量不大时,就会使用整数集合作为底层实现。
为啥用跳表不用红黑树?
Redis的string类型一共有三种存储方式,当字符串长度小于等于44字节,底层采用embstr,是连续内存分配,44字节加上redisObject和sdshdr8的空间共有64字节,redis底层内存分配算法是采用2的n次方分配的;当字符串长度大于44,底层采用raw;当设置是整数,底层则采用int。
当 int 编码保存的值不再是整数,或大小超过了long的范围时,自动转化为raw。对于 embstr 编码,由于 Redis 没有对其编写任何的修改程序(embstr 是只读的),在对embstr对象进行修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了44个字节。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UUz1DXwb-1683542094272)(计网操作系统等面经.assets/image-20220514150259778.png#id=bihhy&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_U5KXXJG488U59QPY.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-55uSKhgX-1683542094274)(计网操作系统等面经.assets/image-20220514153751799.png#id=J4XTV&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_TBC58QJZH8NUSYVA.jpeg" alt="image.png" />
【流程】:bgrewriteaof 子进程执行 AOF 重写期间,主进程需要执行以下三个工作:
当子进程完成 AOF 重写工作(
主进程收到该信号后,会调用一个信号处理函数,该函数主要做以下工作:
信号函数执行完后,主进程就可以继续像往常一样处理命令了。
在整个 AOF 后台重写过程中,除了发生写时复制会对主进程造成阻塞,还有信号处理函数执行时也会对主进程造成阻塞,在其他时候,AOF 后台重写都不会阻塞主进程。
【写时复制】:
在发生写操作的时候,操作系统才会去复制物理内存,这样是为了防止 fork 创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题。
主进程修改了已经存在 key-value,就会发生写时复制,注意这里只会复制主进程修改的物理内存数据,没修改物理内存还是与子进程共享的。所以如果这个阶段修改的是一个 bigkey,也就是数据量比较大的 key-value 的时候,这时复制的物理内存数据的过程就会比较耗时,有阻塞主进程的风险。
在 Redis 4.0 提出的,该方法叫混合使用 AOF 日志和内存快照,也叫混合持久化。aof-use-rdb-preamble yes
当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。
也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。
删除达到过期时间的key。
在Redis中,同时使用了定期删除和惰性删除。不过Redis定期删除采用的是随机抽取的方式删除部分Key,因此不能保证过期key 100%的删除。
Redis过期策略采用定期删除和惰性删除两部分。定期删除是在Redis内部有一个定时任务,会定期抽取一些key检查是否过期。惰性删除是当用户查询某个Key时,会检查这个Key是否已经过期,如果没过期则返回用户,如果过期则删除。
但是这两个策略都无法保证过期key一定删除,漏网之鱼越来越多,还可能导致内存溢出。当发生内存不足问题时,Redis还会做内存回收。内存回收采用LRU策略,就是最近最少使用。其原理就是记录每个Key的最近使用时间,内存回收时,随机抽取一些Key,比较其使用时间,把最老的几个删除。
内存淘汰策略
Redis会在每一次处理命令的时候(processCommand函数调用freeMemoryIfNeeded)判断当前redis是否达到了内存的最大限制,如果达到限制,则使用对应的算法去处理需要删除的key。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g8E05Oj4-1683542094275)(计网操作系统等面经.assets/image-20220523211827887.png#id=kjghb&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AnqKAQO7-1683542094276)(计网操作系统等面经.assets/image-20220523211742363.png#id=V2IqP&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bugkjMu4-1683542094278)(计网操作系统等面经.assets/image-20220523211410484.png#id=CZ9OH&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cjAsNoSA-1683542094279)(计网操作系统等面经.assets/image-20220523212648652.png#id=FcEjS&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_4U82F6AYGWZGM872.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afL63XxD-1683542094281)(计网操作系统等面经.assets/image-20220521160531117.png#id=zF3Z0&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
单例池:存放完整的对象,经过完整声明周期的bean对象concurrentHashMap
二级缓存:不完整的对象,AOP生成的代理对象,或者原始对象HashMap
三级缓存:执行Bean AOP的SingletonFactory工厂,lamda表达式,每个单例对象都会放入三级缓存HashMap
正在创建的bean
填充aService属性的时候: 先在单例池中找Bean–>找不到–>二级缓存–>找不到–>看看是否正在被创建set–>出现了循环依赖–>到三级缓存找–>找到了λ表达式–>执行AOP–>得到aService代理对象–>将代理对象放到二级缓存中(要是aService没有切面,就生成原始对象,放入到二级缓存中)
从三级缓存中找对象就是在执行AOP,找到以后就将其从三级缓存中删除,放入到二级缓存中为了保证只执行一次AOP。getSingleton加锁,保证添加到二级缓存和删除三级缓存的原子性
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 查询一级缓存 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //若一级缓存内不存在,查询二级缓存 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { //若二级缓存内不存在,查询三级缓存 ObjectFactory> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //若三级缓存中的,则通过工厂获得对象,并清除三级缓存,提升至二级缓存 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Vn6MR5x-1683542094281)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/Image-16473997723591.png#id=fICEt&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_2XTXRUR4SQZ8P5V2.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rhdUKfOc-1683542094283)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/Image-164706706896711.png#id=qvP1A&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_FXKKSKREDK2G548R.jpeg" alt="image.png" />
Spring在启动的时候需要「扫描」在XML/注解/JavaConfig 中需要被Spring管理的Bean信息,将这些信息封装成BeanDefinition,最后会把这些信息放到一个beanDefinitionMap中,这个Map的key应该是beanName,value则是BeanDefinition对象
首先根据BeanDefinition得到类的元数据信息,
总共分为四个阶段:实例化、填充属性、初始化、销毁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HJvoznk0-1683542094285)(计网操作系统等面经.assets/Image-16467250646771-16470670662249.png#id=dDUDK&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_JSTTF4PF74NMTUQW.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qn3UZ276-1683542094286)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/v2-5b38cefe3a94a211e42eaa8e49b66a92_720w-16470670644978.jpg#id=QAaue&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_XGFG8APBTV5TTVKY.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i0LIbzPF-1683542094288)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/image-20220307093958729-16470670589596.png#id=HKD7L&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZ8RcSj9-1683542094289)(计网操作系统等面经.assets/image-20220307094003990-16470670609197.png#id=MlLcy&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
拦截器的三个方法:
多个拦截器的执行顺序: 拦截器A的preHandler–>拦截器B的preHandler–>B的postHandler–>A的postHandler–>B的afterCompletion–>A的afterCompletion
@EnableAutoConfiguration、@Configuration、@ConditionalOnClass是自动配置的核心。
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,这些自动配置类都是以AutoConfiguration结尾来命名的。每一个自动配置类对应一个以xxxProperties.java结尾命名的配置文件,会读取配置文件进行自动配置功能。
怎么告诉spring容器@Configuration的类路径
这个就是springboot要做的事。
springboot启动的时候会扫描所以依赖的jar包下面的META-INF文件夹下面的spring.factories文件。
然后找这个文件的关键字org.springframework.boot.autoconfigure.EnableAutoConfiguration=\,这个关键字后面的字符串就是告诉spring要加载哪些@Configuration的类
Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域。Spring3 为 Bean 定义了五种作用域,具体如下。
1)singleton:单例模式,唯一 bean 实例,Spring 中的 bean 默认都是单例的,对单例设计模式的应用。
2)prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。
3)request:在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
4)session:在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
5)global Session:在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。
在容器中获取bean的方式:
总结:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-INB2qsFZ-1683542094289)(计网操作系统等面经.assets/image-20220307154322032-16470670521525.png#id=WzdOP&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_74URTWP37E8RUMV4.jpeg" alt="image.png" />
@Bean public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(false); }
动态代理
基于接口的 JDK 动态代理:需要实现接口
基于继承的 CGLib 动态代理:目标对象不需要实现接口,底层是通过继承目标对象产生代理子对象,代理子对象中继承了目标对象的方法,并可以对该方法进行增强。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qYxc3AmQ-1683542094290)(计网操作系统等面经.assets/image-20220309104228346-16470669191702-165607988230287.png#id=UxtVH&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_F42HRCDV6T7TPUT3.jpeg" alt="image.png" />
**IOC:**IOC容器是一种设计思想,它能够帮助我们创建对象和管理对象之间的依赖关系,他有一个强大的功能DI依赖注入,我们可以通过Java代码或者xml配置文件的方式,把我们想要注入对象的所有依赖的bean自动地注入进去,通过byname或者bytype的方式,正是有了依赖注入IOC才有了强大的解耦的功能。
比如说JDBCtemplate 注入到容器中他是需要数据源的,如果JDBCtemplate和德鲁伊数据源强耦合在一起就会导致一个问题,我们使用JDBCtemplate的时候就必须使用德鲁伊,IOC容器帮助我们让JDBCtemplate只依赖于一DataSource 接口,不需要依赖具体的实现;这样我们自动给容器注入一个德鲁伊的数据源他就自动的给注入到JDBCtemplate中了,如果我们注入其他的数据源也是一样的,这样JDBCtemplate和数据源就实现了解耦合。
AOP:在日常工作中会遇到许多重复的代码,比如说事物、日志,我们需要在很多类里面重复的写这些代码,比如说事物,我们需要在所有的service层里面开启事、 提交、回滚,AOP就可以将这些重复的代码提取封装起来,我们可以在需要的时候切入到我们想要切入到的类里, 这样可以减少系统中的冗余代码,降低了模块间的耦合度,同时提高了系统的复用性和可维护性。AOP的实现是依赖于动态代理实现的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,就会使用CGlib动态代理生成一个被代理对象的子类来作为代理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1C49Xyeq-1683542094292)(计网操作系统等面经.assets/v2-11c9b17f5f79909cf4cbae580ba63493_720w-16470669237673.jpg#id=nUW9b&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
1)XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入。
优点:• 事务的强一致性,满足ACID原则 • 常用数据库都支持,实现简单,并且没有代码侵入
缺点:• 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差 • 依赖关系型数据库实现事务
过程:
• RM一阶段的工作: ① 注册分支事务到TC ② 执行分支业务sql但不提交 ③ 报告执行状态到TC
• TC二阶段的工作:TC检测各分支事务执行状态。① 如果都成功,通知所有RM提交事务② 如果有失败,通知所有RM回滚事务
• RM二阶段的工作: 接收TC指令,提交或回滚事务
2)AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式。数据库快照,一阶段执行完提交,二阶段如果有问题再根据快照回滚。
• 阶段一RM的工作:• 注册分支事务 • 记录undo-log(数据快照)• 执行业务sql并提交 • 报告事务状态
• 阶段二提交时RM的工作:• 删除undo-log即可
• 阶段二回滚时RM的工作:• 根据undo-log恢复数据到更新前
• XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
• XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
• XA模式强一致;AT模式最终一致
3)TCC模式:最终一致的分阶段事务模式,有业务侵入。支持非关系型数据库redis
Try:资源的检测和预留;
Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
Cancel:预留资源释放,可以理解为try的反向操作。
资源预留、幂等性
2PC模式:seata的AT高并发场景不太合适
最终采用:基于RabbitMQ的异步的可靠消息、最终一致性方案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F8lWGvpC-1683542094293)(计网操作系统等面经.assets/image-20220316185122347.png#id=W2a84&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_2DKG6JDXBUZPH4GE.jpeg" alt="image.png" />
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2roIq61T-1683542094293)(计网操作系统等面经.assets/image-20220318152254738.png#id=SImLW&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
最终采用加乐观锁保证库存不超买,即先查库存,如果大于购买数量,在减库存的时候再次判断库存是否大于购买数量
UPDATE `wms_ware_sku` SET stock_locked = stock_locked + #{num} WHERE sku_id = #{skuId} AND ware_id= #{wareId} AND stock - stock_locked > #{num}
解决方案:
最终采用:spring-cache。缓存数据加过期时间,数据过期下次查询触发主动更新;读数据时,加上分布式的读写锁,保证写的时候,不出现不一致。
实在不行,用canal订阅MySQL
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eM0rDyjn-1683542094295)(计网操作系统等面经.assets/image-20220316145347774.png#id=A9PLv&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
可能出现的问题:
1)互斥锁:线程一发现缓存过期,就去获取锁,查数据库,其他线程此时再来查时,发现过期去数据库查时,就会阻塞。
优缺点:没有额外的内存消耗;保持一致性;实现简单;线程需要等待,性能受到影响;可能死锁(两个不同业务,互相拿到对方的 锁)
实现:可以使用redisson的分布式锁进行实现,也可以自己实现,要保证加锁和设置超时时间原子性、查看锁验锁和删除锁的原子 性。
2)逻辑过期:添加一个逻辑过期的字段,如果线程一发现缓存逻辑过期后,就开启新的线程取更新缓存,自己返回旧的数据,此时如果有别的线程来获取数据,发现过期后,再开启新线程时发现有锁,就返回旧的数据。比如存一个字段,是放缓存时的时间加三十分钟,线程一取来数据判断是否逻辑过期。
优缺点:线程无需等待,性能好;不保证一致性;有额外的内存消耗;实现复杂
实现:判断是否过期,是则获取分布式锁,开启新的线程更新数据,其他线程获取不到分布式锁,判断过期直接返回旧数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyP80KsM-1683542094295)(%E8%AE%A1%E7%BD%91%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AD%89%E9%9D%A2%E7%BB%8F.assets/image-20220321145206435.png#id=l4WTc&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&upload/website_attach/202403/1_AZ8BFAFRF8GSAFXW.jpeg" alt="image.png" />
1)缓存空对象:
优点:实现简单,维护方便
缺点:额外的内存消耗、可能造成短期的不一致
2)布隆过滤:
优点:内存占用较少,没有多余key
缺点:实现复杂、存在误判可能;不存在的时候真的不存在,存在的时候去查库可能不存在。
原理:如果我们要映射一个值到布隆过滤器中,我们需要使用多个不同的哈希函数生成**多个哈希值,**并对每个生成的哈希值指向的 bit 位置 1
1)限流策略:通过sential
2)多级缓存: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aOugbzRO-1683542094296)(计网操作系统等面经.assets/image-20220316190629840.png#id=D9MX0&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
可以通过redis自己实现分布式锁、或者redisson实现
redisson加分布式锁,可以保证可重入、可重试、可自动续期、分布式下保证主从一致
•可重入:利用hash结构记录线程id和重入次数
•可重试:利用信号量和 PubSub 功能实现等待、唤醒,获取锁失败的重试机制
•超时续约:利用watchDog,每隔一段时间(releaseTime / 3),重置超时时间
•主从一致性:联锁,利用多个独立redis节点代替主从,所有节点都获取锁成功才算获取锁成功,将锁放入到List中,依次获取锁,都获取到才算成功,在都获取到后如果设置了超时时间,就会重置所有结点的超时时间,没设置就用看门狗续期。
限流算法常见的有三种实现:滑动时间窗口、令牌桶算法、漏桶算法。Gateway则采用了基于Redis实现的令牌桶算法。
而Sentinel内部却比较复杂:
MyISAM为啥比InnoDB快:InnoDB只有一个文件,数据和索引都在一个文件中,而MyISAM有两个文件,一个数据一个索引;InnoDB有非聚簇索引可能需要回表,聚簇索引存放的是数据,在内存中放得少,MyISAM都是非聚簇,叶子节点存放数据地址。MyISAM只缓存索引,不缓存真实数据;InnoDB不仅缓存索引还要缓存真实数据, 对内存要求较高,而且内存大小对性能有决定性的影响。
InnoDB要维护MVCC,每当select时要看查询出来的记录是否对当前事务可见,适合数据量大的情况。
MyISAM增加和查询快一些,适合数据量小的情况。
const:**主键/唯一**索引的**等值**查询 eq_ref:**连接**查询时,被驱动表通过**主键/唯一**索引**等值**匹配时 ref:**普通索引**与常量进行**等值**匹配时 range:只检索给定范围的行,使用一个索引来选择行,能根据索引做范围的扫描 index:可以使用覆盖索引,但仍需要扫描全部的索引记录
Explain中的字段:
type:
Using index:使用覆盖索引并且不用回表 Using index condition:索引下推 Using temporary、Using filesort、Using Where、Using join buffer (Block Nested Loop)
possible_keys、key、key_len
extra:
show profies; show profile cpu,block io for query 2;
如果求前10个最小的数,则先拿前10个数建一个大顶堆,然后遍历剩下的数,只要比堆顶数字大就舍弃,比堆顶数字小,就移除堆顶数字,替换为比它小的这个数字,然后重新调整堆为大顶堆;遍历完成后,大顶堆中的10个数就是100万中最小的10个数
如果求前10个最大的数,则先拿前10个数建一个小顶堆,同样遍历剩下的数,比堆顶小的舍弃,比堆顶大的,则移除堆顶,替换为比它大的这个元素,然后重新调整堆为小顶堆;遍历完成后,小顶堆中的10个数就是100万中最大的10个数
解决方式:IP地址最多有 2^32 = 4G 种取值情况,所以不能完全加载到内存中进行处理,采用 hash分解+ 分而治之 + 归并 方式: (1)按照 IP 地址的 Hash(IP)%1024 值,把海量IP日志分别存储到1024个小文件中。这样,每个小文件最多包含4MB个IP地址。(2)对于每一个小文件,构建一个IP为key,出现次数为value的Hash map,同时记录当前出现次数最多的那个IP地址 。
(3)然后再在这1024组最大的IP中,找出那个频率最大的IP。
Counting Bloom Filter:将标准Bloom Filter位数组的每一位扩展为一个小的计数器(Counter),在插入元素时给对应的k(k为哈希函数个数)个Counter的值分别加1,删除元素时给对应的k个Counter的值分别减1,Counting Bloom Filter通过多占用几倍的存储空间的代价,给Bloom Filter增加了删除操作。
Spectral Bloom Filter: 可以统计频次,在CBF中加入一个元素时,k个哈希位置的counter都要加1,也就是说,如果不考虑碰撞(collision),出现次数为n的元素对应的k个counter的值都为n。即使考虑到碰撞的因素,只要k个位置不全出现碰撞,k个counter中的最小值仍是n
开启 -XX:+HeapDumpBeforeFullGC,导出dump文件,
考虑到每个节点的硬件配置有所区别,我们可以引入权重值,将硬件配置更好的节点的权重值设高,然后根据各个节点的权重值,按照一定比重分配在不同的节点上,让硬件配置更好的节点承担更多的请求,这种算法叫做加权轮询。
加权轮询算法使用场景是建立在每个节点存储的数据都是相同的前提。所以,每次读数据的请求,访问任意一个节点都能得到结果。
但是,加权轮询算法是无法应对「分布式系统」的,因为分布式系统中,每个节点存储的数据是不同的
一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致哈希算法是对 2^32 进行取模运算,是一个固定的值。
一致性哈希是指将「存储节点」和「数据」都映射到一个首尾相连的哈希环上,如果增加或者移除一个节点,仅影响该节点在哈希环上顺时针相邻的后继节点,其它数据也不会受到影响。但是一致性哈希算法不能够均匀的分布节点,会出现大量请求都集中在一个节点的情况,在这种情况下进行容灾与扩容时,容易出现雪崩的连锁反应。
为了解决一致性哈希算法不能够均匀的分布节点的问题,就需要引入虚拟节点,对一个真实节点做多个副本。不再将真实节点映射到哈希环上,而是将虚拟节点映射到哈希环上,并将虚拟节点映射到实际节点,所以这里有「两层」映射关系。
另外,虚拟节点除了会提高节点的均衡度,还会提高系统的稳定性。当节点变化时,会有不同的节点共同分担系统的变化,因此稳定性更高。而且,有了虚拟节点后,还可以为硬件配置更好的节点增加权重,比如对权重更高的节点增加更多的虚拟机节点即可。
在实际的工程中,虚拟节点的数量会大很多,比如 Nginx 的一致性哈希算法,每个权重为 1 的真实节点就含有160 个虚拟节点。
总结:
首位相连的哈希环上,增加/删除节点只影响顺时针相邻的节点。
不够均匀,多个请求会到一个节点,通过加虚拟节点来提高节点的均衡度;提高稳定性,节点发生变化时,会有不同节点分担系统变化;还可以通过控制虚拟节点的数量来增加权重。
正向代理:首先请求代理服务器,然后代理服务器帮我们去快速访问国外的网站,对于这种代理方式,我们就称之为正向代理
反向代理:机房通讯通常采用局域网交换机,internet网用户请求是无法直接访问到局域网内的web服务的,因此这个时候,你需要一台反向代理服务器来接收internet web请求,然后将请求分发到局域网中的不同主机上进行处理,处理完成之后再做出响应
优点:时效性较强,可以立即得到结果 缺点:耦合度高、性能和吞吐能力下降、有额外的资源消耗、有级联失败问题
同步通信:
好处:
异步通信:
缺点:
select id from table where b = 8 for update;普通索引 b 上共有两个锁,分别是 next-key lock (4,8] 和间隙锁 (8,16) 。
所以在线上千万不要执行没有带索引条件的 update 语句,不然会造成业务停滞,
• 断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点。 • 判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举 • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高 • 最后是判断slave节点的运行id大小,越小优先级越高
**哨兵集群:**哨兵监测、故障转移、通知,每秒ping、主观下线,客观下线;AP
选择新主结点:
**分片集群:**0-16384散列插槽,对{key}取hash,然后对16384取余。
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
• 海量数据存储问题
• 高并发写的问题
使用分片集群可以解决上述问题,分片集群特征:
• 集群中有多个master,每个master保存不同数据
• 每个master都可以有多个slave节点
• master之间通过ping监测彼此健康状态
• 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
职责划分:
**分片集群:**elasticsearch会通过hash算法来计算文档应该存储到哪个分片
脑裂问题:
elasticsearch的分布查询分成两个阶段:
**故障转移:**集群的master节点会监控集群中的节点状态,如果发现有节点宕机,会立即将宕机节点的分片数据迁移到其它节点,确保数据安全。CP
kafka怎么解决消息有序性
点赞不用set和bitmap该怎么做,如果有十万人点赞该如何处理