面试总结(某上市公司)
面试总结(某上市公司)
Java IO/NIO/AIO 的区别以及线程模型
Java BIO : 同步并阻塞IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO : 同步非阻塞IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
Java AIO(NIO.2) : 异步非阻塞IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
- AsynchronousServerSocketChannel ,对应于bio中的ServerSocket和nio中的ServerSocketChannel,用于server端的网络程序。
AsynchronousSocketChannel,对云关于bio中的Socket和nio中的SocketChannel,用于client端的网络程序。
- CompletionHandler,回调接口,在socket进行accept/connect/read/write等操作时,可以传入一个CompletionHandler的实现,操作执行完毕后,会调用注册的CompletionHandler。
- 除了CompletionHandler这种回调方式,aio中还支持返回Future对象,使用Future来设定回调操作
Full GC 和 Young GC 区别
Minor GC又称为Young GC,只回收新生代的收集器,MinorGC很频繁,和用户线程同步执行,不会停顿用户线程
Full GC又称为Major GC,就是收集整个堆,包括新生代,老年代,永久代(在JDK 1.8及以后,永久代会被移除,换为metaspace)等收集区域
YoungGC 是否会Stop the World,YGC停顿时间是否可配置
是的,也会Stop The World,YGC可以配置停顿时间,只有Parallel Scavenge收集器可以配置吞吐量和停顿时间,g1收集器也可以设置停顿时间
Spring 启动流程
Spring 上下文的启动流程,也就是启动 AbstractApplicationContext refresh()的流程
1 |
|
Spring Bean 的生命周期
Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。
对于普通的Java对象,当new的时候创建对象,当它没有任何引用的时候被垃圾回收机制回收。而由Spring IoC容器托管的对象,它们的生命周期完全由容器控制。Spring中每个Bean的生命周期如下:
实例化Bean
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。
容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。
实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。设置对象属性(依赖注入)
实例化后的对象被封装在BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。
紧接着,Spring根据BeanDefinition中的信息进行依赖注入。
并且通过BeanWrapper提供的设置属性的接口完成依赖注入。注入Aware接口
紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。
BeanPostProcessor
当经过上述几个步骤后,bean对象已经被正确构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过BeanPostProcessor接口实现。
该接口提供了两个函数:- postProcessBeforeInitialzation( Object bean, String beanName )
当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。
这个函数会先于InitialzationBean执行,因此称为前置处理。
所有Aware接口的注入就是在这一步完成的。 - postProcessAfterInitialzation( Object bean, String beanName )
当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。
这个函数会在InitialzationBean完成后执行,因此称为后置处理。
- postProcessBeforeInitialzation( Object bean, String beanName )
InitializingBean与init-method
当BeanPostProcessor的前置处理完成后就会进入本阶段。
InitializingBean接口只有一个函数:- afterPropertiesSet()
这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前bean对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。
若要使用它,我们需要让bean实现该接口,并把要增加的逻辑写在该函数中。然后Spring会在前置处理完成后检测当前bean是否实现了该接口,并执行afterPropertiesSet函数。当然,Spring为了降低对客户代码的侵入性,给bean的配置提供了init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method本质上仍然使用了InitializingBean接口。
DisposableBean和destroy-method
和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑
SpringMvc 和 SpringBoot 比较
SpringMvc是Spring的Mvc Web框架
Spring Boot实现了自动配置,降低了项目搭建的复杂度。 众所周知Spring框架需要进行大量的配置,Spring Boot引入自动配置的概念,让项目设置变得很容易。Spring Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。
SpringBoot 优点
使用Java或Groovy开发基于Spring的应用程序非常容易。
它减少了大量的开发时间并提高了生产力。
它避免了编写大量的样板代码,注释和XML配置。
Spring Boot应用程序与其Spring生态系统(如Spring JDBC,Spring ORM,Spring Data,Spring Security等)集成非常容易。
它遵循“自用默认配置”方法,以减少开发工作量。
它提供嵌入式Web容器,如Tomcat,Jetty等,以开发和测试Web应用程序非常容易。
它提供CLI(命令行界面)工具从命令提示符,非常容易和快速地开发和测试Spring Boot(Java或Groovy)应用程序。
它提供了许多插件来开发和测试Spring启动应用程序非常容易使用构建工具,如Maven和Gradle。
它提供了许多插件,以便与嵌入式和内存数据库工作非常容易
SpringCloud 组件用过哪些
Spring Cloud Eureka
Spring Cloud config
Spring Cloud zuul
Spring Cloud Bus
Spring Cloud Hystrix
……
SpringCloud 和 Dubbo 区别
SpringCould是基于SpringBoot的基础上的一个微服务架构。包括包服务发现(Eureka),断路器(Hystrix),服务网关(Zuul),客户端负载均衡(Ribbon)、服务跟踪(Sleuth)、消息总线(Bus)、消息驱动(Stream)、批量任务(Task)等。
最大的区别:Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式。
严格来说,这两种方式各有优劣。虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适。
Mybatis 一级缓存、二级缓存介绍一下
一级缓存:
- MyBatis一级缓存的生命周期和SqlSession一致。
- MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
- MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
二级缓存:
- MyBatis的二级缓存相对于一级缓存来说,实现了
SqlSession
之间缓存数据的共享,同时粒度更加的细,能够到namespace
级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。 - MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
- 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
gRPC 和 Dubbo 区别
Dubbo基于Java的rpc框架,是基于Netty实现的
gRPC是跨平台的,支持多种语言,可以在不同的平台上进行相互调用
gRPC 优缺点
优点
- protobuf二进制消息,性能好/效率高(空间和时间效率都很不错)
- proto文件生成目标代码,简单易用
- 序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射(XML,JSON都是这种方式)
- 支持向前兼容(新加字段采用默认值)和向后兼容(忽略新加字段),简化升级
- 支持多种语言(可以把proto文件看做IDL文件)
- Netty等一些框架集成
缺点:
- GRPC尚未提供连接池,需要自行实现
- 尚未提供“服务发现”、“负载均衡”机制
- 因为基于HTTP2,绝大部多数HTTP Server、Nginx都尚不支持,即Nginx不能将GRPC请求作为HTTP请求来负载均衡,而是作为普通的TCP请求。(nginx1.9版本已支持)
- Protobuf二进制可读性差(貌似提供了Text_Fromat功能)
默认不具备动态特性(可以通过动态定义生成消息类型或者动态编译支持
Tcp 粘包怎么处理
tcp是流数据,不存在数据包,问题很宽泛。
MQTT 和 Http 区别
http是超文本协议,是应用层协议
mqtt是基于tcp协议的发布和订阅协议
MQTT 的原理
实现MQTT协议需要:客户端和服务器端
MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT传输的消息分为:主题(Topic)和负载(Payload)两部分
Topic:可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(Payload)
Payload:可以理解为消息的内容,是指订阅者具体要使用的内容
MQTT消息类型
CONNECT
:客户端连接到MQTT代理CONNACK
:连接确认PUBLISH
:新发布消息PUBACK
:新发布消息确认,是QoS 1给PUBLISH消息的回复PUBREC
:QoS 2消息流的第一部分,表示消息发布已记录PUBREL
:QoS 2消息流的第二部分,表示消息发布已释放PUBCOMP
:QoS 2消息流的第三部分,表示消息发布完成SUBSCRIBE
:客户端订阅某个主题SUBACK
:对于SUBSCRIBE消息的确认UNSUBSCRIBE
:客户端终止订阅的消息UNSUBACK
:对于UNSUBSCRIBE消息的确认PINGREQ
:心跳PINGRESP
:确认心跳DISCONNECT
:客户端终止连接前优雅地通知MQTT代理
MQTT QoS消息等级
QoS服务质量指的是交通优先级和资源预留控制机制,而不是接收的服务质量。 服务质量是为不同应用程序,用户或数据流提供的不同优先级的能力,或者也可以说是为数据流保证一定的性能水平的能力。
以下是每一个服务质量级别的具体描述
- 最多一次传送 (只负责传送,发送过后就不管数据的传送情况)
- 至少一次传送 (确认数据交付)
- 正好一次传送 (保证数据交付成功)
MQTT 如何保持长连接的
和TCP一样也是通过心跳机制来保持长连接的
- TCP长连接保持:KeepAlive。TCP协议的实现里有一个KeepAlive机制,自动检测能否和对方连通并保持连接。
- TCP识别不同的请求:每个连接建立时,都会保存一个唯一的套接字,有了这个套接字,你就知道对方的IP地址、端口号等信息。这样,通过这个套接字,就可以向指定方发送信息了
TCP 三次握手和四次握手
三次握手示意图:
- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
四次握手示意图:
断开一个 TCP 连接则需要“四次握手”:
- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
- 服务器-收到这个 FIN,它发回一个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
- 服务器-关闭与客户端的连接,发送一个FIN给客户端
- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1
Redis 主从复制、哨兵介绍一下
Redis主从复制:
从服务器在连接一个主服务器的时候,主服务器会创建一个快照文件并将其发送至从服务器,但这只是主从复制执行过程的其中一步。
从服务器连接主服务器时的步骤:
步骤 | 主服务器操作 | 从服务器操作 |
---|---|---|
1 | 等待命令进入 | 连接(或者重连接)主服务器,发送SYNC命令 |
2 | 开始执行BGSAVE,并使用缓冲区记录BGSAVE之后执行的所有写命令 | 根据配置选项来决定是继续使用现有的数据(如果有的话)来处理客户端的命令请求,还是向发送请求的客户端返回错误 |
3 | BGSAVE执行完毕,向从服务器发送快照文件,并在发送期间继续使用缓冲区记录被执行的写命令 | 丢弃所有旧数据(如果有的话),开始载入主服务器发来的快照文件 |
4 | 快照文件发送完毕,开始向从服务器发送存储在缓冲区里面的写命令 | 完成对快照文件的解释操作,想往常一样开始接受命令请求 |
5 | 缓冲区存储的写命令发送完毕;从现在开始,每执行一个写命令,就向从服务器发送相同的写命令 | 执行主服务器发来的所有存储在缓冲区里面的写命令;并从现在开始,接收并执行主服务器传来的每个写命令 |
哨兵就是个监控服务,监控主节点的监控状态,提供:监控、通知、重新选举主服务和自动故障转移
Redis 和内存缓存的区别
- 读写速度,不考虑并发问题,本地缓存自然是最快的
- 场景使用,同一数据,从数据库取出来,放到redis只要一次,而放到本地缓存,则需要n个集群次
- 本地缓存无法用于重复点击,重复点击会分发请求到多台服务器,而用本地缓存只能防止本机重复点击,redis则可以防止,但是时间间隔也需要在redis的读写差之外。
- redis内存可能n多扩充,而本地扩大堆内存代价是很大的。
- 本地缓存需要自己实现过期功能,实现不好可能导致极其严重的后果,而redis经过大量的流量验证,许多漏洞无需考试,安全。
- 本地缓存无法提供丰富的数据结构,redis可以。
- redis可以写磁盘,持久化,本地缓存不可以或者说很麻烦要考虑的东西太多。
- 使用本地缓存极有可能导致严重的线程安全问题,并发考虑严重。
消息队列类型
- ActiveMQ
- RabbitMQ
- RocketMQ
- Kafka
Kafka在于 分布式架构,RabbitMQ
基于 AMQP
协议 来实现,RocketMQ
的思路来源于 Kafka
,改成了 主从结构,在 事务性 和 可靠性 方面做了优化。广泛来说,电商、金融 等对 事务一致性 要求很高的,可以考虑 RabbitMQ
和 RocketMQ
,对 性能要求高 的可考Kafka.
分布式锁如何实现
基于set nx expire 来实现分布式锁。
基于Redis的Redisson实现,下图是Redisson的原理:
- 加锁机制:线程去获取锁,获取成功就执行lua脚本,保存数据到redis数据库;线程去获取锁,获取失败就一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。
- watch dog自动延期机制:在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生;但持有锁的线程的业务还没处理完,想继续处理,就需要看门狗来进行锁续期了,它可以不断延长锁的持有时间
- 通过lua脚本执行,保证原子操作
- 可重入加锁机制
Aws lambda 介绍一下
AWS Lambda 是一项计算服务,使用时无需预配置或管理服务器即可运行代码。AWS Lambda 只在需要时执行代码并自动缩放。借助 AWS Lambda,几乎可以为任何类型的应用程序或后端服务运行代码,而且无需执行任何管理。现在 AWS Lambda 支持 Node.js、Java、C# 和 Python