本文是 SpringCloud 实战教程第三篇, Spring Cloud 基于 Hoxton.SR8
版本,Spring Boot 基于2.3.5.RELEASE
,使用Spring Cloud Alibaba相关组件。
一、组件选型
模块 | 组件 | 版本 |
---|---|---|
服务注册与发现 | Spring Cloud Nacos | |
声明式服务调用 | OpenFeign | |
服务网关 | Spring Cloud GateWay | |
负载均衡 | Spring Cloud Loadbalancer | |
服务保护(限流降级) | Sentienl | |
配置中心 | Spring Cloud Nacos | |
链路跟踪 | Spring Cloud Sleuth + Zipkin | |
分布式事务 | Seata |
二、服务注册和发现
1、安装并运行 Nacos
获取 Docker 镜像。当前最新版本1.8.5。
$ docker pull consul:1.8.5
通过列出匹配的Docker镜像来检查映像是否已下载 consul
$ docker images -f 'reference=consul'
REPOSITORY TAG IMAGE ID CREATED SIZE
consul 1.8.5 4f7b214361a7 3 weeks ago 122MB
配置并运行 Consul 服务器:
docker run -d \
--name ConsulDiscovery \
-p 8500:8500 \
-p 8600:8600/udp \
consul:1.8.5 agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0
由于以分离模式 -d
启动了容器,因此该进程将在后台运行。Consul Docker 镜像设置 /consul/config
为 Consul 的默认配置目录,代理将加载该目录中放置的所有配置文件。
在命令行查看版本号:
$ docker exec ConsulDiscovery consul -v
Consul v1.8.5
Revision 1e03567d3
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
服务已经启动,在浏览器中访问:http://localhost:8050,显示的界面如下:
2、服务注册到 Nacos
首先创建一个主 Maven 工程,在其 pom 文件引入依赖,Spring Boot版本为 2.3.5.RELEASE
,Spring Cloud版本为 Hoxton.SR8
。这个pom文件作为父 pom 文件,起到依赖版本控制的作用,其他 module 工程继承该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>cn.lixl</groupId>
<artifactId>SpringCloudDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>SpringCloudDemo</name>
<description>Demo project for Spring Cloud</description>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<modules>
<module>eureka-server</module>
<module>eureka-client</module>
<module>consul-provider</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建 model 工程作为服务提供者,即: consul-provider
。以 IDEA 工具为例:
编辑新创建工程的 pom.xml 文件,继承父 pom 文件,引入 spring-cloud-starter-netflix-eureka-server
的依赖:
<parent>
<groupId>cn.lixl</groupId>
<artifactId>SpringCloudDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>consul-provider2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consul-provider2</name>
<description>Demo project for Spring Cloud</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
通过在 application 启动类上加注解 @EnableDiscoveryClient
表明自己是一个 Discovery client:
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class ConsulProviderApplication {
@Value("${server.port}")
String port;
@Value("${spring.application.name}")
String appName;
@RequestMapping("/")
public String home(){
return "Consul service name: " + appName +", port: " + port;
}
public static void main(String[] args) {
SpringApplication.run(ConsulProviderApplication.class, args);
}
}
在配置文件中注明服务注册中心地址,application.yml 配置文件如下:
server:
port: 8763
spring:
application:
name: consul-provider
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: consul-provider #注册到 Consul 的服务名称,客户端根据这个名称来进行服务调用。
为了模拟注册负载均衡,需要启动 2 个 consul-producer
模块。在 IDEA 工具中,复制 consul-producer
启动配置,覆盖运行端口为: 8764
。如图:
依次启动两个服务提供者项目,在浏览器上访问 http://localhost:8500,可以看到 consul-provider
服务已经注册到 Consul 上了。
点进去后可以看到有 2 个服务提供实例。这样服务提供者就准备好了。
三、基于 OpenFeign 调用服务
1、OpenFeign 介绍
Spring Cloud OpenFeign 是一种声明式、模板化的服务调用组件。简化了 RestTemplate
代码,实现了 Ribbon 负载均衡,使代码变得更加简洁。
OpenFeign 的使用主要分为以下几个步骤:
- 服务消费者添加 Feign 依赖;
- 创建业务层接口,添加
@FeignClient
注解声明需要调用的服务; - 业务层抽象方法使用 SpringMVC 注解配置服务地址及参数;
- 启动类添加
@EnableFeignClients
注解激活 Feign 组件。
2、声明式服务调用
与创建服务提供者方法一致,创建服务消费者 model 工程,即: consul-consumer
。编辑 pom.xml 文件,引入 spring-cloud-starter-openfeign
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
application.yml 配置文件如下:
server:
port: 8765
spring:
application:
name: consul-consumer
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: consul-consumer
register: true # 是否将自己注册到注册中心,默认为 true
定义业务层接口,并通过注解 @FeignClient(value = "服务名")
绑定到服务提供方,在抽象方法上使用 SpringMVC 注解配置服务地址及参数:
@FeignClient(value = "consul-provider")
public interface ConsumerService {
@RequestMapping(value = "/",method = RequestMethod.GET)
String callConsulProvider();
}
在 application 启动类上加注解 @EnableFeignClients
,启用 Feign 进行远程调用:
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@EnableFeignClients
public class ConsulConsumerApplication {
@Autowired
ConsumerService consumerService;
@Value("${server.port}")
String port;
@Value("${spring.application.name}")
String appName;
@RequestMapping("/")
public String home(){
return "Consul service name: " + appName +", port: " + port;
}
@RequestMapping("/call")
public String cllConsulProvider(){
return "Call consul provider response: "+ consumerService.cllConsulProvider();
}
public static void main(String[] args) {
SpringApplication.run(ConsulConsumerApplication.class, args);
}
}
启动程序,然后多次访问: http://localhost:8765/call,可以看到消费者在交替请求 2 个服务提供者:
Call consul provider response: Consul service name: consul-provider, port: 8763
Call consul provider response: Consul service name: consul-provider, port: 8764
到此,Feign 消费者验证成功。
3、负载均衡配置
由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 客户端的方式来定义各个服务客户端调用的参数。
ribbon:
ConnectTimeout: 600 # 连接超时时间
ReadTimeout: 5000 # 调用超时时间
我们需要让 Hystrix 的超时时间大于 Ribbon 的超时时间,否则 Hystrix 命令超时后,该命令直接熔断,重试机制就没有任何意义了。
4、服务断路保护
Feign 中的服务降级使用起来非常方便,只需要为 Feign 客户端定义的接口添加一个服务降级处理的实现类即可。下面我们为 ConsumerService
接口添加一个服务降级实现类,并对接口中的每个方法定义服务降级处理逻辑。
@Component
public class ConsumerServiceFallback implements ConsumerService{
@Override
public String callConsulProvider() {
return "调用失败,服务被降级。";
}
}
修改 ConsumerService 接口,设置服务降级处理类为 ConsumerServiceFallback.class
:
@FeignClient(value = "consul-provider", fallback = ConsumerServiceFallback.class) // 声明需要调用的服务
public interface ConsumerService {
修改 application.yml,开启 Hystrix 功能 :
feign:
hystrix:
enabled: true # 开启 Hystrix 服务降级功能
关闭 2 个 consul-provider
服务,重启 consul-consumer
服务,访问 http://localhost:8765/call,可以看到页面显示服务降级信息。
5、调整日志级别
Spring Cloud Feign 在构建被 @FeignClient
注解修饰的服务客户端时,会为每一个客户端都创建一个 Logger.Level 实例,我们可以利用该日志对象的 DEBUG 模式来帮助分析 Feign 的请求细节。日志级别:
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
- FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
通过 Java 配置类将 Feign 日志设定为 FULL
:
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
在 application.yml 中配置日志级别为:debug
:
logging:
level:
cn.lixl.consulconsumer: debug
访问 http://localhost:8765/call ,可以看到控制台显示出详细的日志:
2020-11-21 16:36:10.240 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] ---> GET http://consul-provider/ HTTP/1.1
2020-11-21 16:36:10.240 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] ---> END HTTP (0-byte body)
2020-11-21 16:36:10.252 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] <--- HTTP/1.1 200 (12ms)
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] connection: keep-alive
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] content-length: 48
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] content-type: text/plain;charset=UTF-8
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] date: Sat, 21 Nov 2020 08:36:10 GMT
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] keep-alive: timeout=4
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] proxy-connection: keep-alive
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider]
2020-11-21 16:36:10.255 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] Consul service name: consul-provider, port: 8764
2020-11-21 16:36:10.255 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService : [ConsumerService#callConsulProvider] <--- END HTTP (48-byte body)
四、使用 Nacos作为配置中心
Consul 支持 Key/Value 键值对的存储,可以用来做配置中心。Spring Cloud 提供 Spring Cloud Consul Config 依赖和 Consul 集成。
参照 consul-provider
创建配置中心模块:consul-config-client
,在工程的 pom 文件中添加相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
新建配置文件 bootstrap.yml,对 Consul 进行配置:
spring:
cloud:
consul:
config:
enabled: true # 是否启用配置中心功能
format: yaml # 配置值格式
prefix: SpringCloudDemo # 配置数据所在目录
data-key: consul-config-data # 配置key的名字
application.yml 配置文件如下:
server:
port: 8766
spring:
application:
name: consul-config-client
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: consul-config-client
register: false
创建 ConfigClientController
,从 Consul 配置中心中获取配置信息:
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${config.project.name}")
private String projectName;
@GetMapping("/project-name")
public String getProjectName() {
return projectName;
}
}
在 Consul 中添加如下配置:
启动 consul-config-client
,调用接口 http://localhost:8766 查看配置信息,页面会显示配置值:SpringCloudDemo
。只要修改下 Consul 中的配置信息,再次调用此接口,就会发现配置已经刷新。