面试总结(某上市公司)

面试总结(某上市公司)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备此上下文以进行刷新。
prepareRefresh();

// 告诉子类刷新内部bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 准备在这种情况下使用的bean工厂
prepareBeanFactory(beanFactory);

try {
// 允许在上下文子类中对bean工厂进行后处理。
postProcessBeanFactory(beanFactory);

// 调用在上下文中注册为bean的工厂处理器。
invokeBeanFactoryPostProcessors(beanFactory);

// 注册拦截Bean创建的Bean处理器。
registerBeanPostProcessors(beanFactory);

// 为此上下文初始化消息源。
initMessageSource();

// 为此上下文初始化事件多播器。
initApplicationEventMulticaster();

// 在特定上下文子类中初始化其他特殊bean。
onRefresh();

// 检查侦听器bean并注册它们。
registerListeners();

// 实例化所有剩余的(非延迟初始化)单例bean。
finishBeanFactoryInitialization(beanFactory);

// 最后一步:发布相应的事件。
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// 销毁已创建的单例以避免资源悬空。
destroyBeans();

// 重置“活动”标志。
cancelRefresh(ex);

// 将异常传播给监听者。
throw ex;
}

finally {
// 在Spring的核心中重置常见的自省缓存,因为我们可能再也不需要单例bean的元数据了。
resetCommonCaches();
}
}
}

Spring Bean 的生命周期

Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

对于普通的Java对象,当new的时候创建对象,当它没有任何引用的时候被垃圾回收机制回收。而由Spring IoC容器托管的对象,它们的生命周期完全由容器控制。Spring中每个Bean的生命周期如下:

WX20200825-155349@2x

  1. 实例化Bean

    对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
    对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。
    容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。
    实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。

  2. 设置对象属性(依赖注入)

    实例化后的对象被封装在BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。
    紧接着,Spring根据BeanDefinition中的信息进行依赖注入。
    并且通过BeanWrapper提供的设置属性的接口完成依赖注入。

  3. 注入Aware接口

    紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。

  4. BeanPostProcessor

    当经过上述几个步骤后,bean对象已经被正确构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过BeanPostProcessor接口实现。
    该接口提供了两个函数:

    • postProcessBeforeInitialzation( Object bean, String beanName )
      当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。
      这个函数会先于InitialzationBean执行,因此称为前置处理。
      所有Aware接口的注入就是在这一步完成的。
    • postProcessAfterInitialzation( Object bean, String beanName )
      当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。
      这个函数会在InitialzationBean完成后执行,因此称为后置处理。
  5. InitializingBean与init-method

    当BeanPostProcessor的前置处理完成后就会进入本阶段。
    InitializingBean接口只有一个函数:

    • afterPropertiesSet()

    这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前bean对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。
    若要使用它,我们需要让bean实现该接口,并把要增加的逻辑写在该函数中。然后Spring会在前置处理完成后检测当前bean是否实现了该接口,并执行afterPropertiesSet函数。

    当然,Spring为了降低对客户代码的侵入性,给bean的配置提供了init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method本质上仍然使用了InitializingBean接口。

  6. 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 一级缓存、二级缓存介绍一下

一级缓存:

  1. MyBatis一级缓存的生命周期和SqlSession一致。
  2. MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
  3. MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

二级缓存:

  1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  2. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  3. 在分布式环境下,由于默认的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服务质量指的是交通优先级和资源预留控制机制,而不是接收的服务质量。 服务质量是为不同应用程序,用户或数据流提供的不同优先级的能力,或者也可以说是为数据流保证一定的性能水平的能力。

以下是每一个服务质量级别的具体描述

  1. 最多一次传送 (只负责传送,发送过后就不管数据的传送情况)
  2. 至少一次传送 (确认数据交付)
  3. 正好一次传送 (保证数据交付成功)

MQTT 如何保持长连接的

和TCP一样也是通过心跳机制来保持长连接的

  • TCP长连接保持:KeepAlive。TCP协议的实现里有一个KeepAlive机制,自动检测能否和对方连通并保持连接。
  • TCP识别不同的请求:每个连接建立时,都会保存一个唯一的套接字,有了这个套接字,你就知道对方的IP地址、端口号等信息。这样,通过这个套接字,就可以向指定方发送信息了

TCP 三次握手和四次握手

三次握手示意图:

WX20200824-133727@2x

  • 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
  • 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
  • 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端

四次握手示意图:

WX20200824-133946@2x

断开一个 TCP 连接则需要“四次握手”:

  • 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
  • 服务器-收到这个 FIN,它发回一个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
  • 服务器-关闭与客户端的连接,发送一个FIN给客户端
  • 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1

Redis 主从复制、哨兵介绍一下

Redis主从复制:

从服务器在连接一个主服务器的时候,主服务器会创建一个快照文件并将其发送至从服务器,但这只是主从复制执行过程的其中一步。

从服务器连接主服务器时的步骤:

步骤 主服务器操作 从服务器操作
1 等待命令进入 连接(或者重连接)主服务器,发送SYNC命令
2 开始执行BGSAVE,并使用缓冲区记录BGSAVE之后执行的所有写命令 根据配置选项来决定是继续使用现有的数据(如果有的话)来处理客户端的命令请求,还是向发送请求的客户端返回错误
3 BGSAVE执行完毕,向从服务器发送快照文件,并在发送期间继续使用缓冲区记录被执行的写命令 丢弃所有旧数据(如果有的话),开始载入主服务器发来的快照文件
4 快照文件发送完毕,开始向从服务器发送存储在缓冲区里面的写命令 完成对快照文件的解释操作,想往常一样开始接受命令请求
5 缓冲区存储的写命令发送完毕;从现在开始,每执行一个写命令,就向从服务器发送相同的写命令 执行主服务器发来的所有存储在缓冲区里面的写命令;并从现在开始,接收并执行主服务器传来的每个写命令

哨兵就是个监控服务,监控主节点的监控状态,提供:监控、通知、重新选举主服务和自动故障转移

Redis 和内存缓存的区别

  1. 读写速度,不考虑并发问题,本地缓存自然是最快的
  2. 场景使用,同一数据,从数据库取出来,放到redis只要一次,而放到本地缓存,则需要n个集群次
  3. 本地缓存无法用于重复点击,重复点击会分发请求到多台服务器,而用本地缓存只能防止本机重复点击,redis则可以防止,但是时间间隔也需要在redis的读写差之外。
  4. redis内存可能n多扩充,而本地扩大堆内存代价是很大的。
  5. 本地缓存需要自己实现过期功能,实现不好可能导致极其严重的后果,而redis经过大量的流量验证,许多漏洞无需考试,安全。
  6. 本地缓存无法提供丰富的数据结构,redis可以。
  7. redis可以写磁盘,持久化,本地缓存不可以或者说很麻烦要考虑的东西太多。
  8. 使用本地缓存极有可能导致严重的线程安全问题,并发考虑严重。

消息队列类型

  • ActiveMQ
  • RabbitMQ
  • RocketMQ
  • Kafka

Kafka在于 分布式架构,RabbitMQ 基于 AMQP 协议 来实现,RocketMQ 的思路来源于 Kafka,改成了 主从结构,在 事务性可靠性 方面做了优化。广泛来说,电商金融 等对 事务一致性 要求很高的,可以考虑 RabbitMQRocketMQ,对 性能要求高 的可考Kafka.

分布式锁如何实现

  • 基于set nx expire 来实现分布式锁。

  • 基于Redis的Redisson实现,下图是Redisson的原理:

WX20200825-153328@2x

  1. 加锁机制:线程去获取锁,获取成功就执行lua脚本,保存数据到redis数据库;线程去获取锁,获取失败就一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。
  2. watch dog自动延期机制:在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生;但持有锁的线程的业务还没处理完,想继续处理,就需要看门狗来进行锁续期了,它可以不断延长锁的持有时间
  3. 通过lua脚本执行,保证原子操作
  4. 可重入加锁机制

Aws lambda 介绍一下

AWS Lambda 是一项计算服务,使用时无需预配置或管理服务器即可运行代码。AWS Lambda 只在需要时执行代码并自动缩放。借助 AWS Lambda,几乎可以为任何类型的应用程序或后端服务运行代码,而且无需执行任何管理。现在 AWS Lambda 支持 Node.js、Java、C# 和 Python