SpringCloud 微服务笔记

微服务架构概述

单体架构

一个归档包(war格式)包含所有功能的应用程序,通常称为单体应用。

存在的问题:

  • 复杂性高
  • 技术债务(不坏不修)
  • 部署频率低
  • 可靠性差
  • 扩展能力受限,不同模块可能需要的配置不一样,有的需要CPU好一点的,有的需要存储大一点的,不能根据业务模块的需要进行伸缩
  • 阻碍技术创新,单体应用往往使用统一的技术平台或者方案解决所有的问题

微服务

微服务架构风格:将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制,可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,不同服务可以采用不同的语言开发,可以使用不同的数据存储技术。

特性

  • 每个微服务可以独立运行在自己的进程中
  • 每个服务为独立的业务开发
  • 微服务通过类似于RESTful API等一些轻量级的通信机制进行通信
  • 全自动的部署机制

优点

  • 易于开发和维护
  • 启动速度快
  • 局部修改易部署
  • 技术栈并不受限
  • 按需伸缩

缺点

  • 运维要求高,因为会有几十甚至上百个服务协作运行
  • 分布式固有的复杂性
  • 接口调整成本高
  • 重复劳动

微服务设计原则

  • 单一职责原则(SOLID原则之一)image-20191213161449114

  • 服务自治原则。即每个微服务应具备独立的业务能力、依赖与运行环境。

  • 轻量级通信机制。常用的有REST、AMQP、STOMP、MQTT等。
  • 微服务粒度。这个比较难以控制,一般通过领域驱动设计(Domain Driven Design,DDD)中的限定上下文可作为划分微服务边界、确定微服务粒度。

微服务架构技术选型

主流的解决方案是 SpringCloud、Dubbo

具体的框架对比:Dubbo VS SpringCloud

微服务开发框架——Spring Cloud

Spring Cloud 简介

  • 并不是云计算解决方案,是在Spring Boot基础上用于快速构建分布式系统的通用模式的工具集

  • 使用Spring Cloud开发的程序非常适合在Docker或者PaaS上部署,所以又叫云原生应用,云原生可理解为面向云环境的软件架构

  • 云原生架构的方法论和最佳实践:12-factor Apps

  • Tips: 云服务分为三类,分别是 IaaS(基础设施服务)、PaaS(平台服务)、SaaS(软件服务)

    传送门:IaaS,PaaS,SaaS 的区别

Spring Cloud 和 Spring Boot 的联系

首先,知乎上对这个有比较好的回答:Spring Cloud & Spring Boot

总结一下说的比较好的:

  • spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
  • spring boot 的优点是可以快速启动,快速构建应用程序,而不需要太多的配置文件。
  • spring cloud 是分布式开发的解决方案,基于spring boot,在spring boot做较少的配置,便可成为 spring cloud 中的一个微服务。
  • 最基础的都是spring,然后在这个基础上spring boot做了一些自动配置和实现,然后又在spring boot的基础上加入了分布式负载均衡等功能,这才有了spring cloud。
  • Spring cloud是微服务开发套件。springboot=spring+springmvc,springcloud=springboot+ribbon+注册中心+熔断器+…等一系列组件。

Spring Cloud 特点

  • 约定优于配置
  • 隐藏了组件复杂性,并且提供声明式的配置方式
  • 轻量级的组件,如Eureka、Zuul等
  • 解耦,灵活

Spring Cloud 版本

以SRX形式命名版本号,版本号前面会有一个 release train

从以往的版本有Dalston、Edgware、Finchley、Greenwich,正在孵化的有SpringCloud Alibaba。

Spring Cloud 实战微服务(Finchley版本)

概述

Spring Cloud支持的插件众多,先来看一下Spring Cloud中文网提供的比较优秀的插件:

Spring Cloud集成的相关优质项目推荐

  • Eureka:注册中心,云端服务发现,实现云端中间层服务发现和故障转移
  • Ribbon:负载均衡,可以有多种负载均衡的策略供选择,可配合EurekaHystrix使用
  • Hystrix:熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力
  • Turbine:Turbine是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况(可视化)
  • Feign:声明式、模块化的HTTP客户端,集成了负载均衡
  • Zuul:云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门
  • Config:配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion
  • Bus:事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署
  • Sleuth:日志收集工具包,封装了Dapper和log-based追踪以及Zipkin和HTrace操作,为SpringCloud应用实现了一种分布式追踪解决方案
  • Stream:数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息
  • Security:基于spring security的安全工具包,为你的应用程序添加安全控制

实例

使用服务提供者与服务消费者来描述微服务之间的调用关系。下表解释了服务提供者与服务消费者。

​ 表-服务提供者与服务消费者

名词 定义
服务提供者 服务的被调用方(即:为其他服务提供服务的服务)
服务消费者 服务的调用方(即:依赖其他服务的服务)

以电影售票系统为例。如图,用户向电影微服务发起了一个购票的请求。在进行购票的业务操作前,电影微服务需要调用用户微服务的接口,查询当前用户的余额是多少、是不是符合购票标准等。在这种场景下,用户微服务就是一个服务提供者,电影微服务则是一个服务消费者。

图3-1 服务提供者与服务消费者

围绕该场景,先来编写一个用户微服务,然后编写一个电影微服务。

Eureka

注册中心,云端服务发现,实现云端中间层服务发现和故障转移

不妨先思考一下,怎样才能让服务消费者总能找到服务提供者呢?或者说,怎样才能让服务消费者感知到服务提供者地址的变化呢?

TIPS

目前市面上把服务消费者找到服务提供者的这种机制称为服务发现,又或者服务注册。下面来探索服务发现究竟是怎么回事。

服务发现原理初探

其实,服务发现机制非常简单,不妨用大家熟悉的MySQL来类比——只需一张表(图中的registry表)即可实现服务发现!

图-服务发现-01

如图,如果我们能在:

  • 应用启动时,自动往registry表中插入一条数据,数据包括服务名称、IP、端口等信息。
  • 应用停止时,自动把自己在registry表中的数据的status设为DOWN

这样,服务消费者不就永远都能找到服务提供者了嘛!当服务消费者想调用服务提供者接口时,只需向数据库发送SQL语句 SELECT * FROM registry where service_name = 'user' and status = 'UP' 即可找到服务提供者的所有实例!IP、端口啥的都有了,自己拼接一下,再去调用就行了!

TIPS

看,服务发现机制是不是很简单?程序猿给图中的”MySQL“的组件起了一个牛叉的名字叫:”注册中心“,也有的书将其称为”服务发现组件“。

但,这毕竟只是一个最简陋的服务发现原理。完整的服务发现要考虑的问题有很多,例如:

  • 当服务抑或所在主机突然崩溃或者进入某种不正常的情况无法提供服务(例如应用的数据库挂了)时,对应的数据理应标记DOWN,或者索性删除;
  • 如果每次调用之前,都得向服务发现组件发送类似SELECT * FROM registry where service_name = 'user' and status = 'UP' 的语句,那么服务发现组件的压力得有多大?更重要的,这与当下流行的去中心化设计的思想相悖;
  • 服务发现组件即使挂掉,也不应该影响微服务之间的调用。

那么,一个完善的服务发现组件应该具备哪些能力呢?

服务发现原理深入

不妨来看一下使用服务发现组件后的架构图,如图所示。

图-服务发现-02

服务提供者、服务消费者、服务发现组件这三者之间的关系大致如下:

  • 各个微服务在启动时,将自己的网络地址等信息注册到服务发现组件中,服务发现组件会存储这些信息;
  • 服务消费者可从服务发现组件查询服务提供者的网络地址,并使用该地址调用服务提供者的接口;
  • 各个微服务与服务发现组件使用一定机制(例如心跳)通信。服务发现组件如长时间无法与某微服务实例通信,就会自动注销(即:删除)该实例;
  • 当微服务网络地址发生变更(例如实例增减或者IP端口发生变化等)时,会重新注册到服务发现组件;
  • 客户端缓存:各个微服务将需要调用服务的地址缓存在本地,并使用一定机制更新(例如定时任务更新、事件推送更新等)。这样既能降低服务发现组件的压力,同时,即使服务发现组件出问题,也不会影响到服务之间的调用。

综上,服务发现组件应具备以下功能。

  • 服务注册表:服务注册表是服务发现组件的核心(其实就是类似于上面的registry表),它用来记录各个微服务的信息,例如微服务的名称、IP、端口等。服务注册表提供查询API和管理API,查询API用于查询可用的微服务实例,管理API用于服务的注册和注销;
  • 服务注册与服务发现:服务注册是指微服务在启动时,将自己的信息注册到服务发现组件上的过程。服务发现是指查询可用微服务列表及其网络地址的机制;
  • 服务检查:服务发现组件使用一定机制定时检测已注册的服务,如发现某实例长时间无法访问,就会从服务注册表中移除该实例。

用途

将微服务的提供者和消费者都注册到Eureka服务器上,由Eureka服务器进行管理

源码分析

Eureka原理分析

集成方式

编写Eureka Server

  • 加依赖

    1
    2
    3
    4
    5
    6
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    </dependencies>
  • 加注解

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaApplication.class, args);
    }
    }
  • 写配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server:
    port: 8761
    eureka:
    client:
    # 是否要注册到其他Eureka Server实例
    register-with-eureka: false
    # 是否要从其他Eureka Server实例获取数据
    fetch-registry: false
    service-url:
    defaultZone: http://localhost:8761/eureka/

TIPS

这里,大家可先不去探究registerWithEureka 以及fetchRegistry 究竟是什么鬼,笔者将在下一节为大家揭晓。

将应用注册到Eureka Server上

  • 加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  • 加注解

    1
    2
    3
    4
    5
    6
    @SpringBootApplication
    public class ProviderUserApplication {
    public static void main(String[] args) {
    SpringApplication.run(ProviderUserApplication.class, args);
    }
    }

    注意:早期的版本(Dalston及更早版本)还需在启动类上添加注解@EnableDiscoveryClient@EnableEurekaClient ,从Edgware开始,该注解可省略。

  • 添加配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    spring:
    application:
    # 指定注册到eureka server上的服务名称,对于电影微服务,本系列将名称设为microservice-consumer-movie
    name: microservice-provider-user
    eureka:
    client:
    service-url:
    # 指定eureka server通信地址,注意/eureka/小尾巴不能少
    defaultZone: http://localhost:8761/eureka/
    instance:
    # 是否注册IP到eureka server,如不指定或设为false,那就会注册主机名到eureka server
    prefer-ip-address: true

测试

  • 依次启动Eureka Server以及用户微服务、电影微服务;

  • 访问http://localhost:8761 可观察到类似如下界面:

    Eureka Server-01

  • 将用户微服务停止,可看到Eureka Server首页变成类似如下界面:

    Eureka Server-02

Ribbon

用途

一般来说,提到负载均衡,大家一般很容易想到浏览器 -> NGINX -> 反向代理多个Tomcat这样的架构图——业界管这种负载均衡模式叫“服务器端负载均衡”,因为此种模式下,负载均衡算法是NGINX提供的,而NGINX部署在服务器端。

Nginx扫盲

本节所讲的Ribbon则是一个客户端侧负载均衡组件——通俗地说,就是集成在客户端(服务消费者一侧),并提供负载均衡算法的一个组件。

Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机、响应时间加权等——当然,为Ribbon自定义负载均衡算法也非常容易,只需实现IRule 接口即可。

内置负载均衡的规则

负载均衡规则是Ribbon的核心,下面来看一下Ribbon内置的负载均衡规则。

  • AvailabilityFilteringRule:过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个Server的运行状态;
  • BestAvailableRule:选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。
  • RandomRule:随机选择一个Server;
  • ResponseTimeWeightedRule:作用同WeightedResponseTimeRule,二者作用一样;
  • RetryRule:对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server;
  • RoundRobinRule:轮询选择, 轮询index,选择index对应位置的Server;
  • WeightedResponseTimeRule:根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低;
  • ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择Server;

如需自定义负载均衡规则,只需实现IRule 接口或继承AbstractLoadBalancerRulePredicateBasedRule即可 ,可参考RandomRuleRoundRobinRuleZoneAvoidanceRule 等内置Rule编写自己的负载均衡规则。

引入Ribbon

在Spring Cloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,选择其中一个服务提供者实例。下图展示了Ribbon与Eureka配合使用时的大致架构。

图-Ribbon与Eureka配合

集成方式

代码示例

  • 复制项目microservice-consumer-movie ,将ArtifactId修改为microservice-consumer-movie-ribbon

  • 加依赖:由于spring-cloud-starter-netflix-eureka-client 已经包含spring-cloud-starter-netfilx-ribbon ,故而无需额外添加依赖。

  • 写代码:

    1
    2
    3
    4
    5
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
    return new RestTemplate();
    }

    如代码所示,只需在RestTemplate 上添加LoadBalanced 注解,即可让RestTemplate整合Ribbon!

  • 调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @GetMapping("/users/{id}")
    public User findById(@PathVariable Long id) {
    // 这里用到了RestTemplate的占位符能力
    User user = this.restTemplate.getForObject(
    "http://microservice-provider-user/users/{id}",
    User.class,
    id
    );
    // ...电影微服务的业务...
    return user;
    }

    由代码可知,我们将请求的目标服务改成了http://microservice-provider-user/users/{id} ,也就是http://{目标服务名称}/{目标服务端点} 的形式,Ribbon会自动在实际调用时,将目标服务名替换为该服务的IP和端口

测试

  • 依次启动microservice-discovery-eurekamicroservice-provider-user 两个实例、microservice-consumer-movie-ribbon
  • 访问http://localhost:8010/movies/users/1 多次,会发现两个user服务实例都会打印日志。

Feign

简介

Feign是Netflix开发的声明式、模板化的HTTP客户端,集成了 RestTemplate + Eureka + Ribbon + Hystrix

集成方式

下面来将前面的例子用Feign改写,让其达到与Ribbon + RestTemplate相同的效果。

  • 复制项目microservice-consumer-movie,将ArtifactId修改为microservice-consumer-movie-feign

  • 加依赖:

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  • 加注解:启动类上添加@EnableFeignClients

  • 编写Feign Client:

    1
    2
    3
    4
    5
    @FeignClient(name = "microservice-provider-user")
    public interface UserFeignClient {
    @GetMapping("/users/{id}")
    User findById(@PathVariable("id") Long id);
    }

    这样一个Feign Client就写完啦!其中,@FeignClient 注解中的microservice-provider-user是想要请求服务的名称,这是用来创建Ribbon Client的(Feign整合了Ribbon)。在本例中,由于使用了Eureka,所以Ribbon会把microservice-provider-user 解析成Eureka Server中的服务。

    除此之外,还可使用url属性指定请求的URL(URL可以是完整的URL或主机名),例如@FeignClient(name = "abcde", url = "http://localhost:8000/") 。此时,name可以是任意值,但不可省略,否则应用将无法启动!

  • Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RequestMapping("/movies")
    @RestController
    public class MovieController {
    @Autowired
    private UserFeignClient userFeignClient;

    @GetMapping("/users/{id}")
    public User findById(@PathVariable Long id) {
    return this.userFeignClient.findById(id);
    }
    }

    只需使用@Autowire注解,即可注入上面编写的Feign Client。

RestTemplate与Feign对比

相信通过本文的例子,聪明的你对如何使用Feign已经了然于心了。文章的最后,对比一下RestTemplate + Ribbon与Feign。

角度 RestTemplate + Ribbon Feign(自带Ribbon)
可读性、可维护性 欠佳(无法从URL直观了解这个远程调用是干什么的) 极佳(能在接口上写注释,方法名称也是可读的,能一眼看出这个远程调用是干什么的)
开发体验 欠佳(需要拼凑URL 极佳(无需拼凑url,自动集合Eureka和Ribbon)
风格一致性 欠佳(本地API调用和RestTemplate调用的代码风格截然不同) 极佳(完全一致,不点开Feign的接口,根本不会察觉这是一个远程调用而非本地API调用)
性能 较好 中等(性能是RestTemplate的50%左右;如果为Feign配置连接池,性能可提升15%左右)
灵活性 极佳 中等(内置功能能满足大多数项目的需求)

Hystrix

容错技术的引入

至此,我们已实现服务发现、负载均衡,同时,使用Feign也实现了良好的远程调用——我们的代码是可读、可维护的。理论上,我们现在已经能构建一个不错的分布式应用了,但微服务之间是通过网络通信的,网络可能出问题;微服务本身也不可能100%可用。

如何提升应用的可用性呢?这是我们必须考虑的问题——

举个例子:某大型系统中,服务A调用服务B,某个时刻,微服务B突然崩溃了。微服务A中,依然有大量请求在请求B,如果没有任何措施,微服务A很可能很快就会被拖死——因为在Java中,一次请求往往对应着一个线程,如果不做任何措施,那意味着微服务A请求B的线程要等Feign Client/RestTemplate超时才会释放(这个时间一般非常长,长达几十秒),于是就会有大量的线程被阻塞,而线程又对应着计算资源(CPU/内存),于是乎,大量的资源被浪费,并且越积越多,最终服务器终于没有资源给微服务A浪费了,微服务A也挂了。

因此,在大型应用中,微服务之间的容错必不可少,下面来讨论如何实现微服务的容错。

容错三板斧

  • 超时机制:一旦超时,就释放资源。由于释放资源速度较快,应用就不会那么容易被拖死。

  • 舱壁模式:不把鸡蛋放在一个篮子里。你有你的线程池,我有我的线程池,你的线程池满了和我没关系,你挂了也和我没关系。

  • 断路器:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。

    跳闸一段时间后(例如15秒),断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复“。

断路器状态转换

断路器状态转换示意图

引入Hystrix

Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。

  • 包裹请求

使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“ 命令模式 ”。

  • 跳闸机制(断路器的应用)

当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。

  • 资源隔离(舱壁模式的应用)

Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。

  • 监控(超时模式的应用)

Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。

  • 回退机制

当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。

  • 自我修复

断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、半开的逻辑转换,前面我们已经详细探讨过了,不再赘述。

通用方式使用Hystrix

服务降级

  • 加依赖:

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
  • 加注解:在启动类上添加@EnableCircuitBreaker 注解。

  • 使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @HystrixCommand(fallbackMethod = "findByIdFallback")
    @GetMapping("/users/{id}")
    public User findById(@PathVariable Long id) {
    // 这里用到了RestTemplate的占位符能力
    User user = this.restTemplate.getForObject(
    "http://microservice-provider-user/users/{id}",
    User.class,
    id
    );
    // ...电影微服务的业务...
    return user;
    }

    public User findByIdFallback(Long id) {
    return new User(id, "默认用户", "默认用户", 0, new BigDecimal(1));
    }

    由代码可知,只需使用@HystrixCommand 注解,就可保护该API。这里的”保护“,其实带有三层含义——”超时机制“、”仓壁模式“、”断路器“!

TIPS

  • 本例使用了fallbackMethod 属性,指定了一个降级方法,如不指定,Hystrix会有一个默认的降级方案,那就是抛异常,哈哈哈。

  • 如何知道断路器打开还是关闭呢?只需访问应用的/actuator/health 端点,即可查看!断路器的状态——当然,你必须添加如下配置:

    1
    2
    3
    4
    management:
    endpoint:
    health:
    show-details: always

测试

  • 启动microservice-discovery-eureka

  • 启动microservice-provider-user

  • 启动microservice-consumer-movie-ribbon-hystrix-common

  • 访问http://localhost:8010/movies/users/1 ,能正常返回结果

  • 关闭microservice-provider-user ,再次访问http://localhost:8010/movies/users/1 ,可返回类似如下结果,说明当服务提供者时,服务消费者进入了回退方法

    1
    {"id":1,"username":"默认用户","name":"默认用户","age":0,"balance":1}
  • 访问http://localhost:8010/actuator/health ,可获得类似如下结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "status": "UP",
    "details": {
    "diskSpace": ...,
    "refreshScope": ...,
    "discoveryComposite": ...,
    "hystrix": {
    "status": "UP"
    }
    }
    }

    由结果不难发现,此时断路器并未打开!这是为什么呢? 原因是:此时只请求了一次,没有达到Hystrix的阈值——Hystrix设计来保护高并发应用的,它要求10秒(可用hystrix.command.default.metrics.rollingStats.timeInMilliseconds 自定义)以内API错误次数超过20次(用circuitBreaker.requestVolumeThreshold 自定义),此时才可能触发断路器。

  • 持续不断地访问http://localhost:8010/movies/users/1 多次(至少20次)

  • 再次访问http://localhost:8010/actuator/health ,可获得类似如下结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    "status": "UP",
    "details": {
    "diskSpace": ...,
    "refreshScope": ...,
    "discoveryComposite": ...,
    "hystrix": {
    "status": "CIRCUIT_OPEN",
    "details": {
    "openCircuitBreakers": ["MovieController::findById"]
    }
    }
    }
    }

    由结果可知,此时断路器已经打开,并且列出了是哪个API的断路器被打开了。

获得造成fallback的原因

在实际项目中,很可能需要获得造成fallback的原因,此时可将代码修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@HystrixCommand(fallbackMethod = "findByIdFallback")
@GetMapping("/users/{id}")
public User findById(@PathVariable Long id) {
// 这里用到了RestTemplate的占位符能力
User user = this.restTemplate.getForObject(
"http://microservice-provider-user/users/{id}",
User.class,
id
);
// ...电影微服务的业务...
return user;
}

public User findByIdFallback(Long id, Throwable throwable) {
log.error("进入回退方法", throwable);
return new User(id, "默认用户", "默认用户", 0, new BigDecimal(1));
}

Feign 整合 Hystrix

默认Feign是不启用Hystrix的,如果需要启用,只需要在配置文件中配置即可:

1
2
3
feign:
hystrix:
enabled: true

Hystrix Command 详解

监控端点与数据

应用整合Hystrix,同时应用包含spring-boot-starter-actuator 依赖,就会存在一个/actuator/hystrix.stream 端点,用来监控Hystrix Command。当被@HystrixCommand 注解了的方法被调用时,就会产生监控信息,并暴露到该端点中。当然,该端点默认是不会暴露的,需使用如下配置将其暴露。

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: 'hystrix.stream'

此时,访问/actuator/hystrix.stream 可返回如下结果:

1
{"type":"HystrixCommand","name":"findById","group":"MovieController","currentTime":1547905939151,"isCircuitBreakerOpen":false,"errorPercentage":0,"errorCount":0,"requestCount":0,"rollingCountBadRequests":0,"rollingCountCollapsedRequests":0,"rollingCountEmit":0,"rollingCountExceptionsThrown":0,"rollingCountFailure":0,"rollingCountFallbackEmit":0,"rollingCountFallbackFailure":0,"rollingCountFallbackMissing":0,"rollingCountFallbackRejection":0,"rollingCountFallbackSuccess":0,"rollingCountResponsesFromCache":0,"rollingCountSemaphoreRejected":0,"rollingCountShortCircuited":0,"rollingCountSuccess":0,"rollingCountThreadPoolRejected":0,"rollingCountTimeout":0,"currentConcurrentExecutionCount":0,"rollingMaxConcurrentExecutionCount":0,"latencyExecute_mean":0,"latencyExecute":{"0":0,"25":0,"50":0,"75":0,"90":0,"95":0,"99":0,"99.5":0,"100":0},"latencyTotal_mean":0,"latencyTotal":{"0":0,"25":0,"50":0,"75":0,"90":0,"95":0,"99":0,"99.5":0,"100":0},"propertyValue_circuitBreakerRequestVolumeThreshold":20,"propertyValue_circuitBreakerSleepWindowInMilliseconds":5000,"propertyValue_circuitBreakerErrorThresholdPercentage":50,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_circuitBreakerForceClosed":false,"propertyValue_circuitBreakerEnabled":true,"propertyValue_executionIsolationStrategy":"THREAD","propertyValue_executionIsolationThreadTimeoutInMilliseconds":1000,"propertyValue_executionTimeoutInMilliseconds":1000,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"propertyValue_executionIsolationThreadPoolKeyOverride":null,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"propertyValue_requestCacheEnabled":true,"propertyValue_requestLogEnabled":true,"reportingHosts":1,"threadPool":"MovieController"}

对于Feign

前面讲过Feign默认已经整合了Hystrix,但这个整合其实是“不完整”,因为它默认不带有监控端点,如果你在使用Feign的同时,也想使用监控端点,需按照如下步骤操作:

  • 加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
  • 在启动类上添加注解@EnableCircuitBreaker

  • application.yml 中添加如下配置:

    1
    2
    3
    4
    5
    management:
    endpoints:
    web:
    exposure:
    include: 'hystrix.stream'

可视化监控数据

至此,我们已可通过/actuator/hystrix.strem 端点观察Hystrix运行情况,但文字形式的监控数据很不直观。现实项目中一般都需要一个可视化的界面,这样才能迅速了解系统的运行情况。Hystrix提供了一个轮子——Hystrix Dashboard,它的作用只有一个,那就是将文字形式的监控数据转换成图表展示

编写Hystrix Dashboard

  • 加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
  • 加注解:@EnableHystrixDashboard

  • 写配置:

    1
    2
    3
    # 端口随便写,这里只是表明下自己的端口规划而已
    server:
    port: 8030

启动后,访问http://localhost:8030/hystrix 即可看到类似如下的界面:

Hystrix Dashboard首页

将上文的/actuator/hystrix.stream 端点的地址贴到图中,并指定Title,然后点击Monitor Stream 按钮,即可看到类似如下的图表:

Hystrix Dashboard图表

图表解读

Hystrix图表解读Turbine

Turbine

上文的Hystrix Dashboard只支持一次监控一个微服务实例,显然是不能满足需求的,所以为了能监控多个微服务,Netflix官方再次发挥造轮子的精神——它们又编写了一个组件,Turbine。

不过这个项目刚出生就死了,14年发布后就再也没有进行更新维护。

简介

Turbine是一个聚合Hystrix监控数据的工具,它可将所有相关/hystrix.stream端点的数据聚合到一个组合的/turbine.stream中,从而让集群的监控更加方便。

引入Turbine后,架构图如下:

Turbine架构图

引入Turbine

编写Turbine Server

  • 加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
    </dependency>
  • 加注解:@EnableTurbine

  • 写配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    server:
    port: 8031
    spring:
    application:
    name: microservice-hystrix-turbine
    eureka:
    client:
    service-url:
    defaultZone: http://localhost:8761/eureka/
    instance:
    prefer-ip-address: true
    turbine:
    # 要监控的微服务列表,多个用,分隔
    appConfig: microservice-consumer-movie,microservice-consumer-movie-feign
    clusterNameExpression: "'default'"

测试

这样,Tubine即可聚合microservice-consumer-movie,microservice-consumer-movie-feign两个服务的/actuator/hystrix.stream 信息,并暴露在http://localhost:8031/turbine.stream ,将该地址贴到Hystrix Dashboard上,即可看到类似如下的图表:

Turbine图表

Zuul

Thank you for your accept. mua!
-------------本文结束感谢您的阅读-------------