Spring Cloud Netflix
1. 微服务与SpringCloud
微服务的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务。从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库。
微服务技术栈:
- 服务开发:Springboot、Spring、SpringMVC
 - 服务配置与管理:Netflix公司的Archaius、阿里的Diamond等
 - 服务注册与发现:Eureka、Consul、Zookeeper等
 - 服务调用:Rest、RPC、gRPC
 - 服务熔断器:Hystrix、Envoy等
 - 负载均衡:Ribbon、Nginx等
 - 服务接口调用(客户端调用服务的简化工具):Feign等
 - 消息队列:Kafka、RabbitMQ、ActiveMQ等
 - 服务配置中心管理:SpringCloudConfig、Chef等
 - 服务路由(API网关):Zuul等
 - 服务监控:Zabbix、Nagios、Metrics、Spectator等
 - 全链路追踪:Zipkin,Brave、Dapper等
 - 服务部署:Docker、OpenStack、Kubernetes等
 - 数据流操作开发包:SpringCloud Stream(封装与Redis,Rabbit、Kafka等发送接收消息
 - 事件消息总线:Spring Cloud Bus
 - ......
 
Spring Cloud是一套用于构建微服务架构的规范,本章介绍的 Spring Cloud Netflix 是 Spring Cloud 的一套实现。
SpringCloud
官网:Spring Cloud
SpringCloud基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件。SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等,它们都可以用SpringBoot的开发风格做到一键启动和部署。
SpringBoot将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过SpringBoot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
SpringCloud是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。
和SPringBoot的关系:SpringBoot专注于快速方便的开发单个个体微服务。 SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。
SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系. SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
SpringCloud和Dubbo对比:
- 初始定位不同: SpringCloud定位为微服务架构下的一站式解决方案;Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用和治理
 - 生态环境不同: SpringCloud依托于Spring平台,具备更加完善的生态体系;而Dubbo一开始只是做RPC远程调用,生态相对匮乏。
 - 调用方式: SpringCloud是采用Http协议做远程调用,接口一般是Rest风格,比较灵活;Dubbo是采用Dubbo协议,接口一般是Java的Service接口,格式固定。但调用时采用Netty的NIO方式,性能较好。
 - 组件差异比较多,例如SpringCloud注册中心一般用Eureka,而Dubbo用的是Zookeeper
 
| 功能 | 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 | 
RPC
Remote Procedure Call,远程过程调用。
- 数据传输方式: 多数RPC框架选择TCP作为传输协议,性能比较好。
 - 数据传输内容: 请求方需要告知需要调用的函数的名称、参数、等信息。
 - 序列化方式: 客户端和服务端交互时将参数或结果转化为字节流在网络中传输,那么数据转化为字节流的或者将字节流转换成能读取的固定格式时就需要进行序列化和反序列化
 
2. 构建Rest微服务
父工程依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>microservicecloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <modules>
        <module>microservicecloud-api</module>
        <module>microservicecloud-provider-dept-8001</module>
        <module>microservicecloud-consumer-dept-80</module>
    </modules>
    <packaging>pom</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.5.9.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.31</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.0.31</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.4</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.3</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>
- microservicecloud-api:封装的整体Entity/接口/公共配置等
 
依赖:
<dependencies><!-- 当前Module需要用到的jar包,父类已经包含了,可以不用写版本号 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
注意:网络当中传输对象必须要序列化,并且必须存在空参构造器。
- microservicecloud-provider-dept-8001:微服务落地的服务提供者
 
依赖:
<dependency>
    <!-- 引入自己定义的api通用包,可以使用Dept部门Entity -->
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>microservicecloud-api</artifactId>
    <version>${project.version}</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>springloaded</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>
配置文件:
server:
  port: 8001
mybatis:
  type-aliases-package: com.atguigu.springcloud.entities    # 所有Entity别名类所在包
  mapper-locations: classpath:mybatis/mapper/**/*.xml       # mapper映射文件
  config-location: classpath:mybatis/mybatis.cfg.xml        # mybatis配置文件所在路径
spring:
  application:
    name: microservicecloud-dept
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/clouddb01
    username: root
    password: 123456
    dbcp2:
      min-idle: 5           # 数据库连接池的最小维持连接数
      initial-size: 5       # 初始化连接数
      max-total: 5          # 最大连接数
      max-wait-millis: 200  # 等待连接获取的最大超时时间
启动类:
@SpringBootApplication
public class DeptConsumer80_App {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer80_App.class,args);
    }
}
- microservicecloud-consumer-dept-80:微服务调用的客户端使用
 
依赖:
<dependency>
    <!-- 自己定义的api -->
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>microservicecloud-api</artifactId>
    <version>${project.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>springloaded</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>
配置类:
@SpringBootConfiguration
public class ConfigBean {
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplateBuilder().build();
    }
}
配置文件中只用设置端口号为80;启动类就不再赘述
80端口是为HTTP开放的,浏览网页服务默认的端口号都是80,因此只需输入网址即可,不用输入":80"了。
RestTemplate
RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集。
使用restTemplate访问restful接口非常的简单粗暴。(url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
使用示例:
@RestController
public class DeptController_Consumer {
    private static final String REST_URL_PREFIX = "http://localhost:8001";
    @Autowired
    private RestTemplate restTemplate;
    @RequestMapping(value = "/consumer/dept/add")
    public boolean add(Dept dept) {
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
    }
    @RequestMapping(value = "/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
    }
    @RequestMapping(value = "/consumer/dept/list")
    public List<Dept> list() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
    }
}
这里访问的是8001端口的服务,学习注册中心后我们的请求地址将是动态获取的。
本地测试:http://localhost/consumer/dept/list
3. Eureka
Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,称为服务注册中心,负责服务注册与发现。 而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server并维持心跳连接。
SpringCloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。
Eureka包含两个组件:Eureka Server和Eureka Client。Eureka Server提供服务注册服务各个节点启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
搭建eureka服务注册中心
创建子工程microservicecloud-eureka-7001,引入依赖:
<dependencies>   
    <!--eureka-server服务端 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
    </dependency>  
    <!-- 修改后立即生效,热部署 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>springloaded</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
</dependencies>
修改配置文件:
server:
  port: 7001
eureka:
  instance:
    hostname: loaclhost   #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己
    fetch-registry: false       #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      #设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类:
@EnableEurekaServer  //EurekaServer服务器端启动类,接受其它微服务注册进来
@SpringBootApplication
public class EurekaServer7001_App {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001_App.class,args);
    }
}
本地测试地址:http://localhost:7001/
注册微服务
修改已创建的微服务:microservicecloud-provider-dept-8001
添加依赖:
<!-- 将微服务provider侧注册进eureka -->   
<dependency>
    <groupId>org.springframework.cloud</groupId> 
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
修改配置文件:
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
主启动类中添加注解:@EnableEurekaClient
先后启动后可以在http://localhost:7001/中看到注册的服务:

我们可以在配置文件中优化微服务信息的显示:
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    instance-id: microservicecloud-dept8001  #自定义服务名称信息
    prefer-ip-address: true                  #访问路径显示IP地址
但当前点击超链接会报告ErrorPage,需要添加依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在父工程中添加构建信息:
<build>
    <finalName>microservicecloud</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <delimiters>
                    <delimit>$</delimit>
                </delimiters>
            </configuration>
        </plugin>
    </plugins>
</build>
微服务(8001)可以在配置文件中设置页面信息:
info:
  app.name: atguigu-microservicecloud
  company.name: www.atguigu.com
  #以下两条都是由父类添加的构建插件解析的
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}
自我保护机制
Zookeeper支持CAP理论中的CP,而Eureka支持AP,在某个时刻微服务不可用了,eureka也不会立刻清理,依旧会对该微服务的信息进行保存。
配置集群
以7001为模板创建Eureka Server工程7002和7003。
修改配置文件的命名;修改host文件;修改配置文件,模板:
server:
  port: 7001
eureka:
  instance:
    hostname: eureka1   #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己
    fetch-registry: false       #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
      #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://eureka2:7002/eureka/,http://eureka3:7003/eureka/
修改微服务的配置文件:
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:7001/eureka/,http://eureka2:7002/eureka/,http://eureka3:7003/eureka/
依次运行注册中心和微服务即可,可以在三个注册中心中看到注册的服务。
端口后的
/eureka不可以改,改了微服务未能完成注册(已测试)
4. Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。其主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。我们也很容易使用Ribbon实现自定义的负载均衡算法。
LB(负载均衡)
集中式LB
集中式LB即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;
进程式LB
进程内LB 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
配置示例
修改消费者工程,添加依赖:
<!-- Ribbon相关 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
追加eureka的服务注册地址:
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka1:7001/eureka/,http://eureka2:7002/eureka/,http://eureka3:7003/eureka/
在主启动类上添加@EnableEurekaClient注解,在获取RestTemplate时添加注解:
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
    return new RestTemplateBuilder().build();
}
@LoadBalanced一个注解就实现了负载均衡的效果,默认使用负载均衡的策略。
修改controller中获取url的方式:
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号
此时依次启动注册中心、服务端和客户端,测试:http://localhost/consumer/dept/get/1
负载均衡
创建服务8002,8003以及对应的数据库,修改对应的配置文件。
注意:三个服务对外暴露的服务实例名要统一
spring:
  application:
    name: microservicecloud-dept

核心组件IRule
IRule:负载均衡策略的接口;其实现类有:
RoundRobinRole:轮询,默认策略RandomRule:随机AvailabilityFilterRule:会先过滤掉由于多次访问而处于短路还有并发的连接数量超过阈值的服务,WeightedResponseTimeRule:根据平均响应时间计算所有服务的RetryRule:先按照轮询策略获取服务,如果获取服务失败多次BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能
关系图:

修改策略
在消费端的主启动类上添加注解:
//@RibbonClient(name="微服务名",configuration=自定义规则类.class)
@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MyRule.class)
注意:官方文档明确给出了警告,我们创建的自定义策略类不能放在@ComponentScan所扫描的当前包以及子包下,否则此配置类会被所有的Ribbon客户端共享,也就是达不到特殊化定制的目的。
不能放在主启动类所在的包以及子包下。
新建自定义策略类:
@Configuration
public class MyRule{
    @Bean
    public IRule myRule(){
        return new RandomRule();//自定义为随机策略
    }
}
定制策略
我们可以参照RandomRule的源码进行定制化策略。只需要在return处返回定制的策略即可。
5. Feign
Feign是一个声明式WebService客户端,能让编写Web Service客户端更加简单。Feign支持JAX-RS标准的注解,也支持可拔插式的编码器和解码器。
Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。
在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量,优雅而简单的实现了服务调用
简单使用
参照消费者创建包:microservicecloud-consumer-dept-feign
添加依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
创建service接口,添加@FeignClient注解:
@FeignClient(value = "MICROSERVICECLOUD-DEPT")
public interface DeptClientService {
    @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
    Dept get(@PathVariable("id") long id);
    @RequestMapping(value = "/dept/list", method = RequestMethod.GET)
    List<Dept> list();
    @RequestMapping(value = "/dept/add", method = RequestMethod.POST)
    boolean add(Dept dept);
}
主启动类添加@EnableFeignClients注解:
@EnableFeignClients(basePackages= {"com.atguigu.springcloud"})
修改Controller:
@RestController
public class DeptController_Consumer {
    @Autowired
    private DeptClientService service;
    @RequestMapping(value = "/consumer/dept/add")
    public boolean add(Dept dept) {
        return service.add(dept);
    }
    ...
}
Feign整合了Ribbon,在不做任何额外配置的情况下,Feign默认使用轮询的负载均衡策略。若想要进行修改,可使用@RibbonClient进行设置。
6. Hystrix断路器
问题:
服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,导致整个系统发生更多的级联故障,进而引起系统崩溃
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,称为断路器。它能够保证在一个依赖出问题的情况下,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
作用:
- 服务降级
 - 服务熔断
 - 服务限流
 - 接近实时的监控
 
官网资料:Netflix/Hystrix Wiki
服务熔断
服务熔断熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。
在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。
示例
参考8001新建:microservicecloud-provider-dept-hystrix-8001
添加依赖:
<!--  hystrix -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
修改配置文件:
eureka:
  instance:
    instance-id: microservicecloud-dept8001-hystrix  #自定义服务名称信息
主启动类添加@EnableCircuitBreaker注解。
修改controller:
@RestController
public class DeptController {
    @Autowired
    private DeptService service;
    @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
    @HystrixCommand(fallbackMethod = "processHystrix_Get")
    public Dept get(@PathVariable("id") Long id) {
        Dept dept = this.service.get(id);
        if (null == dept) {
            throw new RuntimeException("该ID:" + id + "没有没有对应的信息");
        }
        return dept;
    }
    public Dept processHystrix_Get(@PathVariable("id") Long id) {
        return new Dept().setDeptno(id).setDname("该ID:" + id + "没有没有对应的信息,null--@HystrixCommand").setDb_source("no this database in MySQL");
    }
}
使用
@HystrixCommand(fallbackMethod = "processHystrix_Get")注解表示开启服务熔断。
processHystrix_Get方法是消费降级的逻辑。
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法。
测试:http://localhost/consumer/dept/get/112
消费降级
整体资源快不够了,忍痛将某些服务先关掉,待渡过难关再开启回来。
服务降级处理应该在客户端实现完成的,与服务端解耦。
示例
上面的写法将Hystrix与生产者高度耦合,当生产者被关闭,系统不会返回"错误"的响应信息。将两者解耦后,可以让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。
在microservicecloud-api中根据DeptClientService接口新建一个实现了FallbackFactory接口的类DeptClientServiceFallbackFactory`:
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService> {
    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public Dept get(long id) {
                return new Dept().setDeptno(id)
                    .setDname("该ID:" + id + "没有没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭")
                    .setDb_source("no this database in MySQL");
            }
            @Override
            public List<Dept> list() {
                return null;
            }
            @Override
            public boolean add(Dept dept) {
                return false;
            }
        };
    }
}
以上代码为 Feign 客户端生成降级的实现,当 Feign 客户端在调用服务提供者失败时,
FallbackFactory的create方法会被调用,返回一个实现了服务接口(DeptClientService)的降级实例。
在microservicecloud-api中DeptClientService接口的注解@FeignClient中添加fallbackFactory属性值:
@FeignClient(value = "MICROSERVICECLOUD-DEPT",fallbackFactory= DeptClientServiceFallbackFactory.class)
修改消费端的配置文件:
feign:
  hystrix:
    enabled: true
分别测试服务端开启和关闭的情况:http://localhost/consumer/dept/get/1
服务监控
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。
Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
示例
参照消费者创建工程microservicecloud-consumer-hystrix-dashboard,新增依赖:
<!-- hystrix和 hystrix-dashboard相关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
修改配置文件:
server:
  port: 9001
创建主启动类,添加@EnableHystrixDashboard注解:
@SpringBootApplication
@EnableHystrixDashboard
public class DeptConsumer_DashBoard_App {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_DashBoard_App.class,args);
    }
}
在需要监控的Provider微服务提供者中添加监控依赖:
<!-- actuator监控信息完善 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
测试:http://localhost:9001/hystrix
启动eureka集群和微服务提供者,查看生产者的状况:http://localhost:8001/hystrix.stream
图形化

- Delay:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,可以通过配置该属性来降低客户端的网络和CPU消耗。
 - Title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题。
 
示意图:

实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。
曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。
7. zuul路由网关
Zuul包含了对请求的路由和过滤两个最主要的功能。
路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意:Zuul服务最终还是会注册进Eureka 提供代理+路由+过滤三大功能
示例
参照消费者创建工程microservicecloud-zuul-gateway-9527,添加依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
创建主启动类并添加@EnableZuulProxy注解:
@EnableZuulProxy
@SpringBootApplication
public class Zuul_9527_StartSpringCloudApp {
    public static void main(String[] args) {
        SpringApplication.run(Zuul_9527_StartSpringCloudApp.class, args);
    }
}
修改配置文件:
server:
  port: 9527
spring:
  application:
    name: microservicecloud-zuul-gateway
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka1:7001/eureka/,http://eureka2:7002/eureka/,http://eureka3:7003/eureka/
  instance:
    instance-id: gateway
    prefer-ip-address: true
info:
  app.name: atguigu-microservicecloud
  company.name: www.atguigu.com
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}
启动eureka集群、服务提供者以及路由,测试:
- 设置代理名称
 
zuul:
  ignored-services: microservicecloud-dept #可以使用 “*”
  routes:
    mydept.serviceId: microservicecloud-dept
    mydept.path: /mydept/**
测试:http://localhost:9527/mydept/dept/get/2
ignored-services设置忽略原真实服务名,即此时http://localhost:9527/microservicecloud-dept/dept/get/2无法访问。
- 设置统一公共前缀
 
zuul:
  prefix: /test
此时的访问路径为:http://localhost:9527/test/mydept/dept/get/2
ZuulFilter
在使用Zuul作为整个微服务系统的入口时,我们可以统一在Zuul中设置拦截器,实现整个系统的请求过滤:
@Component
public class MyZuulFilter extends ZuulFilter {
    @Override
    public boolean shouldFilter() {
        // 1.获取当前 RequestContext 对象
        RequestContext context = RequestContext.getCurrentContext();
        // 2.获取当前请求对象
        HttpServletRequest request = context.getRequest();
        // 3.获取当前请求要访问的目标地址
        String servletPath = request.getServletPath();
        // 4.打印
        System.err.println("servletPath="+servletPath);
        // 5.当前方法返回值
        // true:表示应该过滤,下面继续执行 run()方法
        // false:表示不过滤,直接放行
        return true;
    }
    @Override
    public Object run() throws ZuulException {
        // 执行具体过滤逻辑
        System.err.println("run() ...");
        // 官方文档说:当前实现会忽略这个返回值,所以返回 null 即可
        return null;
    }
    @Override
    public String filterType() {
        // 返回当前过滤器类型
        // 可选类型包括:pre、route、post、static
        // 如果需要在目标微服务前面执行过滤操作,选用 pre 类型
        return "pre";
    }
    @Override
    public int filterOrder() {
        // 过滤器执行顺序
        return 0;
    }
}
当需要实现登录验证时,可以在run方法中重定向到登录页面。
8. 总结

访问流程:
- 请求通过 Zuul 网关进入微服务架构
 - Zuul 负责路由请求到相应的消费者服务
 - 消费者服务使用 Ribbon 客户端负载均衡来选择一个可用的生产者服务实例
 - Ribbon 从 Eureka 服务注册中心获取生产者服务的地址
 - 消费者服务使用 Hystrix 来包装对生产者服务的请求。Hystrix 会进行一些预处理,例如超时设置、熔断配置等。
- 生产者服务正常运行:Hystrix 接收到来自生产者的结果,然后将结果返回给消费者服务
 - 生产者服务出现故障:Hystrix 根据预先设置的熔断策略,决定是否进行服务熔断。如果熔断触发,Hystrix 将返回预先设置的结果(消费降级),而不是将请求传递给不可用的生产者服务。
 
 
注意:服务熔断和消费降级不是必然同时出现的
在一些情况下,服务熔断已经提供了足够的系统稳定性保障,不需要再引入消费降级。例如,对于某些关键的支付服务或者涉及到金融交易的服务,当服务熔断发生时,直接返回错误,而不进行降级处理,以确保数据的一致性和安全性。
消费降级是在服务熔断的基础上实现的
简单理解:
- Hystrix 根据预先设定的策略,阻止请求继续传递给不可用的服务,从而防止雪崩效应。这个过程称为服务熔断
 - 当服务熔断发生时,Hystrix 返回预设的默认值、友好的错误提示或者提供一些基本的、受控的功能,这个过程称为消费降级
 
9. SrpingCloud Config
分布式配置中心SrpingCloud Config为微服务架构中的微服务提供集中化的外部配置支持。
SrpingCloud Config分为服务端和客户端,服务端称为分布式配置中心,是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息,配置服务器默认采用git来存储配置信息,有助于对环境配置进行版本管理,并可以通过git客户端工具来方便的管理和访问配置内容。
SrpingCloud Config作用:
- 集中管理配置文件
 - 动态化的配置更新,分环境部署,比如dev/test/prod
 - 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取自己的配置信息
 - 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
 - 将配置信息以REST接口的形式暴露
 

简单使用
首先需要在GitHub中创建配置文件的仓库,获取git地址;本地创建本地仓库并clone远程仓库;创建文件application.yml:
spring:
  profiles:
    active:
      - dev
---
spring:
  profiles: dev
  application:
    name: config-dev
---
spring:
  profiles: test
  application:
    name: config-test
注意文件需要以UTF-8的格式保存
将文件推送到GitHub上。
服务端配置
创建模块:microservicecloud-config-server
添加依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>
配置文件:
server:
  port: 3344
spring:
  application:
    name: microservicecloud-config-server
  cloud:
    config:
      server:
        git:
          uri: #这里填Github仓库地址
创建主启动类并添加注解@EnableConfigServer
启动微服务,测试:http://localhost:3344/application-dev.yml
五种访问规则:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
label表示分支名;当profile不存在时会返回公共配置
客户端配置
本地仓库新建文件microservicecloud-config-client.yml并提交到远程仓库:
spring:
  profiles:
    active:
      - dev
---
server:
  port: 8201
spring:
  profiles: dev
  application:
    name: config-client
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:7001/eureka/
---
server:
  port: 8202
spring:
  profiles: test
  application:
    name: config-client
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:7001/eureka/
新建工程:microservicecloud-config-client
添加依赖:
<!-- SpringCloud Config客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
创建配置文件:bootstrap.yml:
spring:
  cloud:
    config:
      name: microservicecloud-config-client #需要从GitHub上读取的资源名称,注意没有yml后缀名
      profile: dev #本次访问的配置项
      label: master
      uri: http://localhost:3344 #本服务启动后先去找客户端获取GitHub的服务地址
application.yml是用户级的资源配置项;bootstrap.yml是系统级的,优先级更高
Spring Cloud会创建一个Bootstrap Context,作为Spring应用Application Context的父上下文。初始化时Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment。
Bootstrap属性有高优先级,默认情况下不会被本地配置覆盖,所以新增bootstrap.yml文件,保证Bootstrap Context和Application Context配置的分离。
正常创建主启动类,不需要添加新注解。
创建Rest类,从GitHub上读取配置:
@RestController
public class ConfigClientRest{
    @value("${Spring.application.name}")
    privatet String applicationName;
    @value("${eureka.client.service-url.defaultZone}")
    privatet String eurekaServers;
    @value("${server.port}")
    privatet String port;
    @RequestMapping("/config")
    public String getConfig(){
        return applicationName+"\t"+eurekaServers+"\t"+port;
    }
}
先后启动配置中心客户端和服务端,测试:http://localhost:8201/config

                
            
        
浙公网安备 33010602011771号