spring 接口幂等性和解决方法_Spring基础知识整理

  • Post author:
  • Post category:其他


IOC

Beans 组件和 Context 组件是实现IOC和依赖注入的基础。

IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。

循环依赖

循环依赖就是说A依赖B,B依赖A。Spring默认支持循环依赖。

Spring 实现循环依赖通过三级的Map缓存实现,在BeanA生命周期的第二步,通过反射创建完BeanA的实例后,如果BeanA是单例,处于创建过程中,并且允许循环引用就把BeanA实例放在singletonFactories第三级的map缓存中。这个时候通过set()方法设置一些属性值,发现BeanB,开始创建BeanB。BeanB实例化完成之后,发现依赖BeanA。这个时候就会把三级Map缓存中的BeanA移动到earlySingletonObjects第二级的map缓存中。BeanB可以正常实例化和初始化完成。并且被填充到BeanA的属性之中。BeanA继续进行初始化,初始化完成之后会把BeanA放到singletonObjects第一级map缓存中。

实际通过上面描述使用两级的map缓存也可以实现相同的效果,之所以实现三级缓存,一个可能是为了更好的区分Bean实例当前的状态。再一个可能是可以添加扩展点,在不同的缓存阶段做不同的处理。

显然如果在A的构造函数中依赖B,并且在B的构造函数中依赖A的情况是支持不了的。对于原型类型的Bean的循环依赖也无法处理。

Singleton Bean依赖Prototype Bean如何解决

singleton 依赖 singleton,或者 prototype 依赖 prototype 都很好解决,直接设置属性依赖就可以了。

一种解决方案就是不要用属性依赖,每次获取依赖的 bean 的时候从 BeanFactory 中取。

另一种解决方案就是通过使用方法注入Lookup method。

BeanPostProcessor

BeanPostProcessor 的两个方法,分别在 init-method 的前后得到执行。

public interface BeanPostProcessor {    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}

Bean生命周期

  1. Bean容器找到配置文件中Spring Bean的定义。
  2. Bean容器利用Java Reflection API创建一个Bean的实例。
  3. 如果涉及到一些属性值 利用 set()方法设置一些属性值。
  4. 如果 Bean 实现了 BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
  5. 如果 Bean 实现了 BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
  6. 如果Bean实现了BeanFactoryAware 接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。
  7. 如果Bean实现了ApplicationContextAware接口,调用setApplicationContext()方法,传入ApplicationContext对象的实例。
  8. 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
  9. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。
  10. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
  11. 如果 Bean 在配置文件中的定义包含init-method 属性,执行指定的方法。
  12. 如果有和加载这个Bean的Spring 容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法。
  13. 当要销毁 Bean 的时候,如果 Bean 实现了DisposableBean接口,执行 destroy()方法。
  14. 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。


补充说明

通过实现InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;

通过 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;

在指定方法上加上@PostConstruct或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用。

Bean作用域

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  • session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
  • global-session:全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

Spring 容器可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 bean 的实例后,bean 的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。

单例Bean的线程安全问题

单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。

常见的有两种解决办法:

  1. 在Bean对象中尽量避免定义可变的成员变量(不太现实)。
  2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

@Autowired工作原理

@Autowired默认先按byType,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个,则报出异常。

@Component 和 @Bean 的区别是什么?

  • 作用对象不同: @Component 注解作用于类,而@Bean注解作用于方法。
  • @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的实例,当我需要用它的时候还给我。
  • @Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

AOP

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理。动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。子类拥有父类非 private 的属性、方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单。如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。

Spring AOP 的原理很简单,就是动态代理,它和 AspectJ 不一样,AspectJ 是直接修改掉你的字节码。

代理模式很简单,接口 + 真实实现类 + 代理类,其中 真实实现类 和 代理类 都要实现接口,实例化的时候要使用代理类。所以,Spring AOP 需要做的是生成这么一个代理类,然后替换掉真实实现类来对外提供服务。

替换的过程怎么理解呢?在 Spring IOC 容器中非常容易实现,就是在 getBean(…) 的时候返回的实际上是代理类的实例,而这个代理类我们自己没写代码,它是 Spring 采用 JDK Proxy 或 CGLIB 动态生成的。

@Async注解使用细节

@Async注解一般用在方法上,如果用在类上,那么这个类所有的方法都是异步执行的;

@Async可以放在任何方法上,哪怕你是private的(若是同类调用,请务必注意注解失效的情况)

所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象

@Async可以放在接口处(或者接口方法上)。但是只有使用的是JDK的动态代理时才有效,CGLIB会失效。因此建议:统一写在实现类的方法上

需要注解@EnableAsync来开启异步注解的支持

若你希望得到异步调用的返回值,请你的返回值用Futrue变量包装起来

Spring 事务

Spring 事务实现机制

58782f61474a6d3ff03e106e97c37155.png

添加描述

在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager 操作数据源DataSource提交或回滚事务。

Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,上图是以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。

Spring 管理事务的方式有几种?

  1. 编程式事务,在代码中硬编码。(不推荐使用)
  2. 声明式事务,在配置文件中配置(推荐使用)

声明式事务又分为两种:

  1. 基于XML的声明式事务
  2. 基于注解的声明式事务

Spring事务管理接口


  • PlatformTransactionManager:

    (平台)事务管理器

  • TransactionDefinition:

    事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)

  • TransactionStatus:

    事务运行状态


所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。


Spring并不直接管理事务,而是提供了多种事务管理器

,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是:

org.springframework.transaction.PlatformTransactionManager

,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

DataSourceTransactionManager:适用于使用JDBC和iBatis进行数据持久化操作的情况。

事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,隔离级别,传播行为,回滚规则,是否只读,事务超时。

Spring 事务中的隔离级别有哪几种?


TransactionDefinition 接口中定义了五个表示隔离级别的常量:


  • TransactionDefinition.ISOLATION_DEFAULT:

    使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.

  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:

    最低的隔离级别,允许读取尚未提交的数据变更,

    可能会导致脏读、幻读或不可重复读

  • TransactionDefinition.ISOLATION_READ_COMMITTED:

    允许读取并发事务已经提交的数据,

    可以阻止脏读,但是幻读或不可重复读仍有可能发生

  • TransactionDefinition.ISOLATION_REPEATABLE_READ:

    对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,

    可以阻止脏读和不可重复读,但幻读仍有可能发生。

  • TransactionDefinition.ISOLATION_SERIALIZABLE:

    最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,

    该级别可以防止脏读、不可重复读以及幻读

    。但是这将严重影响程序的性能。通常情况下也不会用到该级别

Spring 事务中哪几种事务传播行为?

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。有以下七种事务传播行为:


支持当前事务的情况:


  • TransactionDefinition.PROPAGATION_REQUIRED:

    如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

  • TransactionDefinition.PROPAGATION_SUPPORTS:

    如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • TransactionDefinition.PROPAGATION_MANDATORY:

    如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)


不支持当前事务的情况:


  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:

    创建一个新的事务,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:

    以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NEVER:

    以非事务方式运行,如果当前存在事务,则抛出异常。


其他情况:


  • TransactionDefinition.PROPAGATION_NESTED:

    如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。嵌套的子事务是保存点(SavePoint)的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。

私有方法是否生效

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外,

@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

只有@Transactional 注解应用到 public 方法,才能进行事务管理。这是因为在使用 Spring AOP 代理时,Spring 在调用TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取@Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

protected TransactionAttribute computeTransactionAttribute(Method method,    Class> targetClass) {        // Don't allow no-public methods as required.        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}

这个方法会检查目标方法的修饰符是不是 public,若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。

Spring事务不生效的原因

请确保你的业务和事务入口在同一个线程里,否则事务也是不生效的。

@Transactional的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中

一个无事务的方法调用另一个有事务的方法,事务是不会起作用的

(这就是业界老问题:类内部方法调用事务不生效的问题原因)解决方法内部调用事务不生效的最常用方法之一:内部维护一个注入自己的Bean,然后使用这个属性来调用方法。其实还有一种方法,那就是利用Aop上下文来获取代理对象(((TestService)AopContext.currentProxy()).create(); ),然后通过代理对象来调用。这里需要注意:Aop上下文spring默认是关闭的,需要手动开启。

事务回滚


事务回滚

规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。


@Transactional(rollbackFor = Exception.class)注解了解吗?

我们知道:Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。

当@Transactional注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。


事务超时

,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。


事务的只读属性

是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。

TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息.

Spring MVC

Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的Web层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。

SpringMVC在初始化的时候,加载了RequestMappingHandlerMapping和RequestMappingHandlerAdapter。在实例化RequestMappingHandlerMapping的时候,过滤了所有类上有Controller、RequestMapping的Bean,获取他们的方法。注册methodMapping和urlMap。为以后的请求地址匹配做准备。 在实例化RequestMappingHandlerAdapter的时候,又注册了一堆解析器,包括参数解析器和返回值类型解析器。为请求的参数解析和返回类型解析做准备。

Spring MVC处理流程

  • 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
  • DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
  • 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
  • HandlerAdapter 会根据 Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
  • 处理器处理完业务后,会返回一个ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
  • ViewResolver 会根据逻辑View查找实际的 View。
  • DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  • 把View返回给请求者(浏览器)

DispatchServlet

@Controller返回一个页面

单独使用 @Controller 不加 @ResponseBody的话一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC 的应用,对应于前后端不分离的情况。

@RestController 返回JSON 或 XML 形式数据

但@RestController只返回对象,对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)。

将一个类声明为Spring的 bean 的注解有哪些?

我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
  • @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。

Spring Boot

Spring Boot是Spring旗下众多的子项目之一,其理念是约定优于配置,它通过实现了自动配置(大多数用户平时习惯设置的配置作为默认配置)的功能来为用户快速构建出标准化的应用。Spring Boot的特点可以概述为如下几点:

  • 内置了嵌入式的Tomcat、Jetty等Servlet容器,应用可以不用打包成War格式,而是可以直接以Jar格式运行。
  • 提供了多个可选择的”starter”以简化Maven的依赖管理(也支持Gradle),让您可以按需加载需要的功能模块。
  • 尽可能地进行自动配置,减少了用户需要动手写的各种冗余配置项,Spring Boot提倡无XML配置文件的理念,使用Spring Boot生成的应用完全不会生成任何配置代码与XML配置文件。
  • 提供了一整套的对应用状态的监控与管理的功能模块(通过引入spring-boot-starter-actuator),包括应用的线程信息、内存信息、应用是否处于健康状态等,为了满足更多的资源监控需求,Spring Cloud中的很多模块还对其进行了扩展。

自动配置的原理

@SpringBootApplication 看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制。
  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的bean,注解默认会扫描该类所在的包下所有的类。
  • @Configuration:允许在上下文中注册额外的bean或导入其他配置类。

@EnableAutoConfiguration 注解通过Spring 提供的 @Import 注解导入了AutoConfigurationImportSelector类(@Import 注解可以导入配置类或者Bean到当前类中)。

AutoConfigurationImportSelector类中getCandidateConfigurations方法会将所有自动配置类的信息以 List 的形式返回。它通过调用SpringFactoriesLoader.loadFactoryNames()来扫描加载含有META-INF/spring.factories文件的jar包,该文件记录了具体有哪些自动配置类。这些配置信息会被 Spring 容器作为bean来管理。

自动配置类都有@Conditional相关的注解。@ConditionalOnClass(指定的类必须存在于类路径下), @ConditionalOnBean(容器中是否有指定的Bean)等等都是对@Conditional注解的扩展。

@AutoConfigureAfter提示自动配置应在其他指定的自动配置类之后应用。

JPA

如何使用JPA在数据库中非持久化一个字段?

如果我们想让字段不被持久化,也就是不被数据库存储怎么办?我们可以采用下面几种方法:

static String transient1; // not persistent because of static final String transient2 = “Satish”; // not persistent because of final transient String transient3; // not persistent because of transient @Transient String transient4; // not persistent because of @Transient

一般使用后面两种方式比较多,我个人使用注解的方式比较多。

Spring Cloud

Spring Cloud是一个比较好的选择,它提供了一站式的分布式系统解决方案,包含了许多构建分布式系统与微服务需要用到的组件,例如服务治理、API网关、配置中心、消息总线以及容错管理等模块。

注册中心

Eureka注册中心


  • 服务提供者

    : 就是提供一些自己能够执行的一些服务给外界。

  • 服务消费者

    : 就是需要使用一些服务的“用户”。

  • 注册中心

    : 其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把自己注册到服务注册中心,而服务消费者如需要消费一些服务(使用一些功能)就可以在注册中心中寻找注册在服务中介的服务提供者。

  • 服务注册 Register

    :当 Eureka 客户端向 Eureka Server 注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。

  • 服务续约 Renew

    :Eureka 客户会

    每隔30秒(默认情况下)

    发送一次心跳来

    续约

    。 通过续约来告知 Eureka Server 该 Eureka 客户仍然存在,没有出现问题。 正常情况下,如果 Eureka Server 在90秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。

  • 获取注册列表信息 Fetch Registries

    :Eureka 客户端从服务器获取注册列表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同, Eureka 客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。 Eureka 服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka 客户端和 Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下 Eureka 客户端使用压缩 JSON 格式来获取注册列表的信息。

  • 服务下线 Cancel

    :Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();

  • 服务剔除 Eviction

    :在默认的情况下,当Eureka客户端连续90秒(3个续约周期)没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。Eureka 会有一种

    自我保护机制

    ,默认是15分钟内收到的续约低于原来的85%(续约配置比例)就会开启自我保护。这阶段 Eureka Server 不会剔除其列表中的实例,即使过了 90秒 也不会。

Eureka:

符合AP原则

为了保证了可用性,

Eureka

不会等待集群所有节点都已同步信息完成,它会无时无刻提供服务。

Zookeeper:

符合CP原则

为了保证一致性,在所有节点同步完成之前是阻塞状态的。

负载均衡

Ribbon 是运行在消费者端的负载均衡器。其工作原理就是 Consumer 端获取到了所有的服务列表之后,在其

内部

使用

负载均衡算法

,进行对多个系统的调用。

Ribbon的负载均衡调度算法,其默认是使用的 RoundRobinRule 轮询策略。


  • RoundRobinRule

    :轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮。若最终还没有找到,则返回 null。

  • RandomRule

    : 随机策略,从所有可用的 provider 中随机选择一个。

  • RetryRule

    : 重试策略。先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。

Dubbo的负载均衡算法缺省为 Random 随机调用。


  • Random:

    随机,按权重设置随机概率。在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

  • RoundRobin:

    轮询,按公约后的权重设置轮询比率。存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

  • LeastActive:

    最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

  • ConsistentHash:

    一致性 Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

Nginx是一种集中式的负载均衡器,将所有请求都集中起来,然后再进行负载均衡。Nginx的负载均衡算法使用的是轮询和加权轮询算法。

熔断,限流和降级

由于某些原因,大量请求在某个服务上阻塞,由于这些请求会消耗占用系统的线程、IO 等资源,导致服务崩溃就是服务雪崩。

熔断 就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过断路器直接将此请求链路断开。

降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。这也就对应着 Hystrix的后备处理模式。你可以通过设置fallbackMethod来给一个方法设置备用的代码逻辑。

在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。

Hystrix 仪表盘用来实时监控 Hystrix 的各项指标信息的。

整个服务的降级需要通过API网关进行处理。

配置中心

Spring Cloud Config

既能对配置文件统一地进行管理,又能在项目运行时动态修改配置文件。

会把各个 应用/系统/模块 的配置文件存放到

统一的地方然后进行管理

(Git 或者 SVN)。


消息总线

Spring Cloud Bus 的作用就是

管理和广播分布式系统中的消息

,也就是消息引擎系统中的广播模式。当然作为

消息总线

的 Spring Cloud Bus 可以做很多事而不仅仅是客户端的配置刷新功能。

API网关

API 网关的作用就是把业务无关的功能剥离出来,让你的 API 只关心业务本身,与业务无关的功能全都丢给 API 网关,包括协议的转换、限流限速、安全、统计、可追踪、缓存、日志报表等。如此,业务才能跑得更快,这就是为什么微服务会从单体变成 API 网关的架构。

ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。Zuul 中最关键的就是

路由和过滤器。

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等。

  • Route(路由):这是网关的基本构建块。它由一个 ID(自定义的路由 ID,保持唯一),一个目标 URI(目标服务地址),一组断言(路由条件)和一组过滤器定义。如果断言为真,则路由匹配。
  • Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
  • Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。Hystrix 过滤器允许你将断路器功能添加到网关路由中,使你的服务免受级联故障的影响,并提供服务降级处理。RequestRateLimiter 过滤器可以用于限流,使用RateLimiter实现来确定是否允许当前请求继续进行,如果请求太大默认会返回HTTP 429-太多请求状态。Retry GatewayFilter对路由请求进行重试的过滤器,可以根据路由请求返回的HTTP状态码来确定是否进行重试。

客户端向 Spring Cloud Gateway 发出请求。如果 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

Spring Cloud Gateway是使用 netty+webflux 实现,通过 Spring WebFlux 的 HandlerMapping 做为底层支持来匹配到转发路由,Spring Cloud Gateway 内置了很多 Predicates 工厂,这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。

Spring Cloud Gateway具有如下特性:

  • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
  • 集成Hystrix的断路器功能;
  • 集成 Spring Cloud 服务发现功能;
  • 易于编写的 Predicate(断言)和 Filter(过滤器);
  • 请求限流功能;
  • 支持路径重写。

在结合注册中心使用过滤器的时候,我们需要注意的是uri的协议为lb,这样才能启用Gateway的负载均衡功能。

用到的设计模式


  • 工厂设计模式

    : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。

  • 代理设计模式

    : Spring AOP 功能的实现。

  • 单例设计模式

    : Spring 中的 Bean 默认都是单例的。

  • 模板方法模式

    : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。

  • 包装器设计模式

    : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

  • 观察者模式:

    Spring 事件驱动模型就是观察者模式很经典的一个应用。

  • 适配器模式

    :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。

参考衔接

  1. https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485576&idx=1&sn=f993349f12650a68904e1d99d2465131&chksm=cea24743f9d5ce55ffe543a0feaf2c566382024b625b59283482da6ab0cbcf2a3c9dc5b64a53&token=2133161636&lang=zh_CN&scene=21#wechat_redirect
  2. https://dzone.com/articles/spring-framework-restcontroller-vs-controller
  3. https://www.javadoop.com/post/spring-ioc
  4. https://juejin.im/post/5b00c52ef265da0b95276091
  5. https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/index.html
  6. https://www.javadoop.com/post/spring-aop-source
  7. https://juejin.im/post/5c754d676fb9a04a0441ae8c
  8. https://sylvanassun.github.io/2018/01/08/2018-01-08-spring_boot_auto_configure/
  9. https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
  10. https://juejin.im/post/5de2553e5188256e885f4fa3
  11. https://juejin.im/post/593cc4c25c497d006b876429
  12. http://www.ityouknow.com/springcloud/2018/12/12/spring-cloud-gateway-start.html
  13. https://www.upyun.com/opentalk/445.html
  14. https://blog.csdn.net/f641385712/article/details/80445933
  15. https://my.oschina.net/u/1266221/blog/797101
  16. https://blog.csdn.net/f641385712/article/details/89430276



版权声明:本文为weixin_39942397原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。