一、JVM面试题
1. 说说你对JVM内存模型的了解,每个区的作用是什么?
栈区:
- 栈分为java虚拟机栈和本地方法栈
- 重点是Java虚拟机栈,它是线程私有的,生命周期与线程相同。
- 每个方法执行都会创建一个栈帧,用于存放局部变量表,操作栈,动态链接,方法出口等。每个方法从被调用,直到被执行完。对应着一个栈帧在虚拟机中从入栈到出栈的过程。
- 通常说的栈就是指局部变量表部分,存放编译期间可知的8种基本数据类型,及对象引用和指令地址。局部变量表是在编译期间完成分配,当进入一个方法时,这个栈中的局部变量分配内存大小是确定的。
- 会有两种异常StackOverFlowError和 OutOfMemoneyError。当线程请求栈深度大于虚拟机所允许的深度就会抛出StackOverFlowError错误;虚拟机栈动态扩展,当扩展无法申请到足够的内存空间时候,抛出OutOfMemoneyError。
- 本地方法栈为虚拟机使用到本地方法服务(native)
堆区:
- 堆是被所有线程共享的一块区域,在虚拟机启动时创建,唯一目的存放对象实例。
- 堆被划分成两个不同的区域:新生代(Young)、老年代(Old)。
- 新生代又被划分为三个区域:Eden和两个幸存区(From survivor 和 To survivor)。
- 新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)后仍然存活的对象。
- 会有异常OutOfMemoneyError
方法区:
- 被所有线程共享的区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。有时候也称它为永久代(permanment generation)
- 垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载。
- 常量池用于存放编译期生成的各种字节码和符号引用,常量池具有一定的动态性,里面可以存放编译期生成的常量;运行期间的常量也可以添加进入常量池中,比如string的intern()方法。
程序计数器:
- 当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。
- Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。
- 唯一一块Java虚拟机没有规定任何OutofMemoryError的区块。
2. JVM什么情况下会发生栈内存溢出
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
3. 类的生命周期
类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载
- 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
- 连接,连接又包含三块内容:验证、准备、初始化。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用
- 初始化,为类的静态变量赋予正确的初始值
- 使用,new出对象程序中使用
- 卸载,执行垃圾回收
4. JVM对象分配规则
- 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
- 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
- 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
- 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
- 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
5. JVM如何判断对象是否存活
判断对象是否存活一般有两种方式:
- 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
- 可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。
6. 介绍一下GC算法有哪些
GC最基础的算法有三种:标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。
- 标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
- 复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
- 标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
- 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
7. JVM有哪些垃圾回收器
- Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。
- ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
- Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。
- Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
- CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
- G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征
8. JVM常用调优命令
Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo
- jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
- jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
- jmap,JVM Memory Map命令用于生成heap dump文件
- jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
- jstack,用于生成java虚拟机当前时刻的线程快照。
- jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。
9. JVM常用性能调优参数
- Xss:规定了每个线程虚拟机栈的大小
- Xms:堆的初始值
- Xmx:堆能达到的最大值
10. Java内存模型中堆和栈的区别
- 管理方式
- 栈自动释放,堆需要GC
- 空间大小
- 栈比堆小
- 碎片相关
- 栈产生的碎片远小于堆
- 分配方式
- 栈支持静态和动态分配,而堆仅支持动态分配
- 效率
- 栈的效率比堆高
11. JVM中有几种类加载器
类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例;有以下几张类加载去:
- 启动类加载器(Bootstrap ClassLoader):Java应用启动时,加载$JAVA_HOME/lib或 -Xbootclasspath指定的路径中的类文件;
- 扩展类加载器(Extension ClassLoaser):由sun.misc.LauncherJAVA_HOME/lib/ext或java.ext.dirs指定路径的类库;
- 应用程序类加载器(Application ClassLoader):又称系统类加载器,由sun.misc.Launcher$AppClassLoader实现,是ClassLoader.getSystemClassLoader()的返回值。
- 自定义类加载器需要继承抽象类ClassLoader,实现findClass方法,该方法会在loadClass调用的时候被调用,findClass默认会抛出异常。
- findClass方法表示根据类名查找类对象
- loadClass方法表示根据类名进行双亲委托模型进行类加载并返回类对象
- loadClass方法表示根据类名进行双亲委托模型进行类加载并返回类对象
12. 什么是双亲委派模型
- 双亲委托模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器(父子关系由组合(不是继承)来实现)去完成,每一个层次的类加载器都是如此, 因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要加载的类)时, 子加载器才会尝试自己去加载。
- 使用双亲委托机制的好处是:
- 避免同一个类被多次加载;
- 每个加载器只能加载自己范围内的类;
13. 类加载过程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中准备、验证、解析3个部分统称为连接(Linking)
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。以下陈述的内容都已HotSpot为基准。
14. 栈溢出的原因
- 是否有递归调用
- 是否有大量循环或死循环
- 全局变量是否过多
- 数组、List、map数据是否过大
15. 方法区溢出的原因
- 动态生成大量Class
- 大量JSP或动态产生JSP文件(JSP第一次运行时需要编译为Java类)
二、消息中间件面试题
1. 说说RabbitMQ的结构
- Brocker:消息队列服务器实体。
- Exchange:消息交换机,用于接收、分配消息。指定消息按什么规则,路由到哪个队列。
- Queue:消息队列,用于存储生产者的消息。每个消息都会被投入到一个或者多个队列里。
- Binding Key:绑定关键字,用于把交换器的消息绑定到队列中,它的作用是把exchange和queue按照路由规则binding起来。
- Routing Key:路由关键字,用于把生产者的数据分配到交换器上。exchange根据这个关键字进行消息投递。
- Vhost:虚拟主机,一个broker里可以开设多个vhost,用作不用用户的权限分离。
- Producer:消息生产者,就是投递消息的程序。
- Consumer:消息消费者,就是接受消息的程序。
- Channel:信道,消息推送使用的通道。可建立多个channel,每个channel代表一个会话任务。
2. RabbitMQ交换器种类
- Direct exchange(直连交换机):直连型交换机(direct exchange)是根据消息携带的路由键(routing key)将消息投递给对应队列的,步骤如下:
- 将一个队列绑定到某个交换机上,同时赋予该绑定一个路由键(routing key)
- 当一个携带着路由值为R的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为R的队列。
- Fanout exchange(扇型交换机):扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列。不同于直连交换机,路由键在此类型上不启任务作用。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的发送给这所有的N个队列
- Topic exchange(主题交换机):主题交换机(topic exchanges)中,队列通过路由键绑定到交换机上,然后,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列。
- 扇型交换机和主题交换机异同:
- 对于扇型交换机路由键是没有意义的,只要有消息,它都发送到它绑定的所有队列上
- 对于主题交换机,路由规则由路由键决定,只有满足路由键的规则,消息才可以路由到对应的队列上
- Headers exchange(头交换机):类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。 此交换机有个重要参数:”x-match”。
- 当”x-match”为“any”时,消息头的任意一个值被匹配就可以满足条件
- 当”x-match”设置为“all”的时候,就需要消息头的所有值都匹配成功
3. RabbitMQ队列与消费者的关系
- 一个队列可以绑定多个消费者;
- 队列分发消息将以轮询的方式分发,每条消息只会分发给一个订阅的消费者;
- 消费者收到消息之后默认是自动确认,可以设置手动确认,保证消费者消费了消息。
4. 如何保证消息的顺序性
- RabbitMQ
- 拆分多个queue,每个queue一个consumer,就是多一些queue而已
- 一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理
- kafka
- 一个topic,一个partition,一个consumer,内部单线程消费,写N个内存queue,然后N个线程分别消费一个内存queue即可
三、数据库面试题
1. InnoDB中的事务隔离级别
SQL标准中的事务四种隔离级别
- 未提交读(Read Uncommitted):可能出现脏读(可能读取到其他会话中未提交事务修改的数据)、不可重复读、幻读
- 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
- 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读
- 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
2. 解释下脏读、不可重复读、幻读
- 脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
- 幻读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
3. MyISAM和InnoDB两者之间的区别
InnoDB是一个事务型的存储引擎,支持回滚,设计目标是处理大数量数据时提供高性能的服务,它在运行时会在内存中建立缓冲池,用于缓冲数据和索引。
优点:
- 支持事务处理、ACID事务特性;
- 实现了SQL标准的四种隔离级别;
- 支持行级锁和外键约束;
- 可以利用事务日志进行数据恢复。
- 锁级别为行锁,行锁优点是适用于高并发的频繁表修改,高并发是性能优于 MyISAM。缺点是系统消耗较大。
- 索引不仅缓存自身,也缓存数据,相比 MyISAM 需要更大的内存。
缺点:
- 因为它没有保存表的行数,当使用COUNT统计时会扫描全表。
MyISAM 是 MySQL 5.5.5 之前的默认引擎,它的设计目标是快速读取。
优点:
- 高性能读取;
- 因为它保存了表的行数,当使用COUNT统计时不会扫描全表;
缺点:
- 锁级别为表锁,表锁优点是开销小,加锁快;缺点是锁粒度大,发生锁冲动概率较高,容纳并发能力低,这个引擎适合查询为主的业务。
- 此引擎不支持事务,也不支持外键。
- INSERT和UPDATE操作需要锁定整个表;
- 它存储表的行数,于是COUNT时只需要直接读取已经保存好的值而不需要进行全表扫描。
适用场景:
- MyISAM适合:
- 做很多count 的计算;
- 插入不频繁,查询非常频繁;
- 没有事务。
- InnoDB适合:
- 可靠性要求比较高,或者要求事务;
- 表更新和查询都相当的频繁,并且表锁定的机会比较大的情况
4. 说说事务的四种特性(ACID)
- 原子性(Atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可以只执行其中的一部分操作。
- 一致性(Consistency):数据库总是从一个一致性的状态转到另一个一致性的状态。 拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱加起来应该还得是5000,这就是事务一致性。
- 隔离性(Isolation):通常来说,一个事务所做的修改在最终提交之前,对其他事务是不可见的。
- 持久性(Durability):一旦事务提交,则其所做的修改就会永久的保存到数据库中。不会因为系统故障等情况而丢失所做的修改。
四、NoSQL面试题
1. Memcache和Redis的区别
Memcache
- 支持简单数据类型
- 不支持数据持久化存储
- 不支持主从
- 不支持分片
Redis
- 数据类型丰富
- 支持数据磁盘持久化存储
- 支持主从
- 支持分片
2. 为什么Redis能这么快
- 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高
- 数据结构简单,对数据操作也简单
- 使用多路I/O复用模型,非阻塞IO
- 采用单线程,单线程也能处理高并发请求,想多核也可以启动多实例
3. 说说你用过的Redis的数据类型
- String:最基本的数据类型,二进制安全
- Hash:String元素组成的字典,适合用于存储对象
- List:列表,按照String元素插入顺序排序
- Set:String元素组成的无序集合,通过Hash表实现,不允许重复
- Sorted Set:通过分数来为集合中的成员进行从小到大的排序
- 用于计数的HyperLogLog(非重点,前面五个尽量说出来)
4. 如何通过Redis实现分布式锁
SET key value [EX seconds] [PX milliseconds] [NX|XX]
- EX seconds:设置键的过期时间为second秒
- PX milliseconds:设置键的过期时间为millisecond毫秒
- NX:只在键不存在时,才对键进行设置操作
- XX:只在键已经存在时,才对键进行设置操作
- SET操作成功完成时,返回OK,否则返回nil
5. Redis中大量的key同时过期的注意事项
- 现象:集中过期,由于清楚大量的key很耗时,会出现短暂的卡顿现象
- 解决方案:在设置key的过期时间的时候,给每个key加上随机值
6. 如何使用Redis做异步队列
使用List作为队列,RPUSH生产消息,LPOP消费消息
缺点:没有等待队列里面有值就直接消费
解决方案:
- 可以通过在应用层引入Sleep机制去调用LPOP重试
- BLPOP key [key ...] timeout:阻塞直到队列有消息或者超时,缺点是只能供一个消费者消费
- pub/sub:主题订阅者模式,缺点:消息的发布是无状态的,无法保证可达
五、分布式面试题
1. 说说SpringCloud的工作原理
Spring Cloud是一个全家桶式的技术栈,包含了很多组件,其中比较核心的有Eureka、Ribbon、Feign、Hystrix、Zuul这几个组件。
- Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。每个服务中都有一个Eureka Client组件,这个组件专门负责将这个服务的信息注册到Eureka Server中。说白了,就是告诉Eureka Server,自己在哪台机器上,监听着哪个端口。而Eureka Server是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口号。服务调用者调用对应服务时,它的Eureka Client组件会找Eureka Server查找对应的服务信息进行调用,然后就可以把这些相关信息从Eureka Server的注册表中拉取到自己本地缓存起来,方便下次调用。
- Eureka Client:负责将这个服务的信息注册到Eureka Server中
- Eureka Server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号
- Feign基于动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求。Feign的一个关键机制就是使用了动态代理。
- 首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理
- 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心
- Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址
- 最后针对这个地址,发起请求、解析响应
- Ribbon的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上。Ribbon的负载均衡默认使用的最经典的Round Robin轮询算法。而且,Ribbon是和Feign以及Eureka紧密协作,完成工作的。
- 首先Ribbon从 Eureka Client里获取到对应的服务注册表,即知道了所有的服务都部署在了哪些机器上,在监听哪些端口号
- 然后Ribbon就可以使用默认的Round Robin轮询算法,从中选择一台机器
- Feign就会针对这台机器,构造并发起请求
- Hystrix是隔离、熔断以及降级的一个框架,发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
- Zuul就是微服务网关,这个组件是负责网络路由的。一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。而且有了网关之后,可以做统一的降级、限流、认证授权、安全等功能。
2. Eureka的心跳时间
- Eureka的客户端默认每隔30s会向eureka服务端更新实例,注册中心也会定时进行检查,发现某个实例默认90s内没有再收到心跳,会注销此实例。但是这些时间间隔是可配置的。
- 不过注册中心还有一个保护模式,在这个保护模式下,他会认为是网络问题,不会注销任何过期的实例
3. Eureka的缺点
- 不满足CAP的一致性
- 停止维护了,2.0之后不开源
4. 说说分布式事务有哪些解决方案
- 基于XA协议的两阶段提交方案
- 第一阶段是表决阶段,所有参与者都将本事务能否成功的信息反馈发给协调者;第二阶段是执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上提交或者回滚。
- 缺点:两阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。
- TCC方案
- TCC方案其实是两阶段提交的一种改进。其将整个业务逻辑的每个分支显式的分成了Try、Confirm、Cancel三个操作。Try部分完成业务的准备工作,confirm部分完成业务的提交,cancel部分完成事务的回滚。
- 事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。
- 缺点:1、对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。 2、实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等。
- 基于可靠消息的最终一致性方案
- 消息一致性方案是通过消息中间件保证上、下游应用数据操作的一致性。基本思路是将本地操作和发送消息放在一个事务中,保证本地操作和消息发送要么两者都成功或者都失败。下游应用向消息系统订阅该消息,收到消息后执行相应操作。
- 消息方案从本质上讲是将分布式事务转换为两个本地事务,然后依靠下游业务的重试机制达到最终一致性。基于消息的最终一致性方案对应用侵入性也很高,应用需要进行大量业务改造,成本较高。
- GTS
- GTS是一款分布式事务中间件,由阿里巴巴中间件部门研发,可以为微服务架构中的分布式事务提供一站式解决方案。
- 优点:性能超强、应用侵入性极低、完整解决方案、容错能力强
5. rpc和http的区别
- RPC主要是基于TCP/IP协议,而HTTP服务主要是基于HTTP协议
- RPC要比http更快,虽然底层都是socket,但是http协议的信息往往比较臃肿
- RPC实现较为复杂,http相对比较简单
- 灵活性来看,http更胜一筹,因为它不关心实现细节,跨平台、跨语言。
- 如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的。
- 如果需要更加灵活,跨语言、跨平台,显然http更合适
六、常用框架面试题
1. 说说Spring事务什么情况下才会回滚
当所拦截的方法有指定异常抛出,事务才会自动进行回滚。默认情况下是捕获到方法的RuntimeException异常,也就是说抛出只要属于运行时的异常(即RuntimeException及其子类)都能回滚;但当抛出一个不属于运行时异常时,事务是不会回滚的。如果是其他异常想要实现回滚,可以进行配置。
2. 说说Spring事务的传播属性
事务的传播性一般在事务嵌套时候使用,比如在事务A里面调用了另外一个使用事务的方法,那么这俩个事务是各自作为独立的事务执行提交,还是内层的事务合并到外层的事务一块提交那,这就是事务传播性要确定的问题。spring支持7种事务传播行为:
- PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
备注:常用的两个事务传播属性是1和4,即PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW
3. 说说Spring事务的隔离性
事务的隔离性是指多个事务并发执行的时候相互之间不受到彼此的干扰。
事务的隔离级别也分为四种,由低到高依次分别为:read uncommited(读未提交)、read commited(读提交)、read repeatable(读重复)、serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
- read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
- read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
- repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
- serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻读。
- DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
4. 说说Spring事务的特性
事务特性分为四个:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持续性(Durability)简称ACID。
- 原子性(Atomicity):事务是数据库逻辑工作单元,事务中包含的操作要么都执行成功,要么都执行失败。
- 一致性(Consistency):事务执行的结果必须是使数据库数据从一个一致性状态变到另外一种一致性状态。当事务执行成功后就说数据库处于一致性状态。如果在执行过程中发生错误,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这是数据库就处于不一致状态。
- 隔离性(Isolation):一个事务的执行过程中不能影响到其他事务的执行,即一个事务内部的操作及使用的数据对其他事务是隔离的,并发执行各个事务之间无不干扰。
- 持续性(Durability):即一个事务执一旦提交,它对数据库数据的改变是永久性的。之后的其它操作不应该对其执行结果有任何影响。
5. 说说你对SpringIOC容器的理解
SpringIOC负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
6. 什么是Spring的依赖注入
依赖注入,是IOC的一个方面。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。
7. IOC(依赖注入)方式
- 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
- Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
- 用构造器参数实现强制依赖,setter方法实现可选依赖。
8. Springboot启动过程
- 构造SpringApplication的实例
- 调用SpringApplication.run()方法
- 构造SpringApplicationRunListeners 实例
- 发布ApplicationStartedEvent事件
- SpringApplicationRunListeners 实例准备环境信息
- 创建ApplicationContext对象
- ApplicationContext实例准备环境信息
- 刷新的上下文
9. spring的bean的生命周期
- Spring 容器根据配置中的 bean 定义中实例化 bean。
- Spring 使用依赖注入填充所有属性,如 bean 中所定义的配置。
- 如果 bean 实现 BeanNameAware 接口,则工厂通过传递 bean 的 ID 来调用 setBeanName()。
- 如果 bean 实现 BeanFactoryAware 接口,工厂通过传递自身的实例来调用 setBeanFactory()。
- 如果存在与 bean 关联的任何 BeanPostProcessors,则调用 preProcessBeforeInitialization() 方法。
- 如果为 bean 指定了 init 方法( 的 init-method 属性),那么将调用它。
- 最后,如果存在与 bean 关联的任何 BeanPostProcessors,则将调用 postProcessAfterInitialization() 方法。
- 如果 bean 实现 DisposableBean 接口,当 spring 容器关闭时,会调用 destory()。
- 如果为 bean 指定了 destroy 方法( 的 destroy-method 属性),那么将调用它。
我们这里描述的是应用Spring上下文Bean的生命周期,如果应用Spring的工厂也就是BeanFactory的话去掉第5步就Ok了。
11. spring事件的实现原理,写出常用的几个事件
写在最后
本次面试题分享到此为止,限于篇幅,没法在这里给大家分享更多的面试题; 之前很多粉丝一直私信我让我整理一些面试题,近些天终于整理好了,笔者整理了一份包含Kafka、Mysql、Tomcat、Docker、Spring、MyBatis、Nginx、Netty、Dubbo、Redis、Netty、Spring cloud、分布式、高并发、性能调优、微服务等架构技术的面试题和部分视频学习资料;
需要的朋友点击下方传送门, 即可免费领取面试资料和视频学习资料
传送门
以下是部分面试题截图