Spring Cloud

前言

1、Spring Cloud

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

  • Spring Cloud 就是一套工具,帮助大家很容易地搭建出这么一个 集群和分布式的架子出来。

2、分布式和集群

2.1 单体架构

  • 所谓的单体架构就是所有功能,都放在一个应用里。

  • 单体架构系统有其好处,如便于开发,测试,部署也很方便,直接打成一个 jar 或者 war, 就什么都好了。

  • 不过单体架构也有其弊端,最主要体现在高访问,高并发的上限是固定的。比如一个单体架构,能够承受 1000 次访问/秒。但是访问量达到 2000 次/秒的时候,就会非常卡顿,严重影响业务,并且仅仅依靠单体架构本身,很难突破这个瓶颈了。

2.2 微服务

  • 简单说,一个 Spring Boot 就是一个微服务,并且这个 Spring Boot 做的事情很单纯。
  • 比如 product-service 这个项目,就可以拆成两个微服务,分别是 数据微服务,和视图微服务,其实就是俩 Spring Boot, 只是各自做的事情都更单纯。

2.3 服务注册

  • 有了微服务,就存在如何管理这个微服务,以及这两个微服务之间如何通信的问题,所以就要引入一个 微服务注册中心概念,这个微服务注册中心在 Spring Cloud 里就叫做 eureka server, 通过它把就可以把微服务注册起来,以供将来调用。

2.4 服务访问

  • 在业务逻辑上,视图微服务 需要 数据微服务 的数据,所以就存在一个微服务访问另一个微服务的需要。
  • 而这俩微服务已经被注册中心管理起来了,所以 视图微服务 就可以通过 注册中心定位并访问 数据微服务了。

2.5 分布式

  • 简单说,原来是在一个 Spring Boot 里就完成的事情,现在分布在多个 Spring Boot 里做,这就是初步具备 分布式雏形了。

  • 分布式好处:

    • 如果要更新数据微服务,视图微服务是不受影响的。
    • 可以让不同的团队开发不同的微服务,他们之间只要约定好接口,彼此之间是低耦合的。
    • 如果视图微服务挂了,数据微服务依然可以继续使用。
    • 等等。

2.6 集群

  • 原来数据微服务只有这一个 Spring Boot, 现在做同样数据微服务的,有两个 Spring Boot, 他们提供的功能一模一样,只是端口不一样,这样就形成了集群。

  • 集群好处:

    • 比起一个 Spring Boot, 两个 Spring Boot 可以分别部署在两个不同的机器上,那么理论上来说,能够承受的负载就是 x 2. 这样系统就具备通过横向扩展而提高性能的机制。
    • 如果 8001 挂了,还有 8002 继续提供微服务,这就叫做高可用。
    • 等等。

2.7 分布式和集群

  • 分布式和集群周边服务

  • 哪些微服务是如何彼此调用的 ?

    • sleuth 服务链路追踪。
  • 如何在微服务间共享配置信息 ?

    • 配置服务 Config Server。
  • 如何让配置信息在多个微服务之间自动刷新 ?

    • RabbitMQ 总线 Bus。
  • 如果数据微服务集群都不能使用了,视图微服务如何去处理 ?

    • 断路器 Hystrix。
  • 视图微服务的断路器什么时候开启了?什么时候关闭了 ?

    • 断路器监控 Hystrix Dashboard。
  • 如果视图微服务本身是个集群,那么如何进行对他们进行聚合监控 ?

    • 断路器聚合监控 Turbine Hystrix Dashboard。
  • 如何不暴露微服务名称,并提供服务 ?

    • Zuul 网关。

2.8 父子项目

  • Maven 父子(聚合)项目

  • Spring Cloud 比较特别,它由多个微服务组成,所谓的微服务,就是 Spring Boot。

  • 所以可以说 Spring Cloud 由多个 Spring Boot 项目组成,而这些 Spring Boot 之间又是围绕一个共同目的而存在的。

  • 所以,为了便于组织这些 Spring Boot 项目,会采用 maven 父子-聚合 项目的方式来开发。

3、服务注册中心

  • 服务注册中心:Eureka Server

  • pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <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>
    <parent>
    <groupId>cn.how2j.springcloud</groupId>
    <artifactId>springcloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>eurekaServer</artifactId>

    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    </dependencies>

    </project>
  • application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    eureka:
    instance:
    hostname: localhost
    client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

    spring:
    application:
    name: eureka-server
  • EurekaServerApplication

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package cn.how2j.springcloud;

    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

    import cn.hutool.core.util.NetUtil;

    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {

    public static void main(String[] args) {
    // 8761 这个端口是默认的
    int port = 8761;
    if(!NetUtil.isUsableLocalPort(port)) {
    System.err.printf("端口 %d 被占用了,无法启动 %n", port );
    System.exit(1);
    }
    new SpringApplicationBuilder(EurekaServerApplication.class).properties("server.port=" + port).run(args);
    }
    }

4、注册微服务

  • 注册数据微服务

  • pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  • application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    eureka:
    client:
    serviceUrl:
    defaultZone: http://localhost:8761/eureka/

    spring:
    application:
    name: product-data-service
    server:
    port: 8001

5、访问微服务

  • Spring Cloud 提供了两种方式访问注册好的微服务。

    • 一种是 Ribbon。
    • 一种是 Feign。

5.1 Ribbon

  • Ribbon 是使用 restTemplate 进行调用,并进行客户端负载均衡。

  • Ribbon 客户端通过 restTemplate 访问 http://PRODUCT-DATA-SERVICE/products

  • 而 PRODUCT-DATA-SERVICE 既不是域名也不是 ip 地址,而是数据服务在 eureka 注册中心的名称。

  • 这里只是指定了要访问的微服务名称,但是并没有指定端口号。

    1
    2
    3
    public List<Product> listProdcuts() {
    return restTemplate.getForObject("http://PRODUCT-DATA-SERVICE/products",List.class);
    }
  • 客户端负载均衡

    • 比如在注册中心,注册了 8001 和 8002 两个微服务,Ribbon 会从注册中心获知这个信息。
    • 然后由 Ribbon 这个客户端自己决定是调用哪个,这个就叫做客户端负载均衡。

5.2 Feign

  • Feign 是对 Ribbon 的封装,使用注解的方式,调用起来更简单。也是主流的方式。

  • Feign 客户端通过 注解方式 访问 PRODUCT-DATA-SERVICE 服务的 products 路径。

  • 而 PRODUCT-DATA-SERVICE 既不是域名也不是 ip 地址,而是数据服务在 eureka 注册中心的名称。

  • 这里只是指定了要访问的微服务名称,但是并没有指定端口号。

    1
    2
    3
    4
    5
    6
    @FeignClient(value = "PRODUCT-DATA-SERVICE")
    public interface ProductClientFeign {

    @GetMapping("/products")
    public List<Product> listProdcuts();
    }
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

6、服务链路追踪

  • 随着业务的增加,就会有越来越多的微服务存在,他们之间也会有更加复杂的调用关系。

  • 这个调用关系,仅仅通过观察代码,会越来越难以识别,所以就需要通过 zipkin 服务链路追踪服务器这个东西来用图片进行识别了。

  • 启动链路追踪服务器 zipkin-server-2.10.1-exec.jar。

    1
    java -jar zipkin-server-2.10.1-exec.jar
  • 配置

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    1
    2
    3
    spring:
    zipkin:
    base-url: http://localhost:9411
    1
    2
    3
    4
    @Bean
    public Sampler defaultSampler() {
    return Sampler.ALWAYS_SAMPLE;
    }

7、配置服务器

  • 有时候,微服务要做集群,这就意味着,会有多个微服务实例。

  • 在业务上有时候需要修改一些配置信息,比如说 版本信息,倘若没有配置服务,那么就需要挨个修改微服务,挨个重新部署微服务,这样就比较麻烦。

  • 为了偷懒,这些配置信息就会放在一个公共的地方,比如 git, 然后通过配置服务器把它获取下来,然后微服务再从配置服务器上取下来。

  • 这样只要修改 git 上的信息,那么同一个集群里的所有微服务都立即获取相应信息了,这样就大大节约了开发,上线和重新部署的时间了。

  • 先在 git 里保存 version 信息,然后通过 ConfigServer 去获取 version 信息,接着不同的视图微服务实例再去 ConfigServer 里获取 version.

  • 配置

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    cloud:
    config:
    label: master
    server:
    git:
    uri: https://github.com/how2j/springcloudConfig/
    searchPaths: respo
    1
    2
    3
    4
    @EnableConfigServer		// 表示本 Spring Boot 是个配置服务器
    public class ConfigServerApplication {

    }

8、配置客户端

  • 从配置服务器上获取版本信息。

  • 作为配置客户端,比较特别,它需要在 bootstrap.yml 里配置 config-server 的信息。

  • 而不是像以前那样在 application.yml 里进行配置。

  • bootstrap.yml 和 application.yml 的区别,简单说就是前者先启动,并且一些系统方面的配置需要在 bootstrap.yml 里进行配置。

  • 配置

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring:
    cloud:
    config:
    label: master
    profile: dev
    discovery:
    enabled: true
    serviceId: config-server
    client:
    serviceUrl:
    defaultZone: http://localhost:8761/eureka/
    1
    2
    3
    4
    5
    6
    7
    8
    @Controller
    @RefreshScope
    public class ProductController {
    @Autowired ProductService productService;

    @Value("${version}")
    String version;
    }

9、消息总线

  • Spring Cloud 通过 RabbitMQ 来进行消息广播,以达到有配置信息发生改变的时候,广播给多个微服务的效果。

  • 配置

    1
    2
    3
    4
    5
    6
    7
    8
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    spring:
    cloud:
    bus:
    enabled: true
    trace:
    enabled: true

    rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

10、断路器

  • 断路器 Hystrix 就是当被访问的微服务无法使用的时候,当前服务能够感知这个现象,并且提供一个备用的方案出来。

  • 比如数据微服务无法使用了,如果有了断路器,那么视图微服务就能够知道此事,并且展示给用户相关的信息。而不会报错或者一直卡在那里。

  • 配置

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    1
    feign.hystrix.enabled: true
    1
    2
    3
    4
    5
    6
    @FeignClient(value = "PRODUCT-DATA-SERVICE",fallback = ProductClientFeignHystrix.class)
    public interface ProductClientFeign {

    @GetMapping("/products")
    public List<Product> listProdcuts();
    }

10.1 断路器监控

  • 当数据服务不可用的时候,断路器就会发挥作用。

  • 那么数据服务什么时候可用,什么时候不可用,如何监控这个事情呢?就要用到 断路器监控 来可视化掌控这个情况了。

  • 配置

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    1
    2
    3
    spring:
    application:
    name: hystrix-dashboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @SpringBootApplication
    @EnableHystrixDashboard
    public class ProductServiceHystrixDashboardApplication {
    public static void main(String[] args) {
    int port = 8020;
    if(!NetUtil.isUsableLocalPort(port)) {
    System.err.printf("端口%d被占用了,无法启动%n", port );
    System.exit(1);
    }
    new SpringApplicationBuilder(ProductServiceHystrixDashboardApplication.class).properties("server.port=" + port).run(args);

    }

    }

10.1 断路器聚合监控

  • 为了方便监控集群里的多个实例,Spring Cloud 提供了一个 turbine 项目。

  • 它的作用是把一个集群里的多个实例汇聚在一个 turbine 里,然后再在 断路器监控里查看这个 turbine, 这样就能够在集群层面进行监控了。

  • 配置

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
    </dependency>
    1
    2
    3
    4
    5
    turbine:
    aggregator:
    clusterConfig: default
    appConfig: product-view-service-feign ### 配置 Eureka 中的 serviceId 列表,表明监控哪些服务
    clusterNameExpression: new String("default")
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @SpringBootApplication
    @EnableTurbine
    public class ProductServiceTurbineApplication {
    public static void main(String[] args) {
    int port = 8021;
    if(!NetUtil.isUsableLocalPort(port)) {
    System.err.printf("端口%d被占用了,无法启动%n", port );
    System.exit(1);
    }
    new SpringApplicationBuilder(ProductServiceTurbineApplication.class).properties("server.port=" + port).run(args);

    }

    }

11、网关 Zuul

  • 有两种微服务,分别是数据微服务和视图微服务。

  • 他们有可能放在不同的 ip 地址上,有可能是不同的端口。

  • 为了访问他们,就需要记录这些地址和端口。而地址和端口都可能会变化,这就增加了访问者的负担。

  • 所以这个时候,就可以用网关来解决这个问题。只需要记住网关的地址和端口号就行了。

  • 配置

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    1
    2
    3
    4
    5
    6
    7
    8
    zuul:
    routes:
    api-a:
    path: /api-data/**
    serviceId: PRODUCT-DATA-SERVICE
    api-b:
    path: /api-view/**
    serviceId: PRODUCT-VIEW-SERVICE-FEIGN
    1
    2
    3
    4
    @EnableZuulProxy
    public class ProductServiceZuulApplication {

    }
文章目录
  1. 1. 前言
  2. 2. 1、Spring Cloud
  3. 3. 2、分布式和集群
    1. 3.1. 2.1 单体架构
    2. 3.2. 2.2 微服务
    3. 3.3. 2.3 服务注册
    4. 3.4. 2.4 服务访问
    5. 3.5. 2.5 分布式
    6. 3.6. 2.6 集群
    7. 3.7. 2.7 分布式和集群
    8. 3.8. 2.8 父子项目
  4. 4. 3、服务注册中心
  5. 5. 4、注册微服务
  6. 6. 5、访问微服务
    1. 6.1. 5.1 Ribbon
    2. 6.2. 5.2 Feign
  7. 7. 6、服务链路追踪
  8. 8. 7、配置服务器
  9. 9. 8、配置客户端
  10. 10. 9、消息总线
  11. 11. 10、断路器
    1. 11.1. 10.1 断路器监控
    2. 11.2. 10.1 断路器聚合监控
  12. 12. 11、网关 Zuul
隐藏目录