微服务是一种架构风格,通过将大型的单体应用划分为比较小的服务单元,从而降低整个系统的复杂度。
优点:
缺点:
SpringCloud 提供了构建微服务系统所需的一组通用开发模式以及一系列快速实现这些开发模式的工具。
通常所说的 SpringCloud 是指 SpringCloud Netflix。它和 SpringCloudAlibaba 都是 SpringCloud 这一系列开发模式的具体实现。
SpringCloud NetFlix组件有:
注册中心:Eureka
配置中心:SpringCloud Config
熔断器:Hytrix
负载均衡:Ribbon
网关:Zuul、gateway
SpringCloud Alibaba:
注册中心:Nacos
配置中心:Nacos
熔断器:Sentinel
负载均衡:Ribbon
网关:gateway
服务调用:Open Feign、Dubbo RPC
拆分微服务的时候,为了尽量保证微服务的稳定,会有一些基本的准则:
高内聚低耦合是一种从上而下指导微服务设计的方法。实现高内聚低耦合的工具主要有同步的接口调用和异步的事件驱动两种方式。
什么是 DDD:在2004年,由 Eric Evans 提出,DDD 是面对软件复杂之道提出来的。Dmain-Driven-Design。
Martin Fowler - 贫血模型,简单的 POJO 只包含对象属性,没有属性的变化, 会造成贫血失忆症。充血模型不仅包含属性,还包含所有属性的变化。
大泥团:不利于微服务的拆分。大泥团拆分出来的微服务依然是大泥团结构,当这个业务服务逐渐复杂。这个泥团又会膨胀成大泥团。
DDD 是一种方法论,没有一个稳定的技术框架。DDD要求领域跟技术无关、跟存储介质无关,跟通信无关。
中台就是将各个业务线中可以复用的一些功能抽取出来,剥离个性,提取共性,形成一些可复用的组件。
中台可以分为三类:业务中台、数据中台、技术中台。大数据杀熟——数据中台。电商——收银中台、支付风控中台。
中台跟DDD结合:DDD会通过限界上下文将系统拆分成一个一个的领域,而这种限界上下文天生就成了中台直接的逻辑屏障。
DDD在技术与资源调度方面都能够给中台建设提供不错的指导。
敏捷开发:目的就是为了提高团队的交付效率,快速迭代,快速试错。
每个月固定发布新版本、以分支的形式保存到代码仓库中。快速入职。任务面板、站会。团队人员灵活流动、同时形成各个专家代表。开发环境、测试环境、预生产环境、生产环境。文档优先。
链路追踪:
持续集成:maven -> build -> shell ; jenkins
AB发布:
SpringCloud NetFlix组件有:
注册中心:Eureka
配置中心:SpringCloud Config
服务熔断:Hytrix
负载均衡:Ribbon
网关:Zuul、gateway
服务调用:Feign/OpenFeign
SpringCloud Alibaba:
注册中心:Nacos
配置中心:Nacos
熔断器:Sentinel
负载均衡:Ribbon
网关:gateway
服务调用:OpenFeign/Dubbo RPC
分布式事务:Seata
Spring Cloud 是一个微服务框架,提供了微服务领域中的很多功能组件。Dubbo 是一个 RPC 调用框架,核心是解决服务调用间的问题。Spring Cloud 是一个大而全的框架,Dubbo 侧重于服务调用,所以 Dubbo提供的功能没有 Spring Cloud 全面,但是 Dubbo 的服务调用性能比 Spring Cloud 高。Spring Cloud 和 Dubbo不是对立的,可以结合起来一起使用。
相同点:
不同点:熔断是下游服务故障触发的,降级是为了降低系统负载。
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
Hystrix 提供服务熔断和服务降级的功能。
熔断机制是应对雪崩效应的一种链路保护机制,当链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息,当检测到该节点微服务调用响应正常后,恢复调用链路。
服务降级是当发生程序运行异常、超时、服务熔断、线程池/信号量打满等导致服务降级时,不让客户端等待,并立即返回一个友好的提示。
Hystrix 通过信号量或线程池来实现服务降级的。
信号量模式:信号量模式控制单个服务提供者执行并发度,比如单个CommondKey下正在请求数为N,若N小于maxConcurrentRequests,则继续执行;若大于等于maxConcurrentRequests,则直接拒绝,进入降级逻辑。信号量模式使用请求线程本身执行,没有线程上下文切换,开销较小,但超时机制失效。
线程池模式:线程池模式控制单个服务提供者执行并发度,代码上都会先走获取信号量,只是使用默认信号量,全部请求可通过,然后实际调用线程池逻辑。线程池模式下,比如单个CommondKey下正在请求数为N,若N小于maximumPoolSize,会先从 Hystrix 管理的线程池里面获得一个线程,然后将参数传递给任务线程去执行真正调用,如果并发请求数多于线程池线程个数,就有任务需要进入队列排队,但排队队列也有上限,如果排队队列也满,则进去降级逻辑。线程池模式可以支持异步调用,支持超时调用,存在线程切换,开销大。
实现原理:Ribbon从注册中心中获取服务列表,然后通过获取到的服务列表,采用负载均衡算法(Ribbon默认采用的是轮训方式),利用通信框架(RestTemplate或Feign等)进行服务调用。
openfeign
是spring cloud
在feign
的基础上支持了spring mvc
的注解,如@RequesMapping
、@GetMapping
、@PostMapping
等。openfeign
还实现与Ribbon
的整合。
服务提供者只需要提供 API 接口,而不需要像 dubbo
那样需要强制使用 implem ents
实现接口,即使用 fegin
不要求服务提供者在 Controller
使用 implements
关键字实现接口。
Feign底层实现原理
openfeign
通过包扫描将所有被 @FeignClient
注解注释的接口扫描出来,并为每个接口注册一个FeignClientFactoryBean
实例。FeignClientFactoryBean
是一个FactoryBean
,当Spring调用FeignClientFactoryBean
的getObject
方法时,openfeign
返回一个Feign生成的动态代理对象,拦截接口的方法执行。
feign
会为代理的接口的每个方法Method
都生成一个MethodHandler
。
当为接口上的@FeignClient
注解的url属性配置服务提供者的url时,其实就是不与Ribbon整合,此时由SynchronousMethodHandler实现接口方法进行远程同步调用。
当接口上的@FeignClient
注解的url属性不配置时,且会走负载均衡逻辑,也就是需要与Ribbon整合使用。这时候不再是使用默认的Client(Default)调用接口,而是使用LoadBalancerFeignClient
调用接口,由LoadBalancerFeignClient
实现与Ribbon的整合。
为什么要将服务注册到nacos?
为了更好的查找这些服务。
Nacos服务是如何判定服务实例的状态?
通过发送心跳包,5秒发送一次,如果15秒没有回应,则说明服务出现了问题,
如果30秒后没有回应,则说明服务已经停止。
服务消费方是如何调用服务提供方的服务的?
通过创建RestTemplate对象来实现。
Nacos中的负载均衡底层是如何实现的?
通过Ribbon实现,Ribbon中定义了一些负载均衡算法。然后基于这些算法从服务
实例中获取一个实例为消费方提供服务。
Ribbon 是什么?Ribbon 可以解决什么问题?
Ribbon是Netflix公司提供的负载均衡客户端。
Ribbon可以基于负载均衡策略进行服务调用,所有策略都会实现IRule接口。
Ribbon 内置的负载策略都有哪些?
8种,可以通过查看IRule接口的实现类进行查看
@LoadBalanced的作用是什么?
描述RestTemplate对象,用于告诉Spring框架,在使用RestTempalte进行服务调用时,这个调用过程会被一个拦截器进行拦截,然后在拦截器内部。启动负载均衡策略。
项目中为什么要定义bootstrap.yml文件?
此文件被读取的优先级比较高,可以在服务启动时读取配置中心的数据
Nacos配置中心宕机了,我们的服务还可以读取到配置信息吗?
可以从内存,客户端获取配置中心的配置信息以后,会将配置信息在本地存储一份
微服务应用中我们的客户端如何从配置中心获取信息?
我们的服务一般会先从内存中读取配置信息,同时我们的微服务还可以定时向nacos配置中心发请求拉取(pull)更新的配置信息。
微服务应用中客户端如何感知配置中心的数据变化?
1.4.x版本以后nacos客户端会基于长轮询机制从nacos获取配置信息,所谓的长轮询就是没有配置更新时,会在nacos服务端的队列进行等待。
Nacos配置中的管理模型是怎样的?
namespace,group,service/data-id
在启动类上加上 @SpringBootApplication
注解,这个注解主要包含:
// spring配置注解
@SpringBootConfiguration
// spring自动专配注解
@EnableAutoConfiguration
// spring 扫描包注解
@ComponentScan
Springboot 通过 run 方法启动,其大致流程如下:
初始化SpringApplication,配置基本的环境变量、资源、构造器、监听器,初始化阶段的主要作用是为运行SpringApplication实例对象启动环境变量准备以及进行必要的资源构造器的初始化动作。如项目运行环境:web应用还是普通的java程序。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// 设置运行环境
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
// 设置初始化
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
运行 SpringApplication,SpringBoot正式启动加载过程,包括启动流程监控模块、配置环境加载模块、ApplicationContext容器上下文环境加载模块。refreshContext方法刷新应用上下文并进行自动化配置模块加载,通过 SpringFactoriesLoader加载META-INF/spring.factories文件的配置,实现自动配置核心功能。运行SpringApplication的主要代码如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
/**
* 启动监听
* 创建应用的监听器SpringApplicationRunListeners并开始监听,监控模块通过调用
* getSpringFactoriesInstances私有协议从META-INF/spring.factories文件中
* 取得SpringApplicationRunListener监听器实例。
* 当前事件监听器SpringApplicationRunListener中只有一个EventPublishingRunlistener广播事件监听器,
* 它的starting方法会封装成SpringApplicationEvent事件广播出去,被SpringApplication中配置的listener监听。
* 这一步骤执行完成后也会同时通知SpringBoot其他模块目前监听初始化已经完成,可以开始执行启动方案了。
*/
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
/**
* ConfigurableEnviroment 配置环境模块和监听
* 创建配置环境,创建应用程序的环境信息。
* 如果是Web程序,创建StandardServletEnvironment;否则创建StandardEnviroment;
* 加载属性配置文件,将配置文件加入监听器对象中(SpringApplicationRunListeners)。
* 通过configPropertySource方法设置properties配置文件,通过执行configProfies方法设置profiles;
* 配置监听,发布environmentPrepared事件,及调用ApplicationListener#onApplicationEvent方法,
* 通知SpringBoot应用的environment已经准备完成。
*/
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
/**
* ConfigurableApplicationContext 配置应用上下文
*(1) 配置Spring容器应用上下文对象,
* 它的作用是创建Run方法的返回对象ConfigurableApplicationContext(应用配置上下文),
* 此类主要继承了Application、LifeCycle、Closeable接口,
* 而ApplicationContext是Spring框架中负责Bean注入容器的主要载体,
* 负责bean加载、配置管理、维护bean之间依赖关系及Bean生命周期管理。
*(2) 配置基本属性,对应prepareContext方法将listener、environment、banner、applicationArguments
* 等重要组件与Spring容器上下文对象关联。借助SpringFactoriesLoader查找可用的
* ApplciationContextInitailizer, 它的initialize方法会对创建好的ApplicationContext进行初始化,
* 然后它会调用SpringApplicationRunListener#contextPrepared方法,
* 此时SpringBoot应用的ApplicationContext已经准备就绪,为刷新应用上下文准备好了容器。
*(3) 刷新应用上下文,对应源码中的refreshContext(context)方法,
* 将通过工程模式产生应用上下文中所需的bean。实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,
* 包括spring.factories的加载、bean的实例化等核心工作。
* 然后调用SpringApplicationRunListener#finish方法告诉SprignBoot应用程序,
* 容器已经完成ApplicationContext装载。
*/
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
SpringBoot应用程序的启动流程主要包括初始化SpringApplication和运行SpringApplication两个过程。其中初始化SpringApplication包括配置基本的环境变量、资源、构造器和监听器,为运行SpringApplciation实例对象作准备;而运行SpringApplication实例为应用程序正式启动加载过程,包括SpringApplicationRunListeners 引用启动监控模块、ConfigrableEnvironment配置环境模块和监听及ConfigrableApplicationContext配置应用上下文。当完成刷新应用的上下文和调用SpringApplicationRunListener#contextPrepared方法后表示SpringBoot应用程序已经启动完成。
初始化 SpringApplication
配置基本的环境变量、资源、构造器和监听器,为运行SpringApplciation实例对象作准备。
运行 SpringApplication
启动监控模块
ConfigrableEnvironment 配置环境和监听
配置应用上下文 ConfigrableApplicationContext
启动完成
当完成刷新应用的上下文和调用SpringApplicationRunListener#contextPrepared方法后表示SpringBoot应用程序已经启动完成
IOC容器就像是一个工厂,里面有很多流水线生产出一个个产品(bean)。bean的加载流程大概分为:
容器启动阶段
spring启动时通过 AnnotatedBeanDefinitionReader 的 doRegisterBean 方法读取注解元信息封装成 BeanDefinition,然后通过BeanDefinationRegistry 注册到 BeanFactory 的 beanDefinitionMap 中。如果是xml文件配置bean元信息的话,需要BeanFactoryPostProcessor把占位符替换为相应的数据。
bean加载阶段
通过 beanName先从缓存中加载实例,缓存中没有则通过父BeanFactory加载,然后把BeanDefinition转换合并成RootBeanDefinition,初始化依赖bean。默认是单例bean,进入单例bean的创建方法、先将原生 Bean 实例装饰成 BeanWrapper,然后检查 Aware 相关接口以及BeanPostProcessor前置处理、初始化方法、后置处理、使用、销毁前的方法、销毁。