技术栈传送门
JAVA 基础手撸架构,Java基础面试100问_vincent-CSDN博客
JAVA 集合手撸架构,JAVA集合面试60问_vincent-CSDN博客
JVM 虚拟机手撸架构,JVM面试30问_vincent-CSDN博客
并发编程手撸架构,并发编程面试123问_vincent-CSDN博客
Spring 手撸架构,Spring面试63问_vincent-CSDN博客
Spring cloud 手撸架构,Spring cloud面试45问_vincent-CSDN博客
SpringBoot手撸面试,Spring Boot面试41问_vincent-CSDN博客
Netty 与 RPC手撸架构,Netty 与 RPC面试48问_vincent-CSDN博客
Doubo 手撸架构,Dubbo面试49问_vincent-CSDN博客
Redis手撸架构,Redis面试41问_vincent-CSDN博客
Zookeeper手撸架构,Zookeeper面试27问_vincent-CSDN博客
Mysql 手撸架构,Mysql 面试126问_vincent-CSDN博客
MyBatis手撸架构,MyBatis面试42问_vincent-CSDN博客
MongoDB 手撸架构,MongDB 面试50问_vincent-CSDN博客
Elasticsearch手撸架构,Elasticsearch 面试25问_vincent-CSDN博客
RabbitMQ 手撸架构,RabbitMQ 面试49问_vincent-CSDN博客
Kafka 手撸架构,Kafka 面试42问_vincent-CSDN博客
Docker手撸架构,Docker 面试25问_vincent-CSDN博客
Nginx手撸架构,Nginx 面试40问_vincent-CSDN博客
算法常用排序算法总结(1)-- 比较排序_vincent-CSDN博客_比较排序
常用排序算法总结(2)-- 非比较排序算法_vincent-CSDN博客_非比较排序的算法有
分布式事务分布式事务解决方案(总览)_vincent-CSDN博客
HTTP太厉害了,终于有人能把TCP/IP 协议讲的明明白白了_vincent-CSDN博客_tcp和ip

什么是微服务

微服务并没有一个官方的定义,想要直接描述微服务比较困难,我们可以通过对比传统WEB应用,来理解什么是微服务。

传统的WEB应用核心分为业务逻辑、适配器以及API或通过UI访问的WEB界面。业务逻辑定义业务流程、业务规则以及领域实体。适配器包括数据库访问组件、消息组件以及访问接口等。一个打车软件的架构图如下:

640?wx_fmt=jpeg

尽管也是遵循模块化开发,但最终它们会打包并部署为单体式应用。例如Java应用程序会被打包成WAR,部署在Tomcat或者Jetty上。

这种单体应用比较适合于小项目,优点是:

  • 开发简单直接,集中式管理

  • 基本不会重复开发

  • 功能都在本地,没有分布式的管理开销和调用开销

当然它的缺点也十分明显,特别对于互联网公司来说:

  • 开发效率低:所有的开发在一个项目改代码,递交代码相互等待,代码冲突不断

  • 代码维护难:代码功能耦合在一起,新人不知道何从下手

  • 部署不灵活:构建时间长,任何小修改必须重新构建整个项目,这个过程往往很长

  • 稳定性不高:一个微不足道的小问题,可以导致整个应用挂掉

  • 扩展性不够:无法满足高并发情况下的业务需求

所以,现在主流的设计一般会采用微服务架构。其思路不是开发一个巨大的单体式应用,而是将应用分解为小的、互相连接的微服务。一个微服务完成某个特定功能,比如乘客管理和下单管理等。每个微服务都有自己的业务逻辑和适配器。一些微服务还会提供API接口给其他微服务和应用客户端使用。

比如,前面描述的系统可被分解为:

640?

每个业务逻辑都被分解为一个微服务,微服务之间通过REST API通信。一些微服务也会向终端用户或客户端开发API接口。但通常情况下,这些客户端并不能直接访问后台微服务,而是通过API Gateway来传递请求。API Gateway一般负责服务路由、负载均衡、缓存、访问控制和鉴权等任务。

微服务与微服务架构

  • 微服务强调的是服务的大小,他关注的是以点,是具体解决某一个问题/提供落地对象服务的一个服务应用,
  • 狭义的看,可以看做IDEA中的一个个微服务工程.
  • 微服务架构为一种架构模式.他提倡将单一应用程序分成一组小的服务,服务之间互相协调,互相配合,为用户提供最终价值.
  • 每个服务运行在其独立的进程中,服务于服务间采用轻量级的通信机制互相协作(通常是Http 的Restful API) 每一个服务
  • 都围绕着具体业务进行构建,并且能够被独立的部署到生产环境,类生产环境中.另外,应当尽量避免统一的,集中式的服务管理机制,
  • 对具体的一个服务而言,应根据业务上下文,选择合适的语言,工具对其进行构建.

微服务架构优劣势

优势

  1. 服务的独立部署,每个服务都是独立的项目,不依赖于其他服务,耦合性低。
  2. 服务的快速启动,依赖的库和代码量减少。
  3. 更加适合敏捷开发,服务拆分可以快速按需发布。
  4. 职责专一,团队可以负责对应的业务线,服务的拆分有利于团队之间的分工。
  5. 按需扩容,当某个服务的访问量较大时,我们只需要将这个服务扩容即可。
  6. 代码的复用,每个服务都提供rest api,所有的基础服务都必须抽出来,很多的底层实现都可以以接口方式提供。

劣势:

  1. 分布式部署,调用的复杂性高:单体应用的时候,所有的模块之前的调用都是在本地进行的,在微服务中,每个模块都是独立部署的,通过http来进行通信,这当中会产生很多问题,比如网络问题、容错问题、调用关系等
  2. 独立的数据库,分布式事务的挑战:每个微服务都有自己的数据库,这就是所谓的去中心化的数据管理。这种模式的优点在于不同的服务,可以选中适合自身业务的数据,比如订单服务可以用MySql,评论服务可以用Mongodb、商品搜索服务可以用ElasticSearch.缺点就是事务的问题了,目前最理想的解决方案就是柔性事务中的最终一致性。
  3. 测试的难度提升:服务和服务之间通过接口来交互,当接口有改变的时候,对所有的调用方法都是有影响的,那工作量就太大了。
  4. 运维难度的提升:当业务增加时,服务也将越来越多,服务的部署、监控将变得非常复杂,这个时候对于运维的要求就高了。

SpringCloud 是什么

  • 基于SpringBoot提供了一套为微服务(microservices)解决方案,包括服务注册与发现,
  • 配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于NetFlix的开营组件做 高度抽象之外,还有一些选型中立了的开源组件.
  • 分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的结合体,俗称为微服务全家桶.

为什么选择SpringCloud 

  • Spring Cloud来源于Spring,质量、稳定性、持续性都可以得到保证

  • Spirng Cloud天然支持Spring Boot,更加便于业务落地。

  • Spring Cloud发展非常的快,从16年开始接触的时候相关组件版本为1.x,到现在将要发布2.x系列

  • Spring Cloud是Java领域最适合做微服务的框架。

  • 相比于其它框架,Spring Cloud对微服务周边环境的支持力度最大。

  • 对于中小企业来讲,使用门槛较低。

SpringBoot和SpringCloud的关系和区别

  1. SpringBoot专注于方便的开发单个个体微服务
  2. SpringCloud是关注于全局的微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来.
  3. 为各个微服务之间提供配置管理,服务发现,断路器,路由,微代理,事件总线,决策竞选,分布式会话等集成服务.
  4. SpringBoot可以离开SpringCloud单独使用,而SpringCloud离不开SpringBoot

SpringCloud整体架构

Spring cloud 的核心组件:

  • Spring Cloud Netflix:核心组件,可以对多个Netflix OSS开源套件进行整合,包括以下几个组件:
    • Eureka:服务治理组件,包含服务注册与发现
    • Hystrix:容错管理组件,实现了熔断器
    • Ribbon:客户端负载均衡的服务调用组件
    • Feign:基于Ribbon和Hystrix的声明式服务调用组件
    • Zuul:网关组件,提供智能路由、访问过滤等功能
    • Archaius:外部化配置组件
  • Spring Cloud Config:配置管理工具,实现应用配置的外部化存储,支持客户端配置信息刷新、加密/解密配置内容等。
  • Spring Cloud Bus:事件、消息总线,用于传播集群中的状态变化或事件,以及触发后续的处理
  • Spring Cloud Security:基于spring security的安全工具包,为我们的应用程序添加安全控制
  • Spring Cloud Consul : 封装了Consul操作,Consul是一个服务发现与配置工具(与Eureka作用类似),与Docker容器可以无缝集成
  • Spring Cloud Gateway:API网关组件,对请求提供路由及过滤功能
  • Spring Cloud Stream:轻量级事件驱动微服务框架,可以使用简单的声明式模型来发送及接收消息,主要实现为Apache Kafka及RabbitMQ。
  • Spring Cloud Task:用于快速构建短暂、有限数据处理任务的微服务框架,用于向应用中添加功能性和非功能性的特性
  • Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用组件,可以动态创建基于Spring MVC注解的接口实现用于服务调用,在Spring Cloud 2.0中已经取代Feign成为了一等公民
  • Spring Cloud Zookeeper:基于Apache Zookeeper的服务治理组件

SpringCloud 和 Dubbo 有哪些区别

首先,他们都是分布式管理框架。

Dubbo 是二进制传输,占用带宽会少一点。SpringCloud是http 传输,带宽会多一点,同时使用http协议一般会使用JSON报文,消耗会更大。

Dubbo 开发难度较大,所依赖的 jar 包有很多问题大型工程无法解决。SpringCloud 对第三方的继承可以一键式生成,天然集成。 SpringCloud 接口协议约定比较松散,需要强有力的行政措施来限制接口无序升级。

最大的区别: Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式。 严格来说,这两种方式各有优劣。虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适。

Eureka

Eureka工作原理

Eureka : 就是服务注册中心(可以是一个集群),对外暴露自己地址;

提供者 : 启动后向Eureka注册自己信息(地址,提供什么服务)

消费者 : 向Eureka 订阅服务,Eureka会将对应服务的服务列表发送给消费者,并且定期更新

心跳(续约): 提供者定期通过http方式向Eureka刷新自己的状态

服务注册

服务提供者在启动时,会向EurekaServer发起一次请求,将自己注册到Eureka注册中心中去

服务续约

在注册服务完成以后,服务提供者会维持一个心跳(每30s定时向EurekaServer 分发起请求)告诉EurekaServer "我还活着"

失效剔除

有时候,我们的服务提供方并不一定是正常下线,可能是内存溢出,网络故障等原因导致服务无法正常工作.EurekaServer会将这些失效的服务剔除服务列表.因此它会开启一个定时任务.每隔60秒会对失效的服务进行一次剔除

自我保护

当服务未按时进行心跳续约时,在生产环境下,因为网络原因,此时就把服务从服务列表中剔除并不妥当发,因为服务也有可能未宕机.Eureka就会把当前实例的注册信息保护起来,不允剔除.这种方式在生产环境下很有效,保证了大多数服务依然可用

Eureka组件

Eureka Server: 提供服务注册服务,各个节点启动后会在这里进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到.

Eureka Client: 是一个java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载复法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒),如果Eureka Server在多个心跳周期内没有接收到某 Eureka Server将会从服务注册表中把这个服务节点移除(默认90)
 

Eureka的保护模式

默认情况下,如果 EurekaServer在一定时间内没有接收到某个微服务实例的心, EurekaServer将会注销该实例(默认90秒)但是当网络分区故障发生时,微服务与 EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。 Eureka通过“自我保护模式来解决这个问题—当 EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式, Eureka Serverl就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服努)。当网络故障恢复后,该 Eureka Server节点会自动退出自我保护模式。

在自我保护模式中, Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该 Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死如赖活着

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让 Eureka集群更加的健壮在 Spring Cloud中,

可以使用 eureka, server enable-sef- preservation= false禁用自我保护模式

不推荐禁用建议要更改注销实例的时间: eureka instance. lease-expiration-duration-in-seconds

简述什么是CAP,并说明Eureka包含CAP中的哪些

分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。

Eureka 遵守:可用性(Availability)和分区容错性(Partition tolerance)

  • Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,神域的节点依然可以提供注册和查询服务
  • Eureka的客户端在向某个Eureka 注册或查询是如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查的信息可能不最新的不保证强一致性).

Eureka和Zookeeper都可以提供服务注册与发现的功能,请说说两个的区别

1. Eureka取CAP中的AP,注重可用性,Zookepper取CAP理论中的CP强调高的一致性:

  • ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的
  • Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的自我保护机制会导致
  • Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务
  • Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)
  • 当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)
  • Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪

2. ZooKeeper有Leader和Follower角色,Eureka各个节点平等

3. ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题

4. Eureka本质上是一个工程,而ZooKeeper只是一个进程

Eureka注册服务慢

原因:由于心跳机制,默认30s,只有当实例、服务器、客户端的本地缓存数据一致时才能被其他服务发现(3次心跳),可以修改心跳时间间隔(eureka.instance.leaseRenewal-IntervalInSeconds)

已经停止的微服务节点注销慢

关闭自我保护、修改清理间隔时间(修改配置文件)

如何主动关闭服务节点

     DiscoveryManager.getInstance().shutdownComponent();

注:如果集群中有一个eureka server宕机。不会有类似选举的过程,客户端请求会自动切换到新的eureka server节点,维持强一致性和高可用性,对网络分区等问题采用自我保护机制,保留信息不过期。

Hystrix

什么是灾难性雪崩效应?

造成灾难性雪崩效应的原因,可以简单归结为下述三种:

  • 服务提供者不可用。如:硬件故障、程序BUG、缓存击穿、并发请求量过大等。
  • 重试加大流量。如:用户重试、代码重试逻辑等。
  • 服务调用者不可用。如:同步请求阻塞造成的资源耗尽等。

  雪崩效应最终的结果就是:服务链条中的某一个服务不可用,导致一系列的服务不可用,最终造成服务逻辑崩溃。这种问题造成的后果,往往是无法预料的。

Hystrix 作用

  • 服务熔断:Hystrix 会记录各个服务的请求信息,通过 成功、失败、拒绝、超时 等统计信息判断是否打开断路器,将某个服务的请求进行熔断。一段时间后切换到半开路状态,如果后面的请求正常则关闭断路器,否则继续打开断路器。

  • 服务降级:服务降级是请求失败时的后备方法,故障时执行降级逻辑。

  • 资源隔离:Hystrix 通过线程池实现资源的隔离,确保对某一服务的调用在出现故障时不会对其他服务造成影响。

  • 限流:避免突发量请求,导致服务宕机。

  • 运维监控:实现了服务监控、报警和运维控制。Hystrix Dashboard和Turbine可以配合Hystrix完成这些功能。

服务降级

当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。

    /**
     * 服务降级处理。
     * 当前方法远程调用service服务的时候,如果service服务出现了任何错误(超时,异常等)
     * 不会将异常抛到客户端,而是使用本地的一个fallback(错误返回)方法来返回一个托底数据。
     * 避免客户端看到错误页面。
     * 使用注解来描述当前方法的服务降级逻辑。
     * @HystrixCommand - 开启Hystrix命令的注解。代表当前方法如果出现服务调用问题,使用Hystrix逻辑来处理。
     *  重要属性 - fallbackMethod
     *      错误返回方法名。如果当前方法调用服务,远程服务出现问题的时候,调用本地的哪个方法得到托底数据。
     *      Hystrix会调用fallbackMethod指定的方法,获取结果,并返回给客户端。
     * @return
     */
    @HystrixCommand(fallbackMethod="downgradeFallback")
    public List<Map<String, Object>> test() {
    }

     /**
     * fallback方法。本地定义的。用来处理远程服务调用错误时,返回的基础数据。
     */
    private List<Map<String, Object>> downgradeFallback(){
    }

请求合并

  请求合并是指,在一定时间内,收集一定量的同类型请求,合并请求需求后,一次性访问服务提供者,得到批量结果。这种方式可以减少服务消费者和服务提供者之间的通讯次数,提升应用执行效率。

未使用请求合并:

            

使用请求合并:         

什么情况下使用请求合并:

  在微服务架构中,我们将一个项目拆分成很多个独立的模块,这些独立的模块通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要来了解Hystrix的请求合并。

  通常来说,服务链条超出4个,不推荐使用请求合并。因为请求合并有等待时间。

请求合并的缺点:

  设置请求合并之后,本来一个请求可能5ms就搞定了,但是现在必须再等10ms看看还有没有其他的请求一起的,这样一个请求的耗时就从5ms增加到15ms了,不过,如果我们要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场景

@HystrixCollapser注解介绍:此注解描述的方法,返回值类型必须是java.util.concurrent.Future类型的。代表方法为异步方法

@HystrixCollapser注解的属性:

  batchMethod - 请求合并方法名。

  scope - 请求合并方式。可选值有REQUEST和GLOBAL。REQUEST代表在一个request请求生命周期内的多次远程服务调用请求需要合并处理,此为默认值。GLOBAL代表所有request线程内的多次远程服务调用请求需要合并处理。

  timerDelayInMilliseconds - 多少时间间隔内的请求进行合并处理,默认值为10ms。建议设置时间间隔短一些,如果单位时间并发量不大,并没有请求合并的必要。

  maxRequestsInBatch - 设置合并请求的最大极值,也就是timerDelayInMilliseconds时间内,最多合并多少个请求。默认值是Integer.MAX_VALUE。

@Service
public class HystrixService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * 需要合并请求的方法。
     * 这种方法的返回结果一定是Future类型的。
     * 这种方法的处理逻辑都是异步的。
     * 是application client在一定时间内收集客户端请求,或收集一定量的客户端请求,一次性发给application service。
     * application service返回的结果,application client会进行二次处理,封装为future对象并返回
     * future对象需要通过get方法获取最终的结果。 get方法是由控制器调用的。所以控制器调用service的过程是一个异步处理的过程。
     * 合并请求的方法需要使用@HystrixCollapser注解描述。
     * batchMethod - 合并请求后,使用的方法是什么。如果当前方法有参数,合并请求后的方法参数是当前方法参数的集合,如 int id >> int[] ids。
     * scope - 合并请求的请求作用域。可选值有global和request。
     *     global代表所有的请求线程都可以等待可合并。 常用,所有浏览器或者请求源(Postman、curl等)调用的请求
     *     request代表一个请求线程中的多次远程服务调用可合并
     * collapserProperties - 细致配置。就是配置合并请求的特性。如等待多久,如可合并请求的数量。
     *     属性的类型是@HystrixProperty类型数组,可配置的属性值可以直接通过字符串或常量类定义。
     *     timerDelayInMilliseconds - 等待时长
     *     maxRequestsInBatch - 可合并的请求最大数量。
     * 
     * 方法处理逻辑不需要实现,直接返回null即可。
     * 合并请求一定是可合并的。也就是同类型请求。同URL的请求。
     * @param id
     * @return
     */
    @HystrixCollapser(batchMethod = "mergeRequest", 
            scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,  
            collapserProperties = {  
            // 请求时间间隔在20ms之内的请求会被合并为一个请求,默认为10ms
            @HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
            // 设置触发批处理执行之前,在批处理中允许的最大请求数。
            @HystrixProperty(name = "maxRequestsInBatch", value = "200"),  
    })  
    public Future<Map<String, Object>> testMergeRequest(Long id){
        return null;
    }
    
    /**
     * 批量处理方法。就是合并请求后真实调用远程服务的方法。
     * 必须使用@HystrixCommand注解描述,代表当前方法是一个Hystrix管理的服务容错方法。
     * 是用于处理请求合并的方法。
     * @param ids
     * @return
     */
    @HystrixCommand
    public List<Map<String, Object>> mergeRequest(List<Long> ids){
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/testMerge?");
        for(int i = 0; i < ids.size(); i++){
            Long id = ids.get(i);
            if(i != 0){
                sb.append("&");
            }
            sb.append("ids=").append(id);
        }
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }
}

熔断

  当一定时间内,异常请求比例(请求超时、网络故障、服务异常等)达到阀值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快速返回托底数据,保证服务链的完整。

  熔断有自动恢复机制,如:当熔断器启动后,每隔5秒,尝试将新的请求发送给服务提供者,如果服务可正常执行并返回结果,则关闭熔断器,服务恢复。如果仍旧调用失败,则继续返回托底数据,熔断器持续开启状态。   

/**
     * 熔断机制
     * 相当于一个强化的服务降级。 服务降级是只要远程服务出错,立刻返回fallback结果。
     * 熔断是收集一定时间内的错误比例,如果达到一定的错误率。则启动熔断,返回fallback结果。
     * 间隔一定时间会将请求再次发送给application service进行重试。如果重试成功,熔断关闭。
     * 如果重试失败,熔断持续开启,并返回fallback数据。
     * @HystrixCommand 描述方法。
     *  fallbackMethod - fallback方法名
     *  commandProperties - 具体的熔断标准。类型是HystrixProperty数组。
     *   可以通过字符串或常亮类配置。
     *   CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD - 错误数量。在10毫秒内,出现多少次远程服务调用错误,则开启熔断。
     *       默认20个。 10毫秒内有20个错误请求则开启熔断。
     *   CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE - 错误比例。在10毫秒内,远程服务调用错误比例达标则开启熔断。
     *   CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS - 熔断开启后,间隔多少毫秒重试远程服务调用。默认5000毫秒。
     * @return
     */
    @HystrixCommand(fallbackMethod = "breakerFallback",
            commandProperties = {
              // 默认20个;10ms内请求数大于20个时就启动熔断器,当请求符合熔断条件时将触发getFallback()。
              @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, 
                          value="10"),
              // 请求错误率大于50%时就熔断,然后for循环发起请求,当请求符合熔断条件时将触发getFallback()。
              @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, 
                          value="50"),
              // 默认5秒;熔断多少秒后去尝试请求
              @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, 
                          value="5000")}
    )

注解属性描述:

CIRCUIT_BREAKER_ENABLED
"circuitBreaker.enabled";
# 是否开启熔断策略。默认值为true。

CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
"circuitBreaker.requestVolumeThreshold";
# 10ms内,请求并发数超出则触发熔断策略。默认值为20。

CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
"circuitBreaker.sleepWindowInMilliseconds";
# 当熔断策略开启后,延迟多久尝试再次请求远程服务。默认为5秒。

CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
"circuitBreaker.errorThresholdPercentage";
# 10ms内,出现错误的请求百分比达到限制,则触发熔断策略。默认为50%。

CIRCUIT_BREAKER_FORCE_OPEN
"circuitBreaker.forceOpen";
# 是否强制开启熔断策略。即所有请求都返回fallback托底数据。默认为false。

CIRCUIT_BREAKER_FORCE_CLOSED
"circuitBreaker.forceClosed";
# 是否强制关闭熔断策略。即所有请求一定调用远程服务。默认为false。

隔离  

  所谓隔离,就是当服务发生问题时,使用技术手段隔离请求,保证服务调用链的完整。隔离分为线程池隔离和信号量隔离两种实现方式。

线程池隔离

  所谓线程池隔离,就是将并发请求量大的部分服务使用独立的线程池处理,避免因个别服务并发过高导致整体应用宕机。

  线程池隔离优点:

  • 使用线程池隔离可以完全隔离依赖的服务,请求线程可以快速放回。
  • 当线程池出现问题时,线程池是完全隔离状态的,是独立的,不会影响到其他服务的正常执行。
  • 当崩溃的服务恢复时,线程池可以快速清理并恢复,不需要相对漫长的恢复等待。
  • 独立的线程池也提供了并发处理能力。

  线程池隔离缺点:
  线程池隔离机制,会导致服务硬件计算开销加大(CPU计算、调度等),每个命令的执行都涉及到排队、调度、上下文切换等,这些命令都是在一个单独的线程上运行的。

                

                

   

                

                

  线程池隔离的实现方式同样是使用@HystrixCommand注解。相关注解配置属性如下:

  • groupKey - 分组命名,在application client中会为每个application service服务设置一个分组,同一个分组下的服务调用使用同一个线程池。默认值为this.getClass().getSimpleName();
  • commandKey - Hystrix中的命令命名,默认为当前方法的方法名。可省略。用于标记当前要触发的远程服务是什么。
  • threadPoolKey - 线程池命名。要求一个应用中全局唯一。多个方法使用同一个线程池命名,代表使用同一个线程池。默认值是groupKey数据。
  • threadPoolProperties - 用于为线程池设置的参数。其类型为HystrixProperty数组。常用线程池设置参数有:
  • coreSize - 线程池最大并发数,建议设置标准为:requests per second at peak when healthy * 99th percentile latency in second + some breathing room。 即每秒最大支持请求数*(99%平均响应时间 + 一定量的缓冲时间(99%平均响应时间的10%-20%))。如:每秒可以处理请求数为1000,99%的响应时间为60ms,自定义提供缓冲时间为60*0.2=12ms,那么结果是 1000*(0.060+0.012) = 72。
  • maxQueueSize - BlockingQueue的最大长度,默认值为-1,即不限制。如果设置为正数,等待队列将从同步队列SynchronousQueue转换为阻塞队列LinkedBlockingQueue。
  • queueSizeRejectionThreshold - 设置拒绝请求的临界值。默认值为5。此属性是配合阻塞队列使用的,也就是不适用maxQueueSize=-1(为-1的时候此值无效)的情况。是用于设置阻塞队列限制的,如果超出限制,则拒绝请求。此参数的意义就是在服务启动后,可以通过Hystrix的API调用config API动态修改,而不用用重启服务,不常用。
  • keepAliveTimeMinutes - 线程存活时间,单位是分钟。默认值为1。
  • execution.isolation.thread.timeoutInMilliseconds - 超时时间,默认为1000ms。当请求超时自动中断,返回fallback,避免服务长期阻塞。
  • execution.isolation.thread.interruptOnTimeout - 是否开启超时中断。默认为TRUE。和上一个属性配合使用。
 /**
     * 如果使用了@HystrixCommand注解,则Hystrix自动创建独立的线程池。
     * groupKey和threadPoolKey默认值是当前服务方法所在类型的simpleName
     * 
     * 所有的fallback方法,都执行在一个HystrixTimer线程池上。
     * 这个线程池是Hystrix提供的一个,专门处理fallback逻辑的线程池。
     * 
     * 线程池隔离实现
     * 线程池隔离,就是为某一些服务,独立划分线程池。让这些服务逻辑在独立的线程池中运行。
     * 不使用tomcat提供的默认线程池。
     * 线程池隔离也有熔断能力。如果线程池不能处理更多的请求的时候,会触发熔断,返回fallback数据。
     * groupKey - 分组名称,就是为服务划分分组。如果不配置,默认使用threadPoolKey作为组名。
     * commandKey - 命令名称,默认值就是当前业务方法的方法名。
     * threadPoolKey - 线程池命名,真实线程池命名的一部分。Hystrix在创建线程池并命名的时候,会提供完整命名。默认使用gourpKey命名
     *  如果多个方法使用的threadPoolKey是同名的,则使用同一个线程池。
     * threadPoolProperties - 为Hystrix创建的线程池做配置。可以使用字符串或HystrixPropertiesManager中的常量指定。
     *  常用线程池配置:
     *      coreSize - 核心线程数。最大并发数。1000*(99%平均响应时间 + 适当的延迟时间)
     *      maxQueueSize - 阻塞队列长度。如果是-1这是同步队列。如果是正数这是LinkedBlockingQueue。如果线程池最大并发数不足,
     *          提供多少的阻塞等待。
     *      keepAliveTimeMinutes - 心跳时间,超时时长。单位是分钟。
     *      queueSizeRejectionThreshold - 拒绝临界值,当最大并发不足的时候,超过多少个阻塞请求,后续请求拒绝。
     */
    @HystrixCommand(groupKey="test-thread-quarantine", 
        commandKey = "testThreadQuarantine",
        threadPoolKey="test-thread-quarantine", 
        threadPoolProperties = {
            @HystrixProperty(name="coreSize", value="30"),
            @HystrixProperty(name="maxQueueSize", value="100"),
            @HystrixProperty(name="keepAliveTimeMinutes", value="2"),
            @HystrixProperty(name="queueSizeRejectionThreshold", value="15")
        },
        fallbackMethod = "threadQuarantineFallback")

关于线程池:

  • 对于所有请求,都交由tomcat容器的线程池处理,是一个以http-nio开头的的线程池;
  • 开启了线程池隔离后,tomcat容器默认的线程池会将请求转交给threadPoolKey定义名称的线程池,处理结束后,由定义的线程池进行返回,无需还回tomcat容器默认的线程池。线程池默认为当前方法名;
  • 所有的fallback都单独由Hystrix创建的一个线程池处理。

 信号量隔离

  所谓信号量隔离,就是设置一个并发处理的最大极值。当并发请求数超过极值时,通过fallback返回托底数据,保证服务完整性。

              

  信号量隔离同样通过@HystrixCommand注解配置,常用注解属性有:

  • commandProperty - 配置信号量隔离具体数据。属性类型为HystrixProperty数组,常用配置内容如下:
  • execution.isolation.strategy - 设置隔离方式,默认为线程池隔离。可选值只有THREAD和SEMAPHORE。
  • execution.isolation.semaphore.maxConcurrentRequests - 最大信号量并发数,默认为10。
 /**
     * 信号量隔离实现
     * 不会使用Hystrix管理的线程池处理请求。使用容器(Tomcat)的线程处理请求逻辑。
     * 不涉及线程切换,资源调度,上下文的转换等,相对效率高。
     * 信号量隔离也会启动熔断机制。如果请求并发数超标,则触发熔断,返回fallback数据。
     * commandProperties - 命令配置,HystrixPropertiesManager中的常量或字符串来配置。
     *     execution.isolation.strategy - 隔离的种类,可选值只有THREAD(线程池隔离)和SEMAPHORE(信号量隔离)。
     *      默认是THREAD线程池隔离。
     *      设置信号量隔离后,线程池相关配置失效。
     *  execution.isolation.semaphore.maxConcurrentRequests - 信号量最大并发数。默认值是10。常见配置500~1000。
     *      如果并发请求超过配置,其他请求进入fallback逻辑。
     */
    @HystrixCommand(fallbackMethod="semaphoreQuarantineFallback",
            commandProperties={
              @HystrixProperty(
                      name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, 
                      value="SEMAPHORE"), // 信号量隔离
              @HystrixProperty(
                      name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, 
                      value="100") // 信号量最大并发数
    })

线程池隔离和信号量隔离的对比           

线程池隔离和信号量隔离的选择

  • 线程池隔离:请求并发大,耗时较长(一般都是计算大,服务链长或访问数据库)时使用线程池隔离。可以尽可能保证外部容器(如Tomcat)线程池可用,不会因为服务调用的原因导致请求阻塞等待。
  • 信号量隔离:请求并发大,耗时短计算小,服务链段或访问缓存)时使用信号量隔离。因为这类服务的响应快,不会占用外部容器(如Tomcat)线程池太长时间,减少线程的切换,可以避免不必要的开销,提高服务处理效率。

 Feign

FeignClient接口,不能使用@GettingMapping 之类的组合注解

@FeignClient("microservice-provider-user")
public interface UserFeignClient {
 @RequestMapping(value = "/simple/{id}", method = RequestMethod.GET)
 public User findById(@PathVariable("id") Long id);
 ...
}

FeignClient接口中,如果使用到@PathVariable ,必须指定其value

@PathVariable("id") 中的”id”,不能省略,必须指定

FeignClient("microservice-provider-user")
public interface UserFeignClient {
 @RequestMapping(value = "/simple/{id}", method = RequestMethod.GET)
 public User findById(@PathVariable("id") Long id);
 ...
}

FeignClient多参数的构造

直接使用复杂对象:该请求不会成功,只要参数是复杂对象,即使指定了是GET方法,feign依然会以POST方法进行发送请求。

@FeignClient("microservice-provider-user")

public interface UserFeignClient {

 @RequestMapping(value = "/query-by", method = RequestMethod.GET)

 public User queryBy(User user);

 ...

}

正确的写法

// 写法一
@FeignClient("microservice-provider-user")
public interface UserFeignClient {
 @RequestMapping(value = "/query-by", method = RequestMethod.GET)
 public User queryBy(@RequestParam("id")Long id, @RequestParam("username")String username);
}

//写法二
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
 @RequestMapping(value = "/query-by", method = RequestMethod.GET)
 public List<User> queryBy(@RequestParam Map<String, Object> param);
}

Feign如果想要使用Hystrix Stream,需要做一些额外操作

我们知道Feign本身就是支持Hystrix的,可以直接使用@FeignClient(value = "microservice-provider-user", fallback = XXX.class) 来指定fallback的类,这个fallback类集成@FeignClient所标注的接口即可。

但是假设我们需要使用Hystrix Stream进行监控,默认情况下,访问http://IP:PORT/hystrix.stream 是个404。如何为Feign增加Hystrix Stream支持呢?

第一步:添加依赖,示例:、

<!-- 整合hystrix,其实feign中自带了hystrix,引入该依赖主要是为了使用其中的hystrix-metrics-event-stream,用于dashboard -->
<dependency> 
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

第二步:在启动类上添加@EnableCircuitBreaker 注解,示例:

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
@EnableCircuitBreaker
public class MovieFeignHystrixApplication {

 public static void main(String[] args) {

 SpringApplication.run(MovieFeignHystrixApplication.class, args);

 }

}

自定义单个Feign配置

Feign的@Configuration 注解的类不能与@ComponentScan 的包重叠。如果包重叠,将会导致所有的Feign Client都会使用该配置。

首次请求失败

造成该问题的原因

Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(因为Spring的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了。知道原因后,我们来总结一下解决放你。

方法1:

该配置是让Hystrix的超时时间改为5秒

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000

方法2:

该配置,用于禁用Hystrix的超时时间

hystrix.command.default.execution.timeout.enabled: false

方法3:
禁用feign的hystrix。该做法除非一些特殊场景,不推荐使用

feign.hystrix.enabled: false

@FeignClient 的属性注意点

1. serviceId属性已经失效,尽量使用name属性。例如:

@FeignClient(serviceId = "microservice-provider-user")

这么写是不推荐的,应写为:

@FeignClient(name = "microservice-provider-user")

2. 在使用url属性时,在老版本的Spring Cloud中,不需要提供name属性,但是在新版本(例如Brixton、Camden)@FeignClient必须提供name属性,并且name、url属性支持占位符。例如:

@FeignClient(name = "${feign.name}", url = "${feign.url}")

Feign添加自定义Header

Spring Cloud Feign添加自定义Header_chuihou6312的博客-CSDN博客

方案一:方法上的@RequestMapping注解添加headers信息

@FeignClient(name = "server",url = "127.0.0.1:8080")
public interface FeignTest {
    @RequestMapping(value = "/test",headers = {"app=test-app","token=${test-app.token}"})
    String test();
}

方案二:接口上的@RequestMapping注解添加headers信息

@FeignClient(name = "server",url = "127.0.0.1:8080")
@RequestMapping(value = "/",headers = {"app=test-app","token=${test-app.token}"})
public interface FeignTest {
    @RequestMapping(value = "/test")
    String test();
}

方案三:使用@Headers注解添加headers信息

@FeignClient(name = "server",url = "127.0.0.1:8080")
@Headers({"app: test-app","token: ${test-app.token}"})
public interface FeignTest {
    @RequestMapping(value = "/test")
    String test();
}

方案四:自定义RequestInterceptor添加headers信息

@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Value("${test-app.token}")
    private String token;
    
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header("app","test-app");//静态
        requestTemplate.header("token",token);//读配置
    }
}=

方案五:自定义RequestInterceptor实现添加动态数据到header

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
String signedMsg = getSignedMsg(reqJson); // 计算签名字符串
Map<String, String> reqMap = new HashMap<>();
reqMap.put("content-type", "application/json");//常量字段
reqMap.put("accessKey", accessKey);//常量字段
reqMap.put("signedMsg", signedMsg);//动态计算/获取字段
request.setAttribute("customizedRequestHeader", reqMap);



@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 设置自定义header
        // 设置request中的attribute到header以便转发到Feign调用的服务
        Enumeration<String> reqAttrbuteNames = request.getAttributeNames();
        if (reqAttrbuteNames != null) {
            while (reqAttrbuteNames.hasMoreElements()) {
                String attrName = reqAttrbuteNames.nextElement();
                if (!"customizedRequestHeader".equalsIgnoreCase(attrName)) {
                    continue;
                }
                Map<String,String> requestHeaderMap = (Map)request.getAttribute(attrName);
                for (Map.Entry<String, String> entry : requestHeaderMap.entrySet()) {
                    requestTemplate.header(entry.getKey(), entry.getValue());
                }
                break;
            }
        }
    }
}

Zuul

Zuul网关的作用

  1. 统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
  2. 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
  3. 动态路由:动态的将请求路由到不同的后端集群中。
  4. 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。
     

网关访问方式

     通过zuul访问服务的,URL地址默认格式为:http://zuulHostIp:port/要访问的服务名称/服务中的URL

  服务名称:properties配置文件中的spring.application.name。

  服务的URL:就是对应的服务对外提供的URL路径监听。
 

Zuul网关过滤器

     Zuul中提供了过滤器定义,可以用来过滤代理请求,提供额外功能逻辑。如:权限验证,日志记录等。

  Zuul提供的过滤器是一个父类。父类是ZuulFilter。通过父类中定义的抽象方法filterType,来决定当前的Filter种类是什么。有前置过滤、路由后过滤、后置过滤、异常过滤。

  • 前置过滤:是请求进入Zuul之后,立刻执行的过滤逻辑。
  • 路由后过滤:是请求进入Zuul之后,并Zuul实现了请求路由后执行的过滤逻辑,路由后过滤,是在远程服务调用之前过滤的逻辑。
  • 后置过滤:远程服务调用结束后执行的过滤逻辑。
  • 异常过滤:是任意一个过滤器发生异常或远程服务调用无结果反馈的时候执行的过滤逻辑。无结果反馈,就是远程服务调用超时。
     

过滤器实现方式

继承父类ZuulFilter。在父类中提供了4个抽象方法,分别是:filterType, filterOrder, shouldFilter, run。其功能分别是:

filterType:方法返回字符串数据,代表当前过滤器的类型。可选值有-pre, route, post, error。

  • pre - 前置过滤器,在请求被路由前执行,通常用于处理身份认证,日志记录等;
  • route - 在路由执行后,服务调用前被调用;
  • error - 任意一个filter发生异常的时候执行或远程服务调用没有反馈的时候执行(超时),通常用于处理异常;
  • post - 在route或error执行后被调用,一般用于收集服务信息,统计服务性能指标等,也可以对response结果做特殊处理。
     

filterOrder:返回int数据,用于为同filterType的多个过滤器定制执行顺序,返回值越小,执行顺序越优先。
shouldFilter:返回boolean数据,代表当前filter是否生效。
run:具体的过滤执行逻辑。如pre类型的过滤器,可以通过对请求的验证来决定是否将请求路由到服务上;如post类型的过滤器,可以对服务响应结果做加工处理(如为每个响应增加footer数据)。

过滤器的生命周期

 Zuul中的服务降级处理

 在Edgware版本之前,Zuul提供了接口ZuulFallbackProvider用于实现fallback处理。从Edgware版本开始,Zuul提供了ZuulFallbackProvider的子接口FallbackProvider来提供fallback处理。
  Zuul的fallback容错处理逻辑,只针对timeout异常处理,当请求被Zuul路由后,只要服务有返回(包括异常),都不会触发Zuul的fallback容错逻辑。
 

@Component
public class TestFallBbackProvider implements FallbackProvider {
 
    /**
     * return - 返回fallback处理哪一个服务。返回的是服务的名称
     * 推荐 - 为指定的服务定义特性化的fallback逻辑。
     * 推荐 - 提供一个处理所有服务的fallback逻辑。
     * 好处 - 服务某个服务发生超时,那么指定的fallback逻辑执行。如果有新服务上线,未提供fallback逻辑,有一个通用的。
     */
    @Override
    public String getRoute() {
        return "eureka-application-service";
    }
 
    /**
     * fallback逻辑。在早期版本中使用。
     * Edgware版本之后,ZuulFallbackProvider接口过期,提供了新的子接口FallbackProvider
     * 子接口中提供了方法ClientHttpResponse fallbackResponse(Throwable cause)。
     * 优先调用子接口新定义的fallback处理逻辑。
     */
    @Override
    public ClientHttpResponse fallbackResponse() {
        System.out.println("ClientHttpResponse fallbackResponse()");
        
        List<Map<String, Object>> result = new ArrayList<>();
        Map<String, Object> data = new HashMap<>();
        data.put("message", "服务正忙,请稍后重试");
        result.add(data);
        
        ObjectMapper mapper = new ObjectMapper();
        
        String msg = "";
        try {
            msg = mapper.writeValueAsString(result);
        } catch (JsonProcessingException e) {
            msg = "";
        }
        
        return this.executeFallback(HttpStatus.OK, msg, 
                "application", "json", "utf-8");
    }
 
    /**
     * fallback逻辑。优先调用。可以根据异常类型动态决定处理方式。
     */
    @Override
    public ClientHttpResponse fallbackResponse(Throwable cause) {
        System.out.println("ClientHttpResponse fallbackResponse(Throwable cause)");
        if(cause instanceof NullPointerException){
            
            List<Map<String, Object>> result = new ArrayList<>();
            Map<String, Object> data = new HashMap<>();
            data.put("message", "网关超时,请稍后重试");
            result.add(data);
            
            ObjectMapper mapper = new ObjectMapper();
            
            String msg = "";
            try {
                msg = mapper.writeValueAsString(result);
            } catch (JsonProcessingException e) {
                msg = "";
            }
            
            return this.executeFallback(HttpStatus.GATEWAY_TIMEOUT, 
                    msg, "application", "json", "utf-8");
        }else{
            return this.fallbackResponse();
        }
    }
    
    /**
     * 具体处理过程。
     * @param status 容错处理后的返回状态,如200正常GET请求结果,201正常POST请求结果,404资源找不到错误等。
     *  使用spring提供的枚举类型对象实现。HttpStatus
     * @param contentMsg 自定义的响应内容。就是反馈给客户端的数据。
     * @param mediaType 响应类型,是响应的主类型, 如: application、text、media。
     * @param subMediaType 响应类型,是响应的子类型, 如: json、stream、html、plain、jpeg、png等。
     * @param charsetName 响应结果的字符集。这里只传递字符集名称,如: utf-8、gbk、big5等。
     * @return ClientHttpResponse 就是响应的具体内容。
     *  相当于一个HttpServletResponse。
     */
    private final ClientHttpResponse executeFallback(final HttpStatus status, 
            String contentMsg, String mediaType, String subMediaType, String charsetName) {
        return new ClientHttpResponse() {
 
            /**
             * 设置响应的头信息
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders header = new HttpHeaders();
                MediaType mt = new MediaType(mediaType, subMediaType, Charset.forName(charsetName));
                header.setContentType(mt);
                return header;
            }
 
            /**
             * 设置响应体
             * zuul会将本方法返回的输入流数据读取,并通过HttpServletResponse的输出流输出到客户端。
             */
            @Override
            public InputStream getBody() throws IOException {
                String content = contentMsg;
                return new ByteArrayInputStream(content.getBytes());
            }
 
            /**
             * ClientHttpResponse的fallback的状态码 返回String
             */
            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }
 
            /**
             * ClientHttpResponse的fallback的状态码 返回HttpStatus
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return status;
            }
 
            /**
             * ClientHttpResponse的fallback的状态码 返回int
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }
 
            /**
             * 回收资源方法
             * 用于回收当前fallback逻辑开启的资源对象的。
             * 不要关闭getBody方法返回的那个输入流对象。
             */
            @Override
            public void close() {
            }
        };
    }
}

Zuul网关的限流保护

  Zuul网关组件也提供了限流保护。当请求并发达到阀值,自动触发限流保护,返回错误结果。只要提供error错误处理机制即可。

  Zuul的限流保护需要额外依赖spring-cloud-zuul-ratelimit组件

# 开启限流保护
zuul.ratelimit.enabled=true
# 60s内请求超过3次,服务端就抛出异常,60s后可以恢复正常请求
zuul.ratelimit.default-policy.limit=3
zuul.ratelimit.default-policy.refresh-interval=60
# 针对IP进行限流,不影响其他IP
zuul.ratelimit.default-policy.type=origin

Zuul网关性能调优

网关的两层超时调优
 

从上图中可以看出。整体请求逻辑还是比较复杂的,在没有zuul网关的情况下,app client请求app service的时候,也有请求超时的可能。那么当增加了zuul网关的时候,请求超时的可能就更明显了。

当请求通过zuul网关路由到服务,并等待服务返回响应,这个过程中zuul也有超时控制。zuul的底层使用的是Hystrix+ribbon来实现请求路由。结构如下:

使用Zuul的spring cloud微服务结构图:

 zuul中的Hystrix内部使用线程池隔离机制提供请求路由实现,其默认的超时时长为1000毫秒。ribbon底层默认超时时长为5000毫秒。如果Hystrix超时,直接返回超时异常。如果ribbon超时,同时Hystrix未超时,ribbon会自动进行服务集群轮询重试,直到Hystrix超时为止。如果Hystrix超时时长小于ribbon超时时长,ribbon不会进行服务集群轮询重试。

  那么在zuul中可配置的超时时长就有两个位置:Hystrix和ribbon。具体配置如下:

# 开启zuul网关重试
zuul.retryable=true
# hystrix超时时间设置
# 线程池隔离,默认超时时间1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=8000
 
# ribbon超时时间设置:建议设置比Hystrix小
# 请求连接的超时时间: 默认5000ms
ribbon.ConnectTimeout=5000
# 请求处理的超时时间: 默认5000ms
ribbon.ReadTimeout=5000
# 重试次数:MaxAutoRetries表示访问服务集群下原节点(同路径访问);MaxAutoRetriesNextServer表示访问服务集群下其余节点(换台服务器)
ribbon.MaxAutoRetries=1
ribbon.MaxAutoRetriesNextServer=1
# 开启重试
ribbon.OkToRetryOnAllOperations=true


  Spring-cloud中的zuul网关重试机制是使用spring-retry实现的。工程必须依赖下述资源:

<dependency>
  <groupId>org.springframework.retry</groupId>
  <artifactId>spring-retry</artifactId>
</dependency>


 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐