Spring Boot
为什么要用Spring Boot?与传统Spring框架的区别?
答:传统的Spring框架开发有几个痛点:
-
复杂的配置,大量的配置写在XML中,开发时需要了解太多的概念
2. 混乱的依赖管理,所有的包都需要自己来导入,有可能每个包依赖的子包的版本不一样,调用方法时可能出现异常 3. 项目部署时需要考虑tomcat的版本
在使用SpringBoot后这些问题都得到了解决,SpringBoot可以快速上手,不需要了解太多配置概念,也不需要大量XML配置,通过使用SpringBoot的版本管理可以避免包依赖的混乱,SpringBoot官方已经对包的相互依赖做了大量的测试,保证不会出现异常,并且SpringBoot内置了tomcat,部署时可以直接通过Java命令运行应用。
讲一下Spring Boot的自动配置原理?
**问题1:**那你说说SpringBoot的自动配置是如何实现的?
面试话术:
一般我们的SpringBoot项目启动类都会添加@SpringBootApplication
注解,而这个注解的其中一个二级注解是@EnableAutoConfiguration
注解。而@EnableAutoConfiguration
注解通过@Import
注解,以ImportSelector
接口的方法来导入classpath下的META-INF/spring.factories
文件,这些文件中会指定需要加载的一些类名称。
这些类一般都加了@Configuration
注解,并且完成了对某框架(例如Redis、SpringMVC)的默认配置,当这些类符合条件时,就会被实例化,其中的配置生效,那么自动配置自然生效了。
问题2:满足怎样的条件配置才会生效?
面试话术:
一般提供默认配置的类都会添加@ConditionalOnXxx
这样的注解,例如:@ConditionalOnClass
,@ConditionalOnProperties
等。@ConditionalOnClass
表示只有classpath中存在某些指定的类时,条件满足,此时该配置类才会生效。例如Redis的默认配置其实早就有了,但是只有你引入redis的starter依赖,才满足了条件,触发自动配置。
问题3:那如果我需要覆盖这些默认配置呢?
有两种方式可以覆盖默认配置:
- SpringBoot提供默认配置时,会在提供的Bean上加注解@ConditionalOnMissingBean,意思是如果这个Bean不存在时条件满足,那么我们只要配置了相同的Bean,那么SpringBoot提供的默认配置就会失效
- SpringBoot提供默认配置时,一些关键属性会通过读取application.yml或者application.properties文件来获取,因此我们可以通过覆盖任意一个文件中的属性来覆盖默认配置。
SpringBoot项目的启动流程
SpringBoot项目启动第一步就是创建SpringApplication的实例,并且调用SpringApplication.run()这个方法。
创建SpringApplication实例主要完成三件事情:
- 记录当前启动类字节码
- 判断当前项目类型,普通Servlet、响应式WebFlux、NONE
- 加载/META-INF/spring.factories文件,初始化ApplicationContextInitializer和ApplicationListener实例
而后的run()方法则会创建spring容器,流程如下:
- 准备监听器,监听Spring启动的各个过程
- 创建并配置环境参数Environment
- 创建ApplicationContext
prepareContext()
:初始化ApplicationContext,准备运行环境refreshContext(context)
:准备Bean工厂,调用一个BeanDefinition和BeanFactory的后处理器,初始化各种Bean,初始化tomcatafterRefresh()
:拓展功能,目前为空- 发布容器初始化完毕的事件
怎么开发一个启动器项目?
答:首先添加一个starter项目,用户管理具体功能实现包的版本,再添加一个自动配置项目,实现自己要封装的功能,添加一个自动配置类,在配置类中将相应的功能注册成Bean,在Resources中添加META-INF/spring.factories,添加自动配置类所在的全路径
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
Spring Cloud
SpringCloud和Dubbo的区别
聊聊看SpringCloud和Dubbo有什么区别?
面试话术:
两者都是现在主流的微服务框架,但却存在不少差异:
- 初始定位不同:SpringCloud定位为微服务架构下的一站式解决方案;Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用和治理
- 生态环境不同:SpringCloud依托于Spring平台,具备更加完善的生态体系;而Dubbo一开始只是做RPC远程调用,生态相对匮乏,现在逐渐丰富起来。
- 调用方式:SpringCloud是采用Http协议做远程调用,接口一般是Rest风格,比较灵活;Dubbo是采用Dubbo协议,接口一般是Java的Service接口,格式固定。但调用时采用Netty的NIO方式,性能较好。
- 组件差异比较多,例如SpringCloud注册中心一般用Eureka,而Dubbo用的是Zookeeper
SpringCloud生态丰富,功能完善,更像是品牌机,Dubbo则相对灵活,可定制性强,更像是组装机。
相关资料:
SpringCloud:Spring公司开源的微服务框架,SpirngCloud 定位为微服务架构下的一站式解决方案。
Dubbo:阿里巴巴开源的RPC框架,Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断
SpringCloudAlibaba
两者的生态对比:
功能 | Dubbo | SpringCloud |
---|---|---|
服务注册中心 | Zookeeper | Eureka(主流)、Consul、zookeeper |
服务调用方式 | RPC基于Dubbo协议 | REST API 基于Http协议 |
服务监控 | Dubbo-Monitor | Spring Boot Admin |
熔断器 | 不完善 | Spring Cloud Netflix Hystrix |
服务网关 | 无 | Spring Cloud Netflix Zuul、Gateway |
分布式配置 | 无 | Spring Cloud Config |
服务跟踪 | 无 | Spring Cloud Sleuth+Zipkin(一般) |
数据流 | 无 | Spring Cloud Stream |
批量任务 | 无 | Spring Cloud Task |
信息总线 | 无 | Spring Cloud Bus |
Spring Cloud 的功能很明显比 Dubbo 更加强大,涵盖面更广,而且作为 Spring 的旗舰项目,它也能够与 Spring Framework、Spring Boot、Spring Data、Spring Batch 等其他 Spring 项目完美融合,这些对于微服务而言是至关重要的。
使用 Dubbo 构建的微服务架构就像组装电脑,各环节选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心,但是如果使用者是一名高手,那这些都不是问题。
而 Spring Cloud 就像品牌机,在 Spring Source 的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础原理有足够的了解。
dubbo和Feign远程调用的差异
面试话术:
Feign是SpringCloud中的远程调用方式,基于成熟Http协议,所有接口都采用Rest风格。因此接口规范更统一,而且只要符合规范,实现接口的微服务可以采用任意语言或技术开发。但受限于http协议本身的特点,请求和响应格式臃肿,其通信效率相对会差一些。
Dubbo框架默认采用Dubbo自定义通信协议,与Http协议一样底层都是TCP通信。但是Dubbo协议自定义了Java数据序列化和反序列化方式、数据传输格式,因此Dubbo在数据传输性能上会比Http协议要好一些。
不过这种性能差异除非是达极高的并发量级,否则无需过多考虑。
相关资料:
Dubbo采用自定义的Dubbo协议实现远程通信,是一种典型的RPC调用方案,而SpringCloud中使用的Feign是基于Rest风格的调用方式。
1)Rest风格
REST是一种架构风格,指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
Rest的风格可以完全通过HTTP协议实现,使用 HTTP 协议处理数据通信。REST架构对资源的操作包括获取、创建、修改和删除资源的操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。
因此请求和想要过程只要遵循http协议即可,更加灵活
SpringCloud中的Feign就是Rest风格的调用方式。
2)RPC
Remote Procedure Call,远程过程调用,就是像调用本地方法一样调用远程方法。
RPC一般要确定下面几件事情:
- 数据传输方式:多数RPC框架选择TCP作为传输协议,性能比较好。
- 数据传输内容:请求方需要告知需要调用的函数的名称、参数、等信息。
- 序列化方式:客户端和服务端交互时将参数或结果转化为字节流在网络中传输,那么数据转化为字节流的或者将字节流转换成能读取的固定格式时就需要进行序列化和反序列化
因为有序列化和反序列化的需求,因此对数据传输格式有严格要求,不如Http灵活
Dubbo协议就是RPC的典型代表。
我们看看Dubbo协议和Feign的调用区别:
Dubbo | Feign(Http调用) | |
---|---|---|
传输协议 | TCP | TCP |
开发语言 | java | 不限 |
性能 | 好 | 一般 |
灵活性 | 一般 | 好 |
Eureka和Zookeeper注册中心的区别
面试话术:
SpringCloud和Dubbo都支持多种注册中心,不过目前主流来看SpringCloud用Eureka较多,Dubbo则以Zookeeper为主。两者存在较大的差异:
- 从集群设计来看:Eureka集群各节点平等,没有主从关系,因此可能出现数据不一致情况;ZK为了满足一致性,必须包含主从关系,一主多从。集群无主时,不对外提供服务
- CAP原则来看:Eureka满足AP原则,为了保证整个服务可用性,牺牲了集群数据的一致性;而Zookeeper满足CP原则,为了保证各节点数据一致性,牺牲了整个服务的可用性。
- 服务拉取方式来看:Eureka采用的是服务主动拉取策略,消费者按照固定频率(默认30秒)去Eureka拉取服务并缓存在本地;ZK中的消费者首次启动到ZK订阅自己需要的服务信息,并缓存在本地。然后监听服务列表变化,以后服务变更ZK会推送给消费者。
相关资料:
首先,Eureka和Zookeeper都是服务治理框架,但是设计上有一定的差别。
先看下CAP原则:C-数据一致性;A-服务可用性;P-服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
-
Eureka满足AP,Zookeeper满足CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是Zookeeper和Eureka在一致性与可用性间做出了不同的选择。
-
Zookeeper
:Zookeeper的设计追求数据的一致性,不保证服务的可用性。当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。 -
Eureka
:Eureka追求的是服务的可用性,从而牺牲了数据的一致性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况。- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。
-
-
Eureka集群各节点平等,Zookeeper中有主从之分
- 如果Zookeeper集群中部分宕机,可能会导致整个集群因为选主而阻塞,服务不可用
- eureka集群宕机部分,不会对其它机器产生影响
-
Eureka的服务发现需要主动去拉取,Zookeeper服务发现是监听机制
- eureka中获取服务列表后会缓存起来,每隔30秒重新拉取服务列表
- zookeeper则是监听节点信息变化,当服务节点信息变化时,客户端立即就得到通知
怎么保证Eureka注册中心高可用?
可以部署多个注册中心,注册中心相互注册
Ribbon实现负载均衡的原理?
在RestTemplate
上添加了@LoadBalanced
注解后,就会使用LoadBalancerClient
来配置RestTemplate
,自动配置LoadBalancerAutoConfiguration
在满足@ConditionalOnBean(LoadBalancerClient.class)
后就会实现自动配置,在RestTemplateCustomizer
中添加了拦截器LoadBalancerInterceptor
,LoadBalancerInterceptor
拦截器会拦截所有的请求,通过ServiceId
获取服务器真实地址列表,获取服务器列表后使用负载均衡算法选出一台服务器进行调用
Ribbon有哪些负载均衡算法?
轮询、随机、权重
Hystix如何解决雪崩问题?
Hystrix为每个依赖服务调用分配一个小的独立线程池,用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满调用将被立即拒绝,否则使用线程来处理请求。
可以在主线程中设置超时时间,超过这个时间如果子线程还没有执行完成任务或者子线程执行出现异常,则会进行服务降级。
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。
Hystix如何进行服务熔断?
Hystix的熔断状态:
- Closed:关闭状态(断路器关闭),所有请求都正常访问。
- Open:打开状态(断路器打开),所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
- Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。