1、TCP与UDP的区别
UDP | TCP | |
---|---|---|
是否连接 | 无连接,即刻传输 | 面向连接,三次握手 |
是否可靠 | 不可靠传输,网络波动拥堵也不会减缓传输 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通 信 | 只能是一对一通信 |
传输方式 | 面向报文,可能出现乱序丢包 | 面向字节流,保证传输顺序可靠 |
首部开销 | 首部开销小,仅8字节 | 首部小20字节, 大60字节 |
场景 | 适用于实时应用 (IP电 话、视频会议、直 播等) | 适用于要 求可靠传输的应用,例如文件传输 |
1.1 TCP/IP网络模型
计算机之间要相互通信就要指定统一的规则:协议
TCP/IP是互联网各种协议的总称,包括:TCP、UDP、IP、ICMP、SMTP、HTTP等
协议可以划分为四层,链路层、网络层、传输层、应用层
链路层:负责封装和解封装IP报文,发送和接收ARP/RARP报文
网络层:负责路由以及把分组报文发送给目标网络或主机
传输层:负责对报文进行分组和重组,并以TCP或UDP协议格式封装报文
应用层:负责向用户提供应用程序
OSI七层模型 | TCP/IP概念模型 | 功能 | TCP/IP协议族 |
---|---|---|---|
应用层 | 应用层 | 文件传输,电子邮件,文件服务,虚拟终端 | TFTP/HTTP/SNMP/FTP/SMTP/DNS/Telnet |
表示层 | 数据格式化,代码转换,数据加密 | / | |
会话层 | 解除或建立与别的节点的联系 | / | |
传输层 | 传输层 | 提供端对端的接口 | TCP/UDP |
网络层 | 网络层 | 为数据包选择路由 | IP/ICMP/RIP/OSPF/BGP/IGMP |
数据链路层 | 链路层 | 传输有地址的帧以及检错功能 | SLIP/CSLIP/PPP/ARP/RARP/MTU |
物理层 | 以二进制数据形式在物理媒体上传输数据 | ISO2110/IEEE803/IEEE802.2 |
网络体系结构中通信的建立都是在通信双方同层进行的,数据在发送端经过各层时都要附加上相应层的协议头协议尾(仅数据链路层要封装协议尾)
1.2 UDP(用户数据包协议)
在网络中UDP与TCP协议一样用于处理数据包,是一种无连接不可靠的传输层协议,UDP不提供数据包分组、组装,不能对数据包进行排序,无法保证数据的安全(不可靠:发送端UDP给数据增加一个UDP头就传递给网络层,接收端UDP去除IP报文头就传递给应用层);
UDP支持一对一、一对多、多对一、多对多的传输方式(单播、多播。广播),UDP会不管网络波动一直以恒定速度发送数据,容易丢包,适合实时性要求高的场景(直播、电话会议)
UDP头部数据量小,传输数据报效率高
1.3 TCP(传输控制协议)
TCP协议是一种面向连接的、可靠的、基于字节流的传输层协议,发送数据前通信双方必须建议一条连接(客户端和服务端各保存一份对方的ip、端口等信息),通过三次握手、四次挥手能够保证数据完整顺序传输
1.3.1 TCP三次握手四次挥手
三次握手的本质是先确认通信双方收发数据的能力,A将一个消息发给B,B收到后就知道A的发件能力可以和自己的收件能力可以,然后B将确认信息发回A,A就知道B的收发能力可以,自己的收发能力可以,然后A再将反馈发给B,B就得知自己的发件能力可以,A的收件能力可以,双方正式可以开始通信
第一次握手:
客户端向服务端发起连接请求,客户端随机生成一个序列号ISN(x),客户端向服务端发送的报文段包含SYN标志位(SYN=1),序列号seq=x
第二次握手:
服务端收到客户端发的报文,发现SYN=1,得知是一个连接请求,将客户端的序列号seq存下,随机生成一个服务端的序列号(y),然后给客户端回复一段报文,包含SYN和ACK标志(SYN=1,ACK=1)、序列号seq=y、确认号ack=x+1
第三次握手:
客户端收到服务端的回复后,发现ACK=1,并且ack=x+1,知道服务端已经收到了序列号为x的那段报文,还发现SYN=1,知道服务端同意了这次连接,于是将服务端的seq存下,然后客户端再回复一段报文给服务端,包含ACK标志位(ACK=1)、ack=y+1、seq=x+1(第一次握手占据一个序列号,所以这一次要加一,不携带数据的ACK报文不占据序列号,所以后面正式发送数据seq依然为x+1),当服务端收到报文后发现ACK=1并且ack=y+1,就知道客服端收到序列号为y的报文了,就这样客服端服务端通过TCP建立了连接
四次挥手的目的是关闭一个连接
第一次挥手:
当客户端的数据都传输完成,客户端发出连接释放报文(没传完也可发送),包含FIN标志位(FIN=1)、序列号seq=u(u=x+1+发送数据的字节数),客户端发出FIN报文后不可发送数据只可接收数据;(FIN报文携带数据也要占据一个序列号)
第二次挥手:
服务端收到客户端发的FIN报文后给客户端回复确认报文,含ACK标志位(ACK=1)、确认号ack=u+1、序列号seq=v(v=y+1+发客户端FIN报文前服务端回复的数据字节数),此时服务端处于关闭等待状态,不会立马向客户端发FIN报文,要等数据发完
第三次挥手:
服务端将最后数据发送完毕后就向客户端发出连接释放报文,包含FIN和ACK标志位(FIN=1,ACK=1)、确认号ack=u+1(与第二次挥手一致)、序列号seq=w(w=v+最后发的数据字节数)
第四次挥手:
客户端收到服务端发的FIN报文后,向服务端发出确认报文,包含ACK标志位(ACK=1) 、确认号ack=w+1、序列号seq=u+1,客户端发出确认报文后不会立马释放TCP连接,而是要经过2MSL(最长报文段寿命的两倍时长)后才释放TCP连接,服务端一旦收到客户端发出的确认报文就会立马释放TCP连接
2、什么是正向代理和反向代理
正向代理:指一个位于客户端和目标服务器之间的服务器(代理服务器),为了从目标服务器取得内容,客户端向代理服务器发送一个请求并指定目标,然后代理服务器向目标服务器转交请求并将获得的内容返回给客户端,这样目标服务器不知道真正的客户端是谁
正向代理作用:突破访问限制、提高访问速度(通常代理服务器都会设置较大的硬盘缓冲区,会将部分请求的响应保存到缓冲区)、隐藏客户端真实IP(免受攻击)
反向代理:指代理服务器来接收客户端的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给请求连接的客户端,这样客户端不知道真正的目标服务器是谁
反向代理作用:隐藏服务器真实IP、负载均衡(根据真实服务器的负载情况,将客户端请求分发到不同的真实服务器上)、提高访问速度(反向代理服务器可以对于静态内容及短时间内有大量访问请求的动态内容提供缓存服务)、提供安全保障(反向代理服务器可以作为应用防火墙,为网站提供基于Web攻击行为的防护,更容易排查恶意软件,还可以为后端服务器统一提供加密,HTTP访问认证等)
3.JVM运行时数据区域
程序计数器:
是当前线程所执行的字节码的行号指示器,内存空间小线程私有,字节码解析器的工作是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程会恢复等功能都需要依赖这个计数器来完成;
线程执行Java方法时,计数器记录的是正在执行的虚拟机字节码指令的地址,线程执行Native方法,计数器的值为Undefined;
此内存区域是唯一一个JVM规范中没有规定OutOfMemoryError情况的区域。
虚拟机栈:
线程私有,生命周期与线程一致,用于存储局部变量表、操作数栈、动态链接、方法出口等信息(局部变量表:存放编译器可知的基本数据类型和对象引用);
如果请求的栈深度大于最大可用深度,则抛出stackOverflowError,如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError
本地方法栈:
与虚拟机栈的作用一致,只不过虚拟机栈是服务Java方法的,而本地方法栈是为虚拟机调用Native方法服务的;
堆:
Java虚拟机中内存最大的一块,被所有线程共享,主要存储对象实例和数组,内部划分多个线程私有的分配缓存区TLAB,可以位于物理上不连续但逻辑上连续的空间;若堆的空间不够实例分配,则OutOfMemoryError
方法区:
属于共享内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等;
4.JVM如何判断一个对象是否可回收
引用计数法:
给对象添加一个引用计数器,新增一个引用时计数加一,引用释放时计数减一,计数为零时可以回收,难以解决对象循环引用问题(对象相互引用,即使不被程序使用也无法回收)
可达性分析:
从GC Roots(垃圾回收根节点,表示一组对象是活动对象不可回收)开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,证明此对象不可用
在可达性分析中判断为不可达的对象会被第一次标记并进行一次筛选,筛选条件是此对象是否有必执行finalize方法,当对象没有覆盖finalize方法或finalize方法已经被虚拟机调用过,就被认为没必要执行
当被认为有必要执行finalize方法时,对象会被放入一个叫F-Queue的队列中等待执行,在执行finalize方法之前GC将对F-Queue中的对象进行第二次小规模的标记,此时对象如果与引用链上的任何对象关联则不被回收
5.JVM的垃圾回收算法有哪些
标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
复制算法:将可用内存按容量划分为相等的两块,每次只使用其中一块,当一块用完了,就将还存活的对象复制到另一块上,然后把用完了的一块内存空间清空
标记-压缩算法:首先标记出所有需要回收的对象,让所有没标记的对象向一端移动,然后清理掉这一端以外的空间
分代收集算法:把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法,新生代每次垃圾回收有大量对象死去应该选用 复制算法,老年代中对象存活率高应该使用标记-清除算法
6.JVM内存分配策略和回收策略
- 对象优先在堆的Eden区分配
- 大对象直接进入老年代
- 长期存活的对象直接进入老年代
为了高效垃圾回收,虚拟机将堆内存划分为新生代、老年代和永久代三个区域
新生代:
新生代由Eden与Survivor构成(默认大小比例8:1),Eden是新生代中最大的区域,用于存放新创建的对象,当Eden空间满时会触发一次Minor GC(新生代垃圾回收),将不再被引用的对象回收掉,并将存活的对象复制到Survivor空间,然后清空Eden
Survivor空间通常分为两个相等大小的区域,通常称为S0和S1,当一次Minor GC发生时,Eden存活的对象复制到其中一个空间S0,另一个空间S1用于垃圾回收,当再次Minor GC时,将Eden和S0中存活的对象移动到S1区域,这样不停互换,对象从移到Survivor中开始累加GC年龄,当对象的GC年龄超过默认阈值15时,会将该对象移动到老年代
老年代:
当老年代空间不足时会触发Major GC/Full GC,速度比Minor GC慢10倍以上,Major GC会对新生代、老年代和永久代(或元空间)进行垃圾回收
永久代:(Java8及以后改用元空间)
用于存储类的元数据(类的结构信息、方法信息、字段信息和常量池等),永久代的大小需要根据应用程序的类加载需求来分配,由于大小通常有限容易溢出,永久代的垃圾回收通常发生在应用程序停止时
元空间:
元空间不再是堆内存的一部分,是本机内存中的一个区域,大小不再受限于永久代的大小,可以根据程序需求动态增长直到本机内存限制,当类加载器不再引用某些类时,这些类及其相关元数据将被回收
7.类的生命周期
解析阶段可以在初始化之后再开始(运行时绑定、动态绑定、晚期绑定)
加载:JVM查找并加载类的二进制数据
- 通过一个类的全限定名来获取定义此类的二进制字节流(ZIP包、网络、JSP生成、运算生成、数据库读取)
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象作为方法区中此类的各种数据的访问入口
加载阶段完成后,二进制字节流就按照JVM所需的格式存储在方法区中(永久代/元空间)
验证:确保从class文件中所加载的字节流符合当前JVM要求,且不会危害虚拟机自身的安全
文件格式验证:
- 是否以魔数0xCAFEBABE开头
- 主、次版本号是否在当前虚拟机处理范围内
- 常量池的常量是否有不被支持常量的类型(检查常量tag标志)
- 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
- CONSTANT_Utf-8_info型的常量中是否有不符合UTF8编码的数据
- Class文件中各个部分集文件本身是否有被删除的附加的其他信息
只有通过这个阶段的验证后,字节流才会进入内存的方法区进行存储,后面三个验证阶段都是基于方法区的存储结构进行的,不再直接操作字节流
元数据验证:
- 个类是否有父类(除java.lang.object以外)
- 这个类的父类是否继承了不允许被继承的类(final修饰的类)
- 若这个类不是抽象类,是否实现了其父亲或接口之中要求实现的所有方法
- 类中的字段、方法是否与父类产生矛盾(覆盖父类final字段、出现不符合规范的重载)
这一阶段主要是对类的元数据信息进行语义校验,保证不存在不符合Java规范的元数据信息
字节码验证:
- 保证任意时刻操作数栈的数据类型与指令代码序列都配合工作(不会出现按照long类型读一个int型数据)
- 保证跳转指令不会跳转到方法体以外的字节码指令上
- 保证方法体中的类型转换是有效的(子类对象赋值给父类数据类型是安全的,反过来不合法)
这是验证过程中最复杂的阶段,主要目的是通过数据流和控制流分析,确定语义是合法的、符合逻辑的,这个阶段对类的方法体进行校验分析,保证校验类的方法在运行时不会做出危害虚拟机安全的事件
符号引用验证:
- 符号引用中通过字符串描述的全限定名能否找到对应的类
- 符号引用中的类、字段、方法能否可被当前类访问
准备:
这个阶段为类分配内存并设置类变量初始值为默认值,内存在方法区中分配(只分配类的静态变量,不包括实例变量)
解析:
JVM将常量池中的符号引用(一组用来描述目标的字面量,就是静态的占位符)替换为直接引用(内存中直接指向目标的指针、相对偏移量或间接定位到目标的句柄),解析主要针对类或接口、字段、类方法、方法类型等
初始化:在该阶段才真正开始执行类中的Java代码,为类的静态变量赋予设定的初始值
使用:类被初始化后就可以使用了,包括创建实例、调用类的方法、访问类的字段等
卸载:
当一个类被判定为无用类时(类的所有实例都已被收回、加载该类的类加载器已被回收、该类对应的java.lang.Class对象没有在任何地方被引用),才可以被卸载
8.分布式事务常见的解决方案
分布式事务是指存在多个跨库事务的事务一致性问题,或者是指在分布式架构下有多个应用节点组成的多个事务之间的事务一致性问题;目前主流的分布式事务解决方案有两种:
- 一种是基于XA协议实现的强一致性事务方案:Atomikos、Seata中的XA事务模型2PC(基于CAP理论可以知道,如果要保证分布式事务的强一致性,就必然会带来性能的影响,所以强一致性事务性能会比较低)
- 另一种是基于BASE理论下的弱一致性事务解决方案:TCC事务模型、基于可靠性消息的最终一致性方案、Seata的Saga事务模型(最终一致性事务损失了数据的强一致性,通过异步补偿的方式达到数据的最终一致,因此在性能上较好,更适合并发量比较高的场景)
两阶段提交(2PC):(基于XA协议)
2PC,即两阶段提交,它将分布式事务的提交拆分为 2 个阶段: prepare和 commit/rollback,即准备阶段和提交执行阶段。在 prepare准备阶段需要等待所有参与子事务的反馈,因此可能造成数据库资源锁定时间过长,不适合并发高以及子事务生命周长较长业务场景。并且若协调者宕机,所有的参与者都收不到提交或回滚指令
三阶段提交(3pc):
三阶段提交分别是: CanCommit,PreCommit 和 doCommit 。 3PC 利用超时机制解决了 2PC 的同步阻塞问题,避免资源被永久锁定,进一步加强了整个事务过程的可靠性。但是 3PC 同样无法应对类似的宕机问题, 只不过出现多数据源中数据不一致问题的概率小。
事务补偿(TCC):
TCC 采用了补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认
和补偿(撤销)操作,分为三个阶段:Try、Confirm、Cancel
- try 阶段:尝试去执行,完成所有业务的一致性检查,预留必须的业务资源。
- Confirm 阶段:该阶段对业务进行确认提交,不做任何检查,因为 try 阶段已经检查过了,默认 Confirm 阶段是不会出错的。
- Cancel 阶段:若业务执行失败,则进入该阶段,它会释放 try 阶段占用的所有业务资源,并回滚 Confirm 阶段执行的所有操作。
TCC 方案让应用可以自定义数据库操作的粒度,降低了锁冲突,可以提升性能。但是应用侵入性强,try、confirm、cancel 三个阶段都需要业务逻辑实现
本地消息表(异步确保):
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ 发送到消息的消费方。如果消息发送失败,会进行重试发送。消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。这种方案遵循BASE 理论,采用的是最终一致性,是这几种方案里面比较适合实际业务场景的,即不会出现像2PC 那样复杂的实现 ( 当调用链很长的时候, 2PC 的可用性是非常低的 ) ,也不会像TCC那样可能出现确认或者回滚不了的情况。优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET 中 有现成的解决方案。缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
MQ事务消息:
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如RabbitMQ 和 Kafka 都不支持。
以阿里的 RocketMQ 中间件为例,其思路大致为:第一阶段Prepared 消息,会拿到消息的地址。 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ 会定期扫描消息集群中的事务消息,这时候发现了 Prepared 消息,它会向消息发送者确认,所以生产方需要实现一个check 接口, RocketMQ 会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。遗憾的是, RocketMQ 并没有 .NET 客户端。优点: 实现了最终一致性,不需要依赖本地数据库事务。缺点: 实现难度大,主流 MQ 不支持,没有 .NET 客户端, RocketMQ 事务消息部分代码也未开源。
Saga模式:
- Saga 是一种基于补偿操作的分布式事务模型,将长时间事务拆分为一系列小事务(称为 Saga 步骤)。
- 每个 Saga 步骤都有自己的补偿操作,用于撤销之前的操作。
- 如果某个步骤失败,系统会执行逆向操作来回滚之前的步骤,或者执行补偿操作以修复问题。
适用于复杂的、长时间运行的事务,如订单处理、航班预订等。
Saga 模型提供了更大的灵活性,允许系统在错误发生时采取适当的措施,而不是简单地回滚。
9.shiro认证流程
- 系统调用subject主体的login方法将用户信息token提交给SecurityManager对象认证
- SecurityManager将认证操作委托给认证器对象Authenticator(认证管理器),认证管理器里有认证策略Authentication Strategy(认证方式)
- Authenticator将身份信息传递给Realm(完成数据的加载和封装)。
- Realm访问数据库获取用户信息然后对信息进行封装并返回给认证管理器Authenticator
- Authenticator 对realm返回的信息进行身份认证(把用户输入的和数据库里查询到的信息做比对)。
10.SpringMVC处理请求流程
- 用户发起请求,请求先被 Servlet 拦截转发给 Spring MVC 框架
- Spring MVC 里面的 DispatcherSerlvet 核心控制器,会接收到请求并转发给处理器映射器 HandlerMapping
- HandlerMapping 负责解析请求,根据请求信息和配置信息找到匹配的 Controller 类,不过这里如果有配置拦截器,就会按照顺序执行拦截器里面的 preHandle 方法
- 找到匹配的 Controller 以后,把请求参数传递给 Controller 里面的方法
- Controller 中的方法执行完以后,会返回一个 ModeAndView,这里面会包括视图名称和需要传递给视图的模型数据
- 视图解析器根据名称找到视图,然后把数据模型填充到视图里面再渲染成 Html 内容返回给客户端