一、 概述

Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具。例如配置管理,服务发现,断路器,智能路由,微代理,控制总线。分布式系统的协调导致了样板模式, 使用 Spring Cloud 开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式环境中运行良好,包括开发人员自己的笔记本电脑,裸机数据中心,以及 Cloud Foundry 等托管平台。

二、维护

2018 年 12 月 12 日,Netflix 宣布 Spring Cloud Netflix 系列技术栈进入维护模式,不再添加新特性。

以下 Spring Cloud Netflix 模块和相应的 Starter 将进入维护模式:

  • spring-cloud-netflix-archaius
  • spring-cloud-netflix-hystrix-contract
  • spring-cloud-netflix-hystrix-dashboard
  • spring-cloud-netflix-hystrix-stream
  • spring-cloud-netflix-hystrix
  • spring-cloud-netflix-ribbon
  • spring-cloud-netflix-turbine-stream
  • spring-cloud-netflix-turbine
  • spring-cloud-netflix-zuul

三、创建主项目

项目结构

image-20200520094318458

创建一个基于SpringBoot的项目hello-spring-cloud-netflix

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.qiang</groupId>
    <artifactId>hello-spring-cloud-netflix</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>dependencies</module>
        <module>provider</module>
        <module>business</module>
        <module>addons</module>
        <module>gateway</module>
    </modules>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.qiang</groupId>
                <artifactId>dependencies</artifactId>
                <version>1.0.0-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <profiles>
        <profile>
            <id>default</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <spring-javaformat.version>0.0.15</spring-javaformat.version>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>io.spring.javaformat</groupId>
                        <artifactId>spring-javaformat-maven-plugin</artifactId>
                        <version>${spring-javaformat.version}</version>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <includes>
                                <include>**/*Tests.java</include>
                            </includes>
                            <excludes>
                                <exclude>**/Abstract*.java</exclude>
                            </excludes>
                            <systemPropertyVariables>
                                <java.security.egd>file:/dev/./urandom</java.security.egd>
                                <java.awt.headless>true</java.awt.headless>
                            </systemPropertyVariables>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-enforcer-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>enforce-rules</id>
                                <goals>
                                    <goal>enforce</goal>
                                </goals>
                                <configuration>
                                    <rules>
                                        <bannedDependencies>
                                            <excludes>
                                                <exclude>commons-logging:*:*</exclude>
                                            </excludes>
                                            <searchTransitive>true</searchTransitive>
                                        </bannedDependencies>
                                    </rules>
                                    <fail>true</fail>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-install-plugin</artifactId>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                        <inherited>true</inherited>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

    <repositories>
        <repository>
            <id>spring-milestone</id>
            <name>Spring Milestone</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshot</id>
            <name>Spring Snapshot</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <name>Spring Milestone</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshot</id>
            <name>Spring Snapshot</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

四、创建依赖管理项目

<?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.qiang</groupId>
    <artifactId>dependencies</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestone</id>
            <name>Spring Milestone</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshot</id>
            <name>Spring Snapshot</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <name>Spring Milestone</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshot</id>
            <name>Spring Snapshot</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

五、创建服务注册中心项目

创建discovery项目

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>hello-spring-cloud-netflix</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>discovery</artifactId>
    <packaging>pom</packaging>

    <modules>
        <module>discovery-eureka</module>
    </modules>
</project>

创建discovery-eureka项目

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>discovery</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>discovery-eureka</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!-- 链路追踪 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.qiang.DiscoveryEurekaApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

DiscoveryEurekaApplication.java

启动一个服务注册中心,只需要一个注解 @EnableEurekaServer

package com.qiang;

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

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryEurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(DiscoveryEurekaApplication.class, args);
    }
}

application.yml

通过 eureka.client.registerWithEureka:falsefetchRegistry:false 来表明自己是一个 Eureka Server

spring:
  application:
    name: discovery-eureka
  # 链路追踪
  zipkin:
    base-url: http://192.168.17.101:9411


server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动项目访问访问http://localhost:8761/

image-20200520094917971

六、创建提供者项目

创建provider项目

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>hello-spring-cloud-netflix</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>provider</artifactId>
    <packaging>pom</packaging>

    <modules>
        <module>provider-admin-service</module>
    </modules>
</project>

创建provider-admin-service项目

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>provider</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>provider-admin-service</artifactId>
    <packaging>jar</packaging>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 链路追踪 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <!-- 配置中心 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.qiang.ProviderAdminApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

ProviderAdminApplication.java

通过注解 @EnableEurekaClient 表明自己是一个 Eureka Client

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class ProviderAdminApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderAdminApplication.class, args);
    }

}

application.yml

spring:
  application:
    name: provider-admin
  cloud:
    config:
      # 配置服务中心的地址
      uri: http://localhost:8888
      # 配置文件名称的前缀
      name: config-client
      # 配置仓库的分支
      label: master
      # 配置文件的环境标识(dev,test,prod)
      profile: provider

server:
  port: 11000

# 增加健康检查配置,这里的目的是开启 actuator/refresh 接口用于刷新配置
management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:
    web:
      exposure:
        include: "*"

ProviderAdminController.java

package com.qiang.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderAdminController {

    @Value("${server.port}")
    private String port;

    @GetMapping(value = "hi")
    public String sayHi() {
        return "Hello Eureka, i am from port: " + port;
    }

}

启动项目

发现项目已经注册上去

image-20200520095531818

访问http://localhost:11000/hi

image-20200520095643267

七、创建消费者项目

什么是Ribbon

Ribbon 是一个负载均衡客户端,可以很好的控制 HTTP 和 TCP 的一些行为。在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于 HTTP RESTful 的。Spring Cloud 有两种服务调用方式,一种是 Ribbon + RestTemplate,另一种是 Feign。

什么是Feign

Feign 是一个声明式的伪 HTTP 客户端,它使得写 HTTP 客户端变得更简单。使用 Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用 Feign 注解和 JAX-RS 注解。Feign 支持可插拔的编码器和解码器。Feign 默认集成了 Ribbon,并和 Eureka 结合,默认实现了负载均衡的效果

  • Feign 采用的是基于接口的注解
  • Feign 整合了 Ribbon

创建business项目

<?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">
    <parent>
        <artifactId>hello-spring-cloud-netflix</artifactId>
        <groupId>com.qiang</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>business</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>business-admin-feign</module>
        <module>business-admin-service</module>
    </modules>


</project>

创建business-admin-feign项目

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>business</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>business-admin-feign</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>
</project>

BusinessAdminFeign.java

通过 @FeignClient("服务名") 注解来指定调用哪个服务

package com.qiang.service;

import com.qiang.feign.BusinessAdminFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "provider-admin",fallback = BusinessAdminFallback.class)
public interface BusinessAdminFeign {

    @GetMapping(value = "hi")
    String sayHi();

}

创建business-admin-service项目

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>business</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>business-admin-service</artifactId>
    <packaging>jar</packaging>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Projects -->
        <dependency>
            <groupId>com.qiang</groupId>
            <artifactId>business-admin-feign</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <!-- 链路追踪 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.qiang.BusinessAdminApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

BusinessAdminController.java

package com.qiang.controller;

import com.qiang.service.BusinessAdminFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BusinessAdminController {

    @Autowired
    private BusinessAdminFeign adminFeign;

    @GetMapping(value = "hi")
    public String sayHi() {
        return adminFeign.sayHi();
    }
}

BusinessAdminApplication.java

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class BusinessAdminApplication {

    public static void main(String[] args) {
        SpringApplication.run(BusinessAdminApplication.class, args);
    }
}

application.yml

spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: business-admin
  # 链路追踪
  zipkin:
    base-url: http://192.168.17.101:9411

server:
  port: 12000

eureka:
  instance:
    hostname: localhosts
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

feign:
  hystrix:
    enabled: true

image-20200520103944983

启动business-admin-service访问http://localhost:12000/hi,服务会通过BusinessAdminController的hi访问到BusinessAdminFeign的hi,最后到ProviderAdminController的hi

image-20200520104142069

image-20200520104243691

image-20200520104258911

通过修改provider-admin-serviceapplication.ymlserver.port启动多个实例,访问http://localhost:12000/hi会达到负载均衡的效果

Hello Eureka, i am from port: 11000
Hello Eureka, i am from port: 11001

八、服务熔断Hystrix

在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 RPC 相互调用,在 Spring Cloud 中可以用 RestTemplate + RibbonFeign 来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。

为了解决这个问题,业界提出了熔断器模型。Netflix 开源了 Hystrix 组件,实现了熔断器模式,Spring Cloud 对这一组件进行了整合。在微服务架构中,一个请求需要调用多个服务是非常常见的。

较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystrix 是 5 秒 20 次) 熔断器将会被打开。

熔断器打开后,为了避免连锁故障,通过 fallback 方法可以直接返回一个固定值。

开启熔断器

Feign 是自带熔断器的,但默认是关闭的。需要在配置文件中配置打开它,在配置文件增加以下代码。

feign:
  hystrix:
    enabled: true

创建熔断类

business-admin-feign 项目中增加熔断类,如果请求失败或超时或异常则会触发熔断并返回一个固定结果

package com.qiang.feign;

import com.qiang.service.BusinessAdminFeign;
import org.springframework.stereotype.Component;

@Component
public class BusinessAdminFallback implements BusinessAdminFeign {
    @Override
    public String sayHi() {
        return "请求失败了,请重试...";
    }
}

声明熔断类

@FeignClient 注解中增加 fallback 属性声明熔断类

package com.qiang.service;

import com.qiang.feign.BusinessAdminFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "provider-admin",fallback = BusinessAdminFallback.class)
public interface BusinessAdminFeign {

    @GetMapping(value = "hi")
    String sayHi();

}

验证熔断

此时我们关闭服务提供者,再次请求 http://localhost:12000/hi 浏览器会显示

请求失败了,请重试...

九、服务监控Turbine

使用场景

在复杂的分布式系统中,相同服务的结点经常需要部署上百甚至上千个,很多时候,运维人员希望能够把相同服务的节点状态以一个整体集群的形式展现出来,这样可以更好的把握整个系统的状态。 为此,Netflix 又提供了一个开源项目 Turbine 来提供把多个 hystrix.stream 的内容聚合为一个数据源供 Dashboard 展示。

什么是Turbine

Turbine 是聚合服务器发送事件流数据的一个工具,Hystrix 的监控中,只能监控单个节点,实际生产中都为集群,因此可以通过 Turbine 来监控集群下 Hystrix 的 Metrics 情况,Turbine 的 GitHub 地址:https://github.com/Netflix/Turbine

创建熔断监控中心

创建addons项目

<?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">
    <parent>
        <artifactId>hello-spring-cloud-netflix</artifactId>
        <groupId>com.qiang</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>addons</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>addons-hystrix-dashboard</module>
        <module>addons-hystrix-turbine</module>
        <module>addons-cloud-config</module>
    </modules>


</project>

创建addons-hystrix-dashboard项目,配置 HystrixMetricsStreamServlet 用以收集熔断信息。

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>addons</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>addons-hystrix-dashboard</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
    </dependencies>
</project>

HystrixDashboardConfiguration.java

package com.qiang.config;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HystrixDashboardConfiguration {

    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        //当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet,正数的值越小,该servlet的优先级越高,应用启动时就越先加载
        registrationBean.setLoadOnStartup(1);
        // 配置收集端点路径
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

}

创建addons-hystrix-turbine项目,用于统一收集集群中的熔断信息。

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>addons</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>addons-hystrix-turbine</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <!-- 链路追踪 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.qiang.HystrixTurbineApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

HystrixTurbineApplication.java

package com.qiang;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.turbine.EnableTurbine;

@EnableTurbine
@EnableHystrixDashboard
@EnableEurekaClient
@SpringBootApplication
public class HystrixTurbineApplication {

    public static void main(String[] args) {
        SpringApplication.run(HystrixTurbineApplication.class, args);
    }

}

application.yml

spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: addons-hystrix-turbine
  # 链路追踪
  zipkin:
    base-url: http://192.168.17.101:9411

server:
  port: 8847

eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

turbine:
  # 可以让同一主机上的服务通过主机名与端口号的组合来进行区分
  # 默认情况下会以 HOST 来区分不同的服务,这会使得在本机调试的时候,本机上的不同服务聚合成一个服务来统计
  combine-host-port: true
  # 配置监控服务的列表,表明监控哪些服务多个使用 "," 分割
  app-config: business-admin
  # 用于指定集群名称,当服务数量非常多的时候,可以启动多个
  cluster-name-expression: metadata['cluster']
  aggregator:
    # 指定聚合哪些集群,多个使用 "," 分割,默认为 default
    cluster-config: business
  # 用于替换源码 org.springframework.cloud.netflix.turbine.SpringClusterMonitor 中的收集端点
  # 我们配置的 Servlet 指向了 /hystrix.stream,Turbine 默认收集端点为 /actuator/hystrix.stream
  instanceUrlSuffix: /hystrix.stream

消费者开启熔断监控

修改消费者的business-admin-service服务的pom.xml

<!-- 开启熔断监控 -->
<dependency>
    <groupId>com.qiang</groupId>
    <artifactId>addons-hystrix-dashboard</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

修改 Application 中的 @SpringBootApplication 注解里包扫描路径,让 Spring 可以扫描到 addons-hystrix-dashboard 项目中的 Java 配置

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = "com.qiang")
public class BusinessAdminApplication {

    public static void main(String[] args) {
        SpringApplication.run(BusinessAdminApplication.class, args);
    }
}

修改 application.yml 配置

eureka:
  instance:
    hostname: localhost
    # 增加用于集群的配置,集群名为 business,与 Turbine 的配置匹配
    metadata-map:
      cluster: business
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

验证熔断收集功能

启动addons-hystrix-turbine服务访问http://localhost:8847/turbine.stream?cluster=business

在没有访问消费者接口的情况下只会打印 PING 信息,调用消费者接口(多调用几次,还需要等待一小会),观察监控流信息变化,可以发现已经收集到了集群信息。

: ping
data: {"rollingCountFallbackSuccess":0,"rollingCountFallbackFailure":0,"propertyValue_circuitBreakerRequestVolumeThreshold":40,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":20000,"latencyTotal_mean":0,"rollingMaxConcurrentExecutionCount":0,"type":"HystrixCommand","rollingCountResponsesFromCache":0,"rollingCountBadRequests":0,"rollingCountTimeout":0,"propertyValue_executionIsolationStrategy":"THREAD","rollingCountFailure":0,"rollingCountExceptionsThrown":0,"rollingCountFallbackMissing":0,"threadPool":"provider-admin","latencyExecute_mean":0,"isCircuitBreakerOpen":false,"errorCount":0,"rollingCountSemaphoreRejected":0,"group":"provider-admin","latencyTotal":{"0":0,"99":0,"100":0,"25":0,"90":0,"50":0,"95":0,"99.5":0,"75":0},"requestCount":0,"rollingCountCollapsedRequests":0,"rollingCountShortCircuited":0,"propertyValue_circuitBreakerSleepWindowInMilliseconds":10000,"latencyExecute":{"0":0,"99":0,"100":0,"25":0,"90":0,"50":0,"95":0,"99.5":0,"75":0},"rollingCountEmit":0,"currentConcurrentExecutionCount":0,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":20,"errorPercentage":0,"rollingCountThreadPoolRejected":0,"propertyValue_circuitBreakerEnabled":true,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"propertyValue_requestCacheEnabled":true,"rollingCountFallbackRejection":0,"rollingCountFallbackMopertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"propertyValue_requestLogEnabled":true,"rollingCountFallbackEmit":0,"rollingCountSuccess":0,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":20,"propertyValue_circuitBreakerErrorThresholdPercentage":100,"propertyValue_circuitBreakerForceClosed":false,"name":"BusinessAdminFeign#sayHi()","reportingHosts":1,"propertyValue_executionIsolationThreadPoolKeyOverride":"null","propertyValue_executionIsolationThreadTimeoutInMilliseconds":2000,"propertyValue_executionTimeoutInMilliseconds":2000}

data: {"currentCorePoolSize":10,"currentLargestPoolSize":2,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"currentActiveCount":0,"currentMaximumPoolSize":10,"currentQueueSize":0,"type":"HystrixThreadPool","currentTaskCount":2,"currentCompletedTaskCount":2,"rollingMaxActiveThreads":0,"rollingCountCommandRejections":0,"name":"provider-admin","reportingHosts":1,"currentPoolSize":2,"propertyValue_queueSizeRejectionThreshold":5,"rollingCountThreadsExecuted":0}

data: {"reportingHostsLast10Seconds":1,"name":"meta","type":"meta","timestamp":1589955798441}

: ping
data: {"reportingHostsLast10Seconds":1,"name":"meta","type":"meta","timestamp":1589955801442}

: ping
data: {"reportingHostsLast10Seconds":1,"name":"meta","type":"meta","timestamp":1589955804443}

进入 Hystrix Dashboard 控制台集中查看监控信息,访问:http://localhost:8847/hystrix

image-20200520142617522

image-20200520142741829

停止服务提供者,触发消费者熔断,观察控制台,此时可以看到控制台监控到了集群状态。

image-20200520142935619

十、Hystrix 相关说明

什么情况下会触发熔断

类型 描述 触发 fallback
EMIT 值传递 NO
SUCCESS 执行完成,没有错误 NO
FAILURE 执行抛出异常 YES
TIMEOUT 执行开始,但没有在允许的时间内完成 YES
BAD_REQUEST 执行抛出 HystrixBadRequestException NO
SHORT_CIRCUITED 断路器打开,不尝试执行 YES
THREAD_POOL_REJECTED 线程池拒绝,不尝试执行 YES
SEMAPHORE_REJECTED 信号量拒绝,不尝试执行 YES

什么情况下会抛出异常

类型 描述 抛异常
FALLBACK_EMIT Fallback 值传递 NO
FALLBACK_SUCCESS Fallback 执行完成,没有错误 NO
FALLBACK_FAILURE Fallback 执行抛出出错 YES
FALLBACK_REJECTED Fallback 信号量拒绝,不尝试执行 YES
FALLBACK_MISSING 没有 Fallback 实例 YES

监控界面指标说明

image-20200520143151863

image-20200520143239390

常用配置

使用方法为在 Feign 接口的调用方法上增加注解,案例代码如下

@HystrixCommand(commandProperties = {
     @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
  • 超时时间(默认 1000ms,单位:ms)
    • execution.isolation.thread.timeoutInMilliseconds:在调用方配置,被该调用方的所有方法的超时时间都是该值,优先级低于下边的指定配置
    • execution.isolation.thread.timeoutInMilliseconds:在调用方配置,被该调用方的指定方法(HystrixCommandKey 方法名)的超时时间是该值
  • 线程池核心线程数
    • coreSize:默认为 10
  • Queue
    • maxQueueSize:最大排队长度。默认 -1,使用 SynchronousQueue。其他值则使用 LinkedBlockingQueue。如果要从 -1 换成其他值则需重启,即该值不能动态调整,若要动态调整,需要使用到下边这个配置
    • queueSizeRejectionThreshold:排队线程数量阈值,默认为 5,达到时拒绝,如果配置了该选项,队列的大小是该队列(注意:如果 maxQueueSize=-1 的话,则该选项不起作用
  • 断路器
    • circuitBreaker.requestVolumeThreshold:当在配置时间窗口内达到此数量的失败后,进行短路。默认 20 个(5s 内请求失败数量达到 20 个,断路器开)
    • circuitBreaker.sleepWindowInMilliseconds:短路多久以后开始尝试是否恢复,默认 5s
    • circuitBreaker.errorThresholdPercentage:出错百分比阈值,当达到此阈值后,开始短路。默认 50%
  • fallback
    • fallback.isolation.semaphore.maxConcurrentRequests:调用线程允许请求 HystrixCommand.GetFallback() 的最大数量,默认 10。超出时将会有异常抛出,注意:该项配置对于 THREAD 隔离模式也起作用

详细参数说明: https://github.com/Netflix/Hystrix/wiki/Configuration

十一、 服务网关Zuul

在 Spring Cloud 微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(Zuul、Ngnix),再到达服务网关(Zuul 集群),然后再到具体的服。服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理,配置服务的配置文件放在 GIT 仓库,方便开发人员随时改配置。

Zuul 的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如 /api/user 转发到到 User 服务,/api/shop 转发到到 Shop 服务。Zuul 默认和 Ribbon 结合实现了负载均衡的功能。


创建一个名为 gateway 的项目,网关可以有多种选型(zuulspring cloud gateway)也可以针对不同的场景(对内的网关,对外的网关,针对某种业务类型的网关,开发平台的网关等)

<?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">
    <parent>
        <artifactId>hello-spring-cloud-netflix</artifactId>
        <groupId>com.qiang</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gateway</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>gateway-zuul-business</module>
    </modules>


</project>

创建一个名为 gateway-zuul-business 的项目,这是使用 zuul 实现的针对业务的网关。

<?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>

    <parent>
        <groupId>com.qiang</groupId>
        <artifactId>gateway</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>gateway-zuul-business</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <!-- 链路追踪 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.funtl.hello.spring.cloud.gateway.ZuulBusinessApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

增加 @EnableZuulProxy 注解开启 Zuul 功能。

ZuulBusinessApplication.java

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulBusinessApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulBusinessApplication.class, args);
    }
}

application.yml

spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: gateway-zuul-business
  # 链路追踪
  zipkin:
    base-url: http://192.168.17.101:9411

server:
  port: 8080

eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

zuul:
  routes:
    api-business-admin:
      # 以 /api/business/admin 开头的请求都转发给 business-admin 服务
      path: /api/business/admin/**
      serviceId: business-admin

路由失败时回调

BusinessAdminFallbackProvider.java

package com.qiang.fallback;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

@Component
public class BusinessAdminFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        // ServiceId,如果需要所有调用都支持回退,则 return "*" 或 return null
        return "business-admin";
    }

    /**
     * 如果请求服务失败,则返回指定的信息给调用者
     *
     * @param route
     * @param cause
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            /**
             * 网关向 api 服务请求失败了,但是消费者客户端向网关发起的请求是成功的,
             * 不应该把 api 的 404,500 等问题抛给客户端
             * 网关和 api 服务集群对于客户端来说是黑盒
             * @return
             * @throws IOException
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.OK.value();
            }

            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                ObjectMapper objectMapper = new ObjectMapper();
                Map<String, Object> map = new HashMap<>();
                map.put("status", 200);
                map.put("message", "无法连接,请检查您的网络");
                return new ByteArrayInputStream(objectMapper.writeValueAsString(map).getBytes("UTF-8"));
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                // 和 getBody 中的内容编码一致
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

验证路由网关

访问:http://localhost:8080/api/business/admin/hi

成功时:

Hello Eureka, i am from port: 11000

失败时:

image-20200520144738798

十二、 链路追踪ZipKin

ZipKin 是一个开放源代码的分布式跟踪系统,由 Twitter 公司开源,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。它的理论模型来自于 Google Dapper 论文。

每个服务向 ZipKin 报告计时数据,ZipKin 会根据调用关系通过 ZipKin UI 生成依赖关系图,显示了多少跟踪请求通过每个服务,该系统让开发者可通过一个 Web 前端轻松的收集和分析数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈。

微服务架构是通过业务来划分服务的,使用 REST 调用。对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂。


在所有需要被追踪的项目(包括 Eureka Server)中增加 spring-cloud-starter-zipkin 依赖。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

在这些项目的 application.yml 配置文件中增加 Zipkin Server 的地址即可。

spring:
  application:
    name: discovery-eureka
  # 链路追踪
  zipkin:
    base-url: http://192.168.17.101:9411

部署ZipKin

docker-compose

version: '3.0'
services:
  zipkin:
    image: openzipkin/zipkin
    restart: always
    container_name: zipkin
    ports:
      - 9411:9411

访问http://192.168.17.101:9411/zipkin

image-20200520145728705

十三、 分布式配置config

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


配置中心服务端

addons创建项目addons-cloud-config

<?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">
    <parent>
        <artifactId>addons</artifactId>
        <groupId>com.qiang</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>addons-cloud-config</artifactId>
    <packaging>jar</packaging>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.funtl.hello.spring.cloud.addons.CloudConfigApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

CloudConfigApplication.java

通过 @EnableConfigServer 注解,开启配置服务器功能

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;

@EnableConfigServer
@EnableDiscoveryClient
@SpringBootApplication
public class CloudConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudConfigApplication.class, args);
    }
}

application.yml

spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: addons-cloud-config
  cloud:
    config:
      # 配置仓库的分支
      label: master
      server:
        git:
          # 配置 Git 仓库地址 GitLab 作为仓库的话,git.uri 需要在结尾加上 .git,GitHub 则不用。
          uri: https://github.com/QForever/SpringCloudNetflix
          # 目录用于存放配置文件
          search-paths: hello-spring-cloud-netflix/respo
          # git的账号
          username: ********
          # git的密码
          password: ********
          # 强制拉取
          force-pull: true
          # https配置
          skipSslValidation: true

server:
  port: 8888

eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

在项目中创建一个名为 respo 的目录用于存放配置文件,创建一个名为 config-client-dev.yml 的配置文件用于测试配置服务器是否生效。

config-client-dev.yml

foo: foo version 1
demo:
  message: Hello Spring Cloud Config

访问http://localhost:8888/config-client/dev/master

image-20200520151234671

请求地址和资源文件映射

  • http://ip:port/{application}/{profile}[/{label}]
  • http://ip:port/{application}-{profile}.yml
  • http://ip:port/{label}/{application}-{profile}.yml
  • http://ip:port/{application}-{profile}.properties
  • http://ip:port/{label}/{application}-{profile}.properties

配置中心客户端

在所有需要使用配置中心的服务添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

增加配置,这里在provider添加。

spring:
  application:
    name: provider-admin
  cloud:
    config:
      # 配置服务中心的地址
      uri: http://localhost:8888
      # 配置文件名称的前缀
      name: config-client
      # 配置仓库的分支
      label: master
      # 配置文件的环境标识(dev,test,prod)
      profile: provider

server:
  port: 11000

# 增加健康检查配置,这里的目的是开启 actuator/refresh 接口用于刷新配置
management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:
    web:
      exposure:
        include: "*"

在 Controller 中获取动态配置,通过 @RefreshScope 注解开启刷新配置功能,可以使用 DynamicPropertyFactory 动态获取配置内容。

BusinessAdminController.java

package com.qiang.controller;

import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicStringProperty;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RefreshScope
@RestController
public class BusinessAdminController {
    @GetMapping(value = "hello")
    public String sayHello() {
        DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty("demo.message", "Hello World");
        return property.getValue();
    }
}

刷新配置,修改配置文件后并不会自动刷新配置,需要手动 POST 请求服务的 actuator/refresh 接口才可以使新配置生效。

http://localhost:11000/actuator/refresh

image-20200520153318751

Spring Boot Profile

我们在做项目开发的时候,生产环境和测试环境的一些配置可能会不一样,有时候一些功能也可能会不一样,所以我们可能会在上线的时候手工修改这些配置信息。但是 Spring 中为我们提供了 Profile 这个功能。我们只需要在启动的时候添加一个虚拟机参数,激活自己环境所要用的 Profile 就可以了。

操作起来很简单,只需要为不同的环境编写专门的配置文件,如:application-dev.ymlapplication-prod.yml, 启动项目时只需要增加一个命令参数 --spring.profiles.active=环境配置 即可,启动命令如下。

java -jar app.jar --spring.profiles.active=prod