SpringCloud

You are the big drop of dew under the lotus leaf, I am the smaller one on its upper side,” said the dewdrop to the lake.

露珠对湖水说道;“你是在荷叶下面的大露珠,我是在荷叶上面的较小的露
珠。” 

SpringCloud

SpringCloud中文网

SpringCloud官网

SpringCloud是一个基于SpringBoot实现的云应用开发工具,它为基于JVM的云应用开发中涉及的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

微服务架构

微服务(Microservices Architecture)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

微服务架构的核心思想是,一个应用是由多个小的、相互独立的、微服务组成,这些服务运行在自己的进程中,开发和发布都没有依赖。不同服务通过一些轻量级交互机制来通信,例如 RPC、HTTP 等,服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。简单的来说,一个系统的不同模块转变成不同的服务!而且服务可以使用不同的技术加以实现!

学习环境搭建

父项目pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<junit.version>4.13.1</junit.version>
<lombok.version>1.18.20</lombok.version>
<log4j.version>1.2.12</log4j.version>
</properties>

<!--打包方式-->
<packaging>pom</packaging>

<dependencyManagement>
<dependencies>
<!--Springcloud依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--连接数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!--springboot启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--日志测试-->
<!--log4j-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.7</version>
</dependency>
</dependencies>
</dependencyManagement>

SpringCloud-api

配置SpringCloud-api,pom环境引入父项目依赖,构建实体类。

springcloud-provider-dept-8001

构建服务者,springcloud-provider-dept-8001

配置服务者application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 8001

#mybatis配置
mybatis:
type-aliases-package: com.bobo.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml

#spring配置
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource #数据源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/(api实体类对应数据库名)?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
password: 密码
username: 用户名

配置pom.xml版本号依赖于父项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<dependencies>
<!--添加监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.4.6</version>
</dependency>

<!--我们需要拿到实体类 索要配置api module-->
<dependency>
<groupId>com.bobo</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</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>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jetty-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>

springcloud-consumer-dept-80

构建消费者,springcloud-consumer-dept-80

配置消费者application.yml

1
2
server:
port: 80

配置pom.xml版本号依赖于父项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>  
<!--实体类+web-->
<dependency>
<groupId>com.bobo</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>

SpringCloud Eureka

SpringCloud Eureka来实现服务治理

SpringCloud Eureka是SpringCloud Netflix项目下的服务治理模块。而SpringCloud Netflix项目是SpringCloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为SpringBoot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。

它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。

EurekaServer

提供服务注册和发现 springcloud-eureka-7002

  1. 添加依赖

    在项目 pom.xml中引入需要的依赖内容

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
    <version>版本号</version>
    </dependency>
  2. 添加配置

    在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.yml配置文件中增加如下信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #配置端口号
    server:
    port: 7001

    #Eureka
    eureka:
    instance:
    hostname: localhost #Eureka服务端的实例名称
    client:
    register-with-eureka: false #表示是否向eureka服务中心注册自己
    fetch-registry: false #如果为false则表示自己为注册中心
    service-url:
    #监控页面
    defaultZone: http:// {eureka.instance.hostname}: {server.port}/eureka/
  3. 开启服务注册

    通过 @EnableEurekaServer 注解启动一个服务注册中心提供给其他应用进行对话,这个注解需要在SpringBoot工程的主启动类上加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.bobo.springcloud;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

    @SpringBootApplication
    @EnableEurekaServer //EnableEurekaServer服务端的启动类,可以接受别人注册进来
    public class EurekaServer_7001 {
    public static void main(String[] args) {
    SpringApplication.run(EurekaServer_7001.class, args);
    }
    }
  4. 访问服务

    启动工程后,访问:http://localhost:7001/

Service Provider

服务提供方:将自身服务注册到 Eureka 注册中心,从而使服务消费方能够找到

  1. 添加依赖pom.xml

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
    <version>版本号</version>
    </dependency>
  2. 添加配置application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #Eureka的配置  服务注册到哪里
    eureka:
    client:
    service-url:
    defaultZone: http://localhost:7001/eureka/
    instance:
    instance-id: springcloud-provider-dept8001 #修改Eureka 的默认描述信息

    #info配置
    info:
    app.name: bobo-springcloud
    company.name: bobo666
  3. 开启服务注册

    在应用主类中通过加上 @EnableEurekaClient,但只有 Eureka 可用.

    也可以使用@EnableDiscoveryClient,需要配置才能找到Eureka注册中心服务器。

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableEurekaClient //自动在服务启动后自动注册到Eureka中
    @EnableDiscoveryClient
    public class DeptProvider_8001 {
    public static void main(String[] args) {
    SpringApplication.run(DeptProvider_8001.class, args);
    }
    }
  4. 访问服务

    启动该工程后,再次访问启动工程:http://localhost:7001/

多集群模拟

如果一台主机试验,修改host文件

注释默认映射,添加映射

分别修改每个项目下application.yam文件,以7001为例

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 7001

#Eureka
eureka:
instance:
hostname: eureka7001.com #Eureka服务端的实例名称
client:
register-with-eureka: false #表示是否向eureka服务中心注册自己
fetch-registry: false #如果为false则表示自己为注册中心
service-url:
defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/

修改服务提供方application.yam

1
2
3
4
5
6
7
8
9
10
11
12
#Eureka的配置  服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: springcloud-provider-dept8001 #修改Eureka 的默认描述信息

#info配置
info:
app.name: bobo-springcloud
company.name: bobo666

启动7001、7002、7003项目

以7001为例

启动8001服务提供者项目

Eureka与Zookeeper的区别

CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性P在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。
Zookeeper保证CP
Zookeeper 为主从结构,有leader节点和follow节点。当leader节点down掉之后,剩余节点会重新进行选举。选举过程中会导致服务不可用,丢掉了可用行。
Eureka保证AP
Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。

为什么CAP只能达到 CP 或者 AP?

由以上我们得知,P是必然存在的。
如果我们保证了CP,即一致性与分布容错。当我们通过一个服务器修改数据后,该服务器会向另一个服务器发送请求,将数据进行同步,但此时,该数据应处于锁定状态,不可再次修改,这样,如果此时我们想服务器发送请求,则得不到相应,这样就不能A,高可用。
如果我们保证了AP,那么我们不能对服务器进行锁定,任何时候都要得到相应,那么数据的一致性就不好说了。

Eureka自我保护机制

在默认配置中Eureka Server服务在一定时间(默认为90秒)没接受到某个服务的心跳连接后,Eureka Server会注销该服务。但是会存在当网络分区发生故障,导致该时间内没有心跳连接,但该服务本身还是健康运行的情况。Eureka通过“自我保护模式”来解决这个问题。

  1. 在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当他收到的心跳数重新恢复到阈值以上时,该Eureka Server节点会自动退出自我保护模式。

  2. 在自我保护模式下,Eureka Server 仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点依然可用。)

  3. 当网络稳定时,当前实例新的注册信息会被同步到其他节点中

Ribbon

SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。它可以通过在客户端中配置ribbonServerList来设置服务端列表去轮询访问以达到均衡负载的作用。

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

LB方案

负载均衡,英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务。

负载均衡构建在原有网络结构之上,它提供了一种透明且廉价有效的方法扩展服务器和网络设备的带宽、加强网络数据处理能力、增加吞吐量、提高网络的可用性和灵活性。

集中式LB, 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;

进程内LB,将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

集群模拟

Ribbon Consumer

服务消费者

  1. 添加依赖 pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--Ribbon负载均衡-->
    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
    <version>版本号</version>
    </dependency>
    <!--erueka-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>版本号</version>
    </dependency>
  2. 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    server:
    port: 80

    #eureka配置
    eureka:
    client:
    register-with-eureka: false #不向eureka注册自己
    service-url:
    defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
  3. 开启服务负载均衡

    在工程的启动类中,通过@EnableEurekaClient向服务注册中心注册;并且向程序的ioc注入一个bean: restTemplate并通过@LoadBalanced注解表明这个restTemplate开启负载均衡的功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.bobo.springcloud;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

    @SpringBootApplication
    @EnableEurekaClient
    public class DeptConsumer_80 {
    public static void main(String[] args) {
    SpringApplication.run(DeptConsumer_80.class, args);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.bobo.springcloud.config;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.client.RestTemplate;

    @Configuration
    public class ConfigBean { //@Configuration ----spring applicationContext.xml

    //配置负载均衡实现 RestTemplate
    @Bean
    @LoadBalanced //Ribbon
    public RestTemplate getRestTemplate() {
    return new RestTemplate();
    }
    }
  4. 消费提供者方法

    Controller 类中,调用提供者的 home 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.bobo.springcloud.controller;
    import com.bobo.springcloud.pojo.Dept;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    import java.util.List;

    @RestController
    public class DeptConsumerController {
    //理解 消费者不应该有service层
    //RestTemplate
    //提供多种便捷访问远程http服务的方法,是一种简单的restful服务模版
    @Autowired
    private RestTemplate restTemplate;

    //Ribbon 这里的地址 应该是一个变量,通过服务名来访问
    //private static final String REST_URL_PREFIX = "http://localhost:8001";
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list() {
    return restTemplate.getForObject(REST_URL_PREFIX + "/dept/deptlist", List.class);
    }
    }
  5. 负载均衡

    访问http://localhost/consumer/dept/list`,发现Ribbon已经实现负载均衡

负载均衡算法

  1. 轮询法

    轮询法,就是将用户的请求轮流分配给服务器,就像是挨个数数,轮流分配。这种算法比较简单,他具有绝对均衡的优点,但是也正是因为绝对均衡它必须付出很大的代价,例如它无法保证分配任务的合理性,无法根据服务器承受能力来分配任务。

  2. 随机法

    随机法,是随机选择一台服务器来分配任务。它保证了请求的分散性达到了均衡的目的。同时它是没有状态的不需要维持上次的选择状态和均衡因子。但是随着任务量的增大,它的效果趋向轮询后也会具有轮询算法的部分缺点。

  3. 最小连接法

    最小连接法,将任务分配给此时具有最小连接数的节点,因此它是动态负载均衡算法。一个节点收到一个任务后连接数就会加1,当节点故障时就将节点权值设置为0,不再给节点分配任务。

    最小连接法适用于各个节点处理的性能相似时。任务分发单元会将任务平滑分配给服务器。但当服务器性能差距较大时,就无法达到预期的效果。因为此时连接数并不能准确表明处理能力,连接数小而自身性能很差的服务器可能不及连接数大而自身性能极好的服务器。所以在这个时候就会导致任务无法准确的分配到剩余处理能力强的机器上。

Feign

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。

使用Feign,只需要创建一个接口并注解,它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解,Feign支持可插拔的编码器和解码器Feign默认集成了Ribbo,并和Eureka结合,默认实现了负载均衡的效果。

Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它。即可以完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装服务调用客户端的开发量。

Feign远程调用的基本流程

Feign 具有如下特性

  • 可插拔的注解支持,包括Feign注解和JAX-RS注解
  • 支持可插拔的HTTP编码器和解码器
  • 支持Hystrix和它的Fallback
  • 支持Ribbon的负载均衡
  • 支持HTTP请求和响应的压缩Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。它整合了Ribbon和Hystrix,从而不再需要显式地使用这两个组件。Feign还提供了HTTP请求的模板,通过编写简单的接口和注解,就可以定义好HTTP请求的参数、格式、地址等信息。接下来,Feign会完全代理HTTP的请求,我们只需要像调用方法一样调用它就可以完成服务请求。

Hystrix

在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在SpringCloud可以用RestTemplate+Ribbon和Feign来调用。

由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

针对上述问题,在SpringCloud Hystrix中实现了线程隔离、断路器等一系列的服务保护功能。它也是基于Netflix的开源框架 Hystrix实现的,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能。

雪崩场景 举例 应对策略
硬件故障 服务器宕机,机房断电,光纤被挖断等 多机房容灾、异地多活等。
流量激增 异常流量,重试加大流量等 服务自动扩容、流量控制(限流、关闭重试)等。
缓存穿透 一般发生在应用重启,所有缓存失效时,以及短时间内大量缓存失效时。大量的缓存不命中,使请求直击后端服务,造成服务提供者超负荷运行,引起服务不可用。 缓存预加载、缓存异步加载等。
程序BUG 如程序逻辑导致内存泄漏,JVM长时间FullGC等。 修改程序bug、及时释放资源等。
同步等待 服务间采用同步调用模式,同步等待造成的资源耗尽。 资源隔离、MQ解耦、不可用服务调用快速失败等。资源隔离通常指不同服务调用采用不同的线程池;不可用服务调用快速失败一般通过熔断器模式结合超时机制实现。

断路器

断路器模式源于Martin Fowler的Circuit Breaker一文。“断路器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

使用 Ribbon Hystrix

服务熔断

  1. 添加依赖

    在项目pom 加上hystrix的依赖

    1
    2
    3
    4
    5
    <!-- hystrix 断路器 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    </dependency>
  2. 在程序的启动类通过 @EnableHystrix 开启 Hystrix 断路器监控.

  3. 消费提供者方法

    1. 新方法defaultStores,当出现雪崩情况,跳转到该方法
    2. 修改 DeptConsumerController 类的,list 方法,加上注解@HystrixCommand(fallbackMethod = "defaultStores")

服务熔断与降级

服务熔断:作用在服务提供者

服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务响应正常后恢复调用链路.

服务降级:作用在消费者

服务降级处理是在客户端实现完成的,与服务端没有关系

服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。\

相同点

目标一致 都是从可用性和可靠性出发,为了防止系统崩溃;

用户体验类似 最终都让用户体验到的是某些功能暂时不可用;

不同点

触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;

zuul

务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

路由在微服务体系结构的一个组成部分。例如,/可以映射到您的Web应用程序,/api/users映射到用户服务,并将/api/shop映射到商店服务。ZuulNetflix的基于JVM的路由器和服务器端负载均衡器。

什么是服务网关

服务网关 = 路由转发 + 过滤器

  1. 路由转发:接收一切外界请求,转发到后端的微服务上去;

  2. 过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。

为什么需要服务网关

上述所说的横切功能(以权限校验为例)可以写在三个位置:

  • 每个服务自己实现一遍
  • 写到一个公共的服务中,然后其他所有服务都依赖这个服务
  • 写到服务网关的前置过滤器中,所有请求过来进行权限校验

第一种,缺点太明显,基本不用;

第二种,相较于第一点好很多,代码开发不会冗余,但是有两个缺点:

  • 由于每个服务引入了这个公共服务,那么相当于在每个服务中都引入了相同的权限校验的代码,使得每个服务的jar包大小无故增加了一些,尤其是对于使用docker镜像进行部署的场景,jar越小越好;
  • 由于每个服务都引入了这个公共服务,那么我们后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难,而且假设我们改变了公共服务中的权限校验的方式,想让所有的服务都去使用新的权限校验方式,我们就需要将之前所有的服务都重新引包,编译部署。

而服务网关恰好可以解决这样的问题:

  • 将权限校验的逻辑写在网关的过滤器中,后端服务不需要关注权限校验的代码,所以服务的jar包中也不会引入权限校验的逻辑,不会增加jar包大小;
  • 如果想修改权限校验的逻辑,只需要修改网关中的权限校验过滤器即可,而不需要升级所有已存在的微服务。

简单使用

新建项目 spring-cloud-zuul-service

  1. 添加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
    </dependency>
  2. 开启服务注册

    在程序的启动类 ZuulApplication 通过 @EnableZuulProxy 开启 Zuul 服务网关

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.bobo.springcloud;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    @EnableZuulProxy
    @SpringBootApplication
    public class ZuulApplication {
    public static void main(String[] args) {
    SpringApplication.run(ZuulApplication.class, args);
    }
    }
  3. 添加配置

    配置文件 application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    spring:
    application:
    name: springboot-zuul
    server:
    port: 9527
    eureka:
    client:
    service-url:
    defaultZone: http://eureka7001.com:7001/eureka/, ,http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
    instance:
    instance-id: zuul9527.com

    zuul:
    routes:
    mydept.serverId: springcloud-provider-dept
    mydept.path: /mydept/**

SpringCloud Config

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在SpringCloud中,有分布式配置中心组件pringloud Config,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client

SpringCloud Config就是我们通常意义上的配置中心,把应用原本放在本地文件的配置抽取出来放在中心服务器,从而能够提供更好的管理、发布能力。SpringCloud Config分服务端和客户端,服务端负责将git svn中存储的配置文件发布成REST接口客户端可以从服务端REST接口获取配置。但客户端并不能主动感知到配置的变化,从而主动去获取新的配置,这需要每个客户端通过POST方法触发各自的/refresh

SpringCloud Bus通过一个轻量级消息代理连接分布式系统的节点。这可以用于广播状态更改(如配置更改)或其他管理指令。SpringCloudBus提供了通过POST方法访问的endpoint/bus/refresh,这个接口通常由git的钩子功能调用,用以通知各个SpringCloud Config的客户端去服务端更新配置。

服务端配置 Config Server

  1. 新建项目 spring-cloud-config-server

  2. 添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
    <version>版本号</version>
    </dependency>
  3. 添加配置

    配置文件 application.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    port: 3344
    spring:
    application:
    name: config-server
    cloud:
    config:
    label: main
    server:
    git:
    uri: git仓库HTTPS地址
    search-paths: spring-cloud-config
    • spring.cloud.config.server.git.uri:配置git仓库地址
    • spring.cloud.config.server.git.searchPaths:配置仓库路径
    • spring.cloud.config.label:配置仓库的分支
  4. 开启服务注册

    在程序的启动类 ConfigServerApplication 通过 @EnableConfigServer 开启 SpringCloud Config 服务端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.bobo.springcloud;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.config.server.EnableConfigServer;
    @EnableConfigServer
    @SpringBootApplication
    public class ConfigServerApplication {
    public static void main(String[] args) {
    SpringApplication.run(ConfigServerApplication.class, args);
    }
    }

客户端配置 Config Client

  1. 新建项目 spring-cloud-config-client

  2. 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
    <version>版本号</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>版本号</version>
    </dependency>
  3. 添加配置

    配置文件 application.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server:
    port: 8088
    spring:
    application:
    name: config-client
    cloud:
    config:
    label: mian
    profile: dev
    uri: http://localhost:3344/
  4. 开启服务注册

    在程序的启动类 ConfigClientApplication 通过 @Value 获取服务端的 content 值的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.bobo.springcloud.controller;
    @RestController
    @SpringBootApplication
    public class ConfigClientApplication {
    @Value("${content}")
    String content;
    @RequestMapping("/")
    public String home() {
    return "content:" + content;
    }
    public static void main(String[] args) {
    SpringApplication.run(ConfigClientApplication.class, args);
    }
    }
查看评论