特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我
视频地址: https://space.bilibili.com/31137138/favlist?fid=326428938
课件说明: 本次提供的课件是 Spring Cloud Netflix 版微服务架构指南,如果有兴趣想要学习 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相关课程资源
案例代码: https://github.com/topsale/hello-spring-cloud-netflix
概述
Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用 Spring Cloud 开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式环境中运行良好,包括开发人员自己的笔记本电脑,裸机数据中心,以及 Cloud Foundry 等托管平台
项目进入维护期
【官方新闻】Spring Cloud Greenwich.RC1 available now
2018 年 12 月 12 日,Netflix 宣布 Spring Cloud Netflix 系列技术栈进入维护模式(不再添加新特性)
最近,Netflix 宣布 Hystrix
正在进入维护模式。自 2016 年以来,Ribbon
已处于类似状态。虽然 Hystrix 和 Ribbon 现已处于维护模式,但它们仍然在 Netflix 大规模部署。
Hystrix Dashboard
和 Turbine
已被 Atlas 取代。这些项目的最后一次提交分别是 2 年前和 4 年前。Zuul1
和 Archaius1
都被后来不兼容的版本所取代。
以下 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
什么是维护模式
将模块置于维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能。我们将修复 block 级别的 bug 以及安全问题,我们也会考虑并审查社区的小型 pull request
替代品
我们建议对这些模块提供的功能进行以下替换
CURRENT | REPLACEMENT |
---|---|
Hystrix | Resilience4j |
Hystrix Dashboard / Turbine | Micrometer + Monitoring System |
Ribbon | Spring Cloud Loadbalancer |
Zuul 1 | Spring Cloud Gateway |
Archaius 1 | Spring Boot external config + Spring Cloud Config |
创建项目工程
Spring Cloud 项目都是基于 Spring Boot 进行开发,并且都是使用 Maven 做项目管理工具。在实际开发中,我们一般都会创建一个依赖管理项目作为 Maven 的 Parent 项目使用,这样做可以极大的方便我们对 Jar 包版本的统一管理
POM
<?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 https://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.funtl</groupId>
<artifactId>hello-spring-cloud-netflix</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>dependencies</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.funtl</groupId>
<artifactId>dependencies</artifactId>
<version>${project.version}</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>
创建统一的依赖管理
POM
<?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.funtl</groupId>
<artifactId>dependencies</artifactId>
<version>0.0.1-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>
服务注册与发现(Eureka)
POM
- 创建一个名为
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.funtl</groupId>
<artifactId>hello-spring-cloud-netflix</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>discovery</artifactId>
<packaging>pom</packaging>
<modules>
<module>discovery-eureka</module>
</modules>
</project>
- 创建一个名为
discovery-eureka
的项目,它是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.funtl</groupId>
<artifactId>discovery</artifactId>
<version>0.0.1-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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.funtl.hello.spring.cloud.eureka.DiscoveryEurekaApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Application
启动一个服务注册中心,只需要一个注解 @EnableEurekaServer
package com.funtl.hello.spring.cloud.eureka;
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 是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下 Erureka Server 也是一个 Eureka Client , 必须要指定一个 Server
spring:
application:
name: discovery-eureka
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
通过 eureka.client.registerWithEureka:false
和 fetchRegistry:false
来表明自己是一个 Eureka Server
通过浏览器访问
Eureka Server 是有界面的,启动工程,打开浏览器访问:http://localhost:8761
创建服务提供者
当 Client 向 Server 注册时,它会提供一些元数据,例如主机和端口,URL,主页等。Eureka Server 从每个 Client 实例接收心跳消息。 如果心跳超时,则通常将该实例从注册 Server 中删除
POM
- 创建一个名为
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.funtl</groupId>
<artifactId>hello-spring-cloud-netflix</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>provider</artifactId>
<packaging>pom</packaging>
<modules>
<module>provider-admin-service</module>
</modules>
</project>
- 创建一个名为
provider-admin-service
的项目,它是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.funtl</groupId>
<artifactId>provider</artifactId>
<version>0.0.1-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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.funtl.hello.spring.cloud.provider.ProviderAdminApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Application
通过注解 @EnableEurekaClient
表明自己是一个 Eureka Client
package com.funtl.hello.spring.cloud.provider;
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:
main:
allow-bean-definition-overriding: true
application:
name: provider-admin
server:
port: 11000
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8761/eureka/
注意: 需要指明
spring.application.name
,这个很重要,这在以后的服务与服务之间相互调用一般都是根据这个name
Controller
package com.funtl.hello.spring.cloud.provider.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;
}
}
通过浏览器访问
启动工程,打开 http://localhost:8761 ,即 Eureka Server 的网址:
你会发现一个服务已经注册在服务中了,服务名为 PROVIDER-ADMIN
, 端口为 11000
,这时打开 http://localhost:11000/hi ,你会在浏览器上看到 :
Hello Eureka, i am from port: 11000
创建服务消费者(Feign)
关于 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
POM
- 创建一个名为
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.funtl</groupId>
<artifactId>hello-spring-cloud-netflix</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>business</artifactId>
<packaging>pom</packaging>
<modules>
<module>business-admin-feign</module>
<module>business-admin-service</module>
</modules>
</project>
- 创建一个名为
business-admin-feign
的项目,它是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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.funtl</groupId>
<artifactId>business</artifactId>
<version>0.0.1-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>
- 创建一个名为
business-admin-service
的项目,它是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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.funtl</groupId>
<artifactId>business</artifactId>
<version>0.0.1-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.funtl</groupId>
<artifactId>business-admin-feign</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.funtl.hello.spring.cloud.business.BusinessAdminApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
注意: 以上方式是为了利用 Maven 模块化功能提高 Feign 的复用性
Feign
通过 @FeignClient("服务名")
注解来指定调用哪个服务
package com.funtl.hello.spring.cloud.business.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "provider-admin")
public interface BusinessAdminFeign {
@GetMapping(value = "hi")
String sayHi();
}
Application
通过 @EnableFeignClients
注解开启 Feign 功能
package com.funtl.hello.spring.cloud.business;
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
server:
port: 12000
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8761/eureka/
Controller
package com.funtl.hello.spring.cloud.business.controller;
import com.funtl.hello.spring.cloud.business.feign.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();
}
}
测试负载均衡
修改 provider-admin-service
项目的 application.yml
中的 server.port
,以启动多个实例,通过浏览器请求:http://localhost:12000/hi ,此时浏览器交替显示
Hello Eureka, i am from port: 11000
Hello Eureka, i am from port: 11001
增加服务熔断功能(Hystrix)
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 RPC
相互调用,在 Spring Cloud 中可以用 RestTemplate + Ribbon
和 Feign
来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet
容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应
为了解决这个问题,业界提出了熔断器模型。Netflix 开源了 Hystrix 组件,实现了熔断器模式,Spring Cloud 对这一组件进行了整合。在微服务架构中,一个请求需要调用多个服务是非常常见的
较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystrix 是 5 秒 20 次) 熔断器将会被打开
熔断器打开后,为了避免连锁故障,通过 fallback
方法可以直接返回一个固定值
开启熔断器
Feign 是自带熔断器的,但默认是关闭的。需要在配置文件中配置打开它,在配置文件增加以下代码
feign:
hystrix:
enabled: true
创建熔断类
在 business-admin-feign
项目中增加熔断类,如果请求失败或超时或异常则会触发熔断并返回一个固定结果
package com.funtl.hello.spring.cloud.business.feign.fallback;
import com.funtl.hello.spring.cloud.business.feign.BusinessAdminFeign;
import org.springframework.stereotype.Component;
@Component
public class BusinessAdminFallback implements BusinessAdminFeign {
@Override
public String sayHi() {
return "请求失败了,请重试...";
}
}
声明熔断类
在 @FeignClient
注解中增加 fallback
属性声明熔断类
package com.funtl.hello.spring.cloud.business.feign;
import com.funtl.hello.spring.cloud.business.feign.fallback.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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.funtl</groupId>
<artifactId>hello-spring-cloud-netflix</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>addons</artifactId>
<packaging>pom</packaging>
<modules>
<module>addons-hystrix-dashboard</module>
<module>addons-hystrix-turbine</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.funtl</groupId>
<artifactId>addons</artifactId>
<version>0.0.1-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>
- 在
addons-hystrix-dashboard
项目中创建熔断器配置类
package com.funtl.hello.spring.cloud.addons.configuration;
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);
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.funtl</groupId>
<artifactId>addons</artifactId>
<version>0.0.1-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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.funtl.hello.spring.cloud.addons.HystrixTurbineApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
addons-hystrix-turbine
项目的 Application,通过注解@EnableTurbine
,@EnableHystrixDashboard
开启 Hystrix Dashboard 和 Turbine 收集功能
package com.funtl.hello.spring.cloud.addons;
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);
}
}
-
addons-hystrix-turbine
项目的application.yml
spring:
main:
allow-bean-definition-overriding: true
application:
name: addons-hystrix-turbine
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
消费者开启熔断监控
- 修改 POM,增加刚才创建的
com.funtl:addons-hystrix-dashboard
项目依赖
<dependency>
<groupId>com.funtl</groupId>
<artifactId>addons-hystrix-dashboard</artifactId>
<version>${project.parent.version}</version>
</dependency>
- 修改 Application 中的
@SpringBootApplication
注解里包扫描路径,让 Spring 可以扫描到addons-hystrix-dashboard
项目中的 Java 配置
package com.funtl.hello.spring.cloud.business;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableHystrix
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = "com.funtl.hello.spring.cloud")
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/
测试熔断收集功能
- 查看监控流:http://localhost:8847/turbine.stream?cluster=business ,浏览器输出如下
: ping
data: {"reportingHostsLast10Seconds":1,"name":"meta","type":"meta","timestamp":1575400020449}
: ping
data: {"reportingHostsLast10Seconds":1,"name":"meta","type":"meta","timestamp":1575400023450}
: ping
data: {"reportingHostsLast10Seconds":1,"name":"meta","type":"meta","timestamp":1575400026452}
: ping
data: {"reportingHostsLast10Seconds":1,"name":"meta","type":"meta","timestamp":1575400029453}
注意: 在没有访问消费者接口的情况下只会打印 PING 信息
- 调用消费者接口(多调用几次,还需要等待一小会),观察监控流信息变化,可以发现已经收集到了集群信息
: ping
data: {"rollingCountFallbackSuccess":0,"rollingCountFallbackFailure":0,"propertyValue_circuitBreakerRequestVolumeThreshold":20,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"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":5000,"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":10,"errorPercentage":0,"rollingCountThreadPoolRejected":0,"propertyValue_circuitBreakerEnabled":true,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"propertyValue_requestCacheEnabled":true,"rollingCountFallbackRejection":0,"propertyValue_requestLogEnabled":true,"rollingCountFallbackEmit":0,"rollingCountSuccess":0,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_circuitBreakerErrorThresholdPercentage":50,"propertyValue_circuitBreakerForceClosed":false,"name":"BusinessAdminFeign#sayHi()","reportingHosts":1,"propertyValue_executionIsolationThreadPoolKeyOverride":"null","propertyValue_executionIsolationThreadTimeoutInMilliseconds":1000,"propertyValue_executionTimeoutInMilliseconds":1000}
- 进入 Hystrix Dashboard 控制台集中查看监控信息,访问:http://localhost:8847/hystrix
- 停止服务提供者,触发消费者熔断,观察控制台,此时可以看到控制台监控到了集群状态
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 |
监控界面指标说明
常用配置说明
使用方法为在 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 个(10s 内请求失败数量达到 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 结合实现了负载均衡的功能
POM
- 创建一个名为
gateway
的项目,网关可以有多种选型(zuul
,spring 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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.funtl</groupId>
<artifactId>hello-spring-cloud-netflix</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<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.funtl</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-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>
</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>
Application
增加 @EnableZuulProxy
注解开启 Zuul 功能
package com.funtl.hello.spring.cloud.gateway;
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
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
路由失败时回调
路由的服务无法请求时也需要触发熔断功能,通过实现 FallbackProvider
接口可以直接返回固定结果给客户端
package com.funtl.hello.spring.cloud.gateway.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;
/**
* 路由 business-admin 失败时的回调
* <p>
* Description:
* </p>
*
* @author Lusifer
* @version v1.0.0
* @date 2019-12-04 16:09:44
* @see com.funtl.hello.spring.cloud.gateway.fallback
*
*/
@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
服务链路追踪(ZipKin)
ZipKin 是一个开放源代码的分布式跟踪系统,由 Twitter 公司开源,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。它的理论模型来自于 Google Dapper 论文
每个服务向 ZipKin 报告计时数据,ZipKin 会根据调用关系通过 ZipKin UI 生成依赖关系图,显示了多少跟踪请求通过每个服务,该系统让开发者可通过一个 Web 前端轻松的收集和分析数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈
为什么需要链路追踪
微服务架构是通过业务来划分服务的,使用 REST 调用。对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂
随着服务的越来越多,对调用链的分析会越来越复杂。它们之间的调用关系也许如下
术语解释
- Span:基本工作单元,例如,在一个新建的 Span 中发送一个 RPC 等同于发送一个回应请求给 RPC,Span 通过一个 64 位 ID 唯一标识,Trace 以另一个 64 位 ID 表示。
- Trace:一系列 Spans 组成的一个树状结构,例如,如果你正在运行一个分布式大数据工程,你可能需要创建一个 Trace。
- Annotation:用来即使记录一个事件的存在,一些核心 Annotations 用来定义一个请求的开始和结束
- cs:Client Sent,客户端发起一个请求,这个 Annotation 描述了这个 Span 的开始
- sr:Server Received,服务端获得请求并准备开始处理它,如果将其 sr 减去 cs 时间戳便可得到网络延迟
- ss:Server Sent 表明请求处理的完成(当请求返回客户端),如果 ss 减去 sr 时间戳便可得到服务端需要的处理请求时间
- cr:Client Received 表明 Span 的结束,客户端成功接收到服务端的回复,如果 cr 减去 cs 时间戳便可得到客户端从服务端获取回复的所有所需时间
将 Span 和 Trace 在一个系统中使用 Zipkin 注解的过程图形化:
部署链路追踪
我们可以使用 Docker 快速部署 ZipKin 服务器,docker-compose.yml
配置文件如下
version: '3.1'
services:
zipkin:
image: openzipkin/zipkin
container_name: zipkin
ports:
- 9411:9411
通过浏览器访问:http://your_host:9411/zipkin/
配置链路追踪
- 在所有需要被追踪的项目(包括 Eureka Server)中增加
spring-cloud-starter-zipkin
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
- 在这些项目的
application.yml
配置文件中增加 Zipkin Server 的地址即可
spring:
zipkin:
base-url: http://localhost:9411
外部化配置中心(Config)
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在 Spring Cloud 中,有分布式配置中心组件 Spring Cloud Config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程 Git 仓库中。在 Spring Cloud Config 组件中,分两个角色,一是 Config Server,二是 Config Client
配置中心服务端
- 在
addons
项目中增加子项目addons-cloud-config
,POM 配置文件如下
<?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.funtl</groupId>
<artifactId>addons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<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>
- 创建 Application,通过
@EnableConfigServer
注解,开启配置服务器功能
package com.funtl.hello.spring.cloud.addons;
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 配置文件中增加相关配置,此处我们使用 GitLab 仓库存放我们的配置文件,其中 Spring Cloud Config 默认端口为 8888
spring:
main:
allow-bean-definition-overriding: true
application:
name: addons-cloud-config
cloud:
config:
label: master
server:
git:
uri: http://10.3.50.121:8080/root/hello-spring-cloud-netflix.git
search-paths: respo
username: root
password: 12345678
server:
port: 8888
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8761/eureka/
注意: 如果使用 GitLab 作为仓库的话,
git.uri
需要在结尾加上.git
,GitHub 则不用
部署测试用配置
在项目中创建一个名为 respo
的目录用于存放配置文件,创建一个名为 config-client-dev.yml
的配置文件用于测试配置服务器是否生效
foo: foo version 1
demo:
message: Hello Spring Cloud Config
测试配置中心
浏览器端访问:http://localhost:8888/config-client/dev/master 显示如下
<Environment>
<name>config-client</name>
<profiles>
<profiles>dev</profiles>
</profiles>
<label>master</label>
<version>fcaca1fd6b3f95cb577f55d075a24f79d5e6a4a7</version>
<state/>
<propertySources>
<propertySources>
<name>
http://10.3.50.121:8080/root/hello-spring-cloud-netflix.git/respo/config-client-dev.yml
</name>
<source>
<foo>foo version 1</foo>
<demo.message>Hello Spring Cloud Config</demo.message>
</source>
</propertySources>
</propertySources>
</Environment>
能够成功读取出刚才上传的配置则说明配置中心部署成功
配置中心相关说明
-
配置文件
-
spring.cloud.config.label
:配置仓库的分支 -
spring.cloud.config.server.git.uri
:配置 Git 仓库地址(GitHub、GitLab、码云 ...) -
spring.cloud.config.server.git.search-paths
:配置仓库路径(存放配置文件的目录) -
spring.cloud.config.server.git.username
:访问 Git 仓库的账号 -
spring.cloud.config.server.git.password
:访问 Git 仓库的密码
-
-
HTTP 请求地址和资源文件映射
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>
- 增加配置
spring:
cloud:
config:
# 配置服务中心的地址
uri: http://localhost:8888
# 配置文件名称的前缀
name: config-client
# 配置仓库的分支
label: master
# 配置文件的环境标识(dev,test,prod)
profile: dev
# 增加健康检查配置
# 这里的目的是开启 actuator/refresh 接口用于刷新配置
management:
endpoint:
shutdown:
enabled: false
endpoints:
web:
exposure:
include: "*"
- 在 Controller 中获取动态配置,通过
@RefreshScope
注解开启刷新配置功能,可以使用DynamicPropertyFactory
动态获取配置内容
package com.funtl.hello.spring.cloud.business.controller;
import com.funtl.hello.spring.cloud.business.feign.BusinessAdminFeign;
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;
import javax.annotation.Resource;
@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
接口才可以使新配置生效,我们可以使用 Postman 测试
- 通过浏览器访问对应接口查看效果即可
Spring Boot Profile
我们在做项目开发的时候,生产环境和测试环境的一些配置可能会不一样,有时候一些功能也可能会不一样,所以我们可能会在上线的时候手工修改这些配置信息。但是 Spring 中为我们提供了 Profile 这个功能。我们只需要在启动的时候添加一个虚拟机参数,激活自己环境所要用的 Profile 就可以了。
操作起来很简单,只需要为不同的环境编写专门的配置文件,如:application-dev.yml
、application-prod.yml
, 启动项目时只需要增加一个命令参数 --spring.profiles.active=环境配置
即可,启动命令如下
java -jar app.jar --spring.profiles.active=prod
特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我
视频地址: https://space.bilibili.com/31137138/favlist?fid=326428938
课件说明: 本次提供的课件是 Spring Cloud Netflix 版微服务架构指南,如果有兴趣想要学习 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相关课程资源
案例代码: https://github.com/topsale/hello-spring-cloud-netflix