目录
1、Gateway
1.1 概述简介
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5、Spring Boot 2和Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤功能,例如:熔断、限流、重试等
SpringCloud Gateway作为SpringCloud的生态系统中的网关,目标是替代Zuul,在SpringCloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.X非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。它的目的是提供统一的路由方式且基于Filter链的方式提供了网关基本的功能。
主要用途:反向代理、鉴权、流量控制、熔断、日志监控等等
1.2 为什么选择Gateway
特点:
基于Spring Framework 5、Project Reactor 和 Spring Boot 2进行构建
动态路由:能够匹配任何请求属性
可以对路由指定Predicate(断言)和Filter(过滤器)
集成Hystrix的断路器功能
集成Spring Cloud服务发现功能
易于编写的Predicate(断言)和Filter(过滤器)
请求限流功能
支持路径重写
Spring Cloud Gateway与Zuul的区别:
- Zuul 1.X是一个基于阻塞I/O的API Gateway
- Zuul 1.X基于Servlet 2.5使用阻塞架构,它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次I/O操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差
- Zuul 2.X理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.X的性能较1.X有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍
- Spring Cloud Gateway建立在Spring Framework 5、Project Reactor 和Spring Boot2之上,使用非阻塞API。
- Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的额开发体验。
Gateway模型:
(1)WebFlux是什么?
传统Web框架,如Struts2、SpringMVC等都是基于Servlet API与Servlet容器基础上运行的。在Servlet3.1之后又了异步非阻塞的支持。而WebFlux是一个典型的非阻塞异步的容器,它的核心是基于Reactor的相关API实现的。相对传统的Web框架,它可以运行在诸如Netty、Undertow及支持Servlet3.1的容器上。非阻塞 + 函数式编程(Spring 5必须让你使用Java8及以上)。
Spring WebFlux是Spring 5引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。
1.3 三大核心概念
(1)Route(路由)
路由是构建网关的基本模块,它由ID、目标URI、一系列的断言和过滤器组成,如果断言为true则匹配该路由
(2)Predicate(断言)
参考的是Java8的java.util.function.Predicate。开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
(3)Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
总体而言,web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。predicate就是我们的匹配条件,而filter就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由。
1.4 Gateway工作流程
核心逻辑:路由转发 + 执行过滤器链
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。
Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出、流量监控等有着重要作用。
1.5 实例
(1)新建cloud-gateway-gateway9527项目
(2)添加pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.fx.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!-- eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- for Spring Cloud Gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>
(3)配置yml文件
server: port: 9527 eureka: client: # 将自身注册到EurekaServer register-with-eureka: true # true,表示从EurekaServer抓取已有的注册信息 fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ spring: application: name: cloud-gateway cloud: gateway: routes: - id: payment_routh # 路由的Id,没有固定规则但要求唯一,建议使用配合服务名 uri: http://localhost:8001 # 匹配后提供的路由地址 predicates: - Path=/payment/get/** # 断言,路径相匹配的进行路由 - id: payment_routh2 uri: http://localhost:8001 predicates: - Path=/payment/discovery
(4)设置启动类
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class, args);
}
}
注:上述代码测试需要启动Eureka服务端、8001服务端(订单支付),这两个项目在之前的文章中都有创建。
1.6 Gateway网关路由的两种配置
(1)在配置文件yml中配置
详情见上述案例配置。
(2)代码中注入RouteLocator的Bean
@Configuration
public class GateWayConfig {
/**
* 访问/guonei这一地址时,路由到http://news.baidu.com/guonei
* @param routeLocatorBuilder
* @return
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_payment",
r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
1.7 配置动态路由
修改application.yml:
server: port: 9527 eureka: client: # 将自身注册到EurekaServer register-with-eureka: true # true,表示从EurekaServer抓取已有的注册信息 fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_routh # 路由的Id,没有固定规则但要求唯一,建议使用配合服务名 # uri: http://localhost:8001 # 匹配后提供的路由地址 uri: lb://CLOUD-PAYMENT-SERVICE predicates: - Path=/payment/get/** # 断言,路径相匹配的进行路由 - id: payment_routh2 # uri: http://localhost:8001 uri: lb://CLOUD-PAYMENT-SERVICE predicates: - Path=/payment/discovery
1.8 常用的Predicate
(1)The After Route Predicate Factory
- id: payment_routh2 uri: lb://CLOUD-PAYMENT-SERVICE predicates: - Path=/payment/discovery - After=2021-05-12T20:24:27.705+08:00[Asia/Shanghai]
时区如何获得: ZonedDateTime now = ZonedDateTime.now();
(2)Before、Between
- Before=2021-05-12T20:24:27.705+08:00[Asia/Shanghai] - Between=2021-05-12T20:24:27.705+08:00[Asia/Shanghai]
(3) Cookie Route Predicate Factory
需要两个参数,一个是Cookie name,一个是正则表达式。路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配则不执行。
uri: lb://CLOUD-PAYMENT-SERVICE predicates: - Path=/payment/discovery - After=2021-05-12T20:24:27.705+08:00[Asia/Shanghai] - Cookie=username,abc
通过curl模拟访问:
(4)Header Route Predicate Factory
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配执行。与cookie相似。
- Header=X-Request-Id, \d+ # 表示请求头要有X-Request-Id属性,且值为整数的正则表达式
curl http://localhost:9527/payment/discovery - H "X-Request-Id:111"
(5)Host Route Predicate Factory
- Host=**.somehost.org,**.anotherhost.org
(6)Method Route Predicate Factory
- Method=GET,POST
(7) Path Route Predicate Factory
- Path=/red/{segment},/blue/{segment}
这是常用的断言。
(8)Query Route Predicate Factory
- Query=username, \d+ # 要有参数名username且值还要是整数才能路由
1.9 Filter
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多钟路由过滤器,它们都由GatewayFilter的工厂类产生。
(1)生命周期
业务逻辑之前(pre)、业务逻辑之后(post)
(2)种类
单一的:GatewayFilter
全局的:GlobalFilter
主要两个接口:implements GlobalFilter, Ordered
用于:全局日志记录、统一网关鉴权等
案例代码:自定义全局filter
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("--------------- come in MyLogGateWayFilter ---------------- " + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null) {
log.info("******************用户名为null,非法用户******************");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
// 数值越小优先级越高
@Override
public int getOrder() {
return 0;
}
}
2 Config配置中心
2.1 概述
(1)分布式系统面临的配置问题
微服务意味着要将单位应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式、动态的配置管理设施是必不可少的。
(2)Config是什么
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
(3)怎么使用
SpringCloud Config分为服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。
客户端则是通过指定的盘配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
集中管理配置文件
不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
将配置信息以REST接口的形式暴露:post、curl访问刷新均可......
2.2 Config服务端配置与测试
(1)在gitee上配置仓库
这里使用gitee配置名为SpringCloud-Config的公开仓库(也可以使用github),新建一个测试文件config-dev.yml。
注:这里不会git的请自行百度,只需掌握基本语法即可。
(2)建立项目
新建名称为cloud-config-center3344的子项目。
(3)配置pom
只添加了主要的,其余按需添加
<!-- Config Server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
<!-- eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
(4)配置applciation.yml
server: port: 3344 spring: application: name: cloud-config-center cloud: config: server: git: uri: https://gitee.com/fxyong/springcloud-config.git # 搜索目录 search-paths: - SpringCloud-Config # 读取分支 label: master eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/
(5)测试访问
在浏览器总访问:http://localhost:3344/master/config-dev.yml
解释说明(/master/config-dev.yml):/{label}/{application/name}-{profile}.yml
label:分支(branch)
name:服务名
profiles:环境(dev/test/prod)
2.3 Config客户端配置与测试
(1)新建项目
新建cloud-config-client3355子项目
(2)pom文件
<!-- eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- Config --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
(3)配置bootstrap.yml
说明:
application.yml是用户级的资源配置项;bootstrap.yml是系统级的,优先级更高
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的‘Application Context’的父上下文。初始化的时候,‘Bootstrap Context’负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的“Environment”。
“Bootstrap”属性有高优先级,默认情况下,它不会被本地配置覆盖。“Bootstrap Context”和“Application Context”有着不同的约定,所以新增了一个“bootstrap.yml”文件,保证“Bootstrap Context”和“Application Context”配置的分离。
(4)controller类
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
(5)gitee上的配置文件
server:
port: 3355
config:
info: "master branch, SpringCloud-Config/config-dev.yml version=1"
通过http://localhost:3355/configInfo访问。
(6)问题出现
项目3355无法做到动态更新gitee上配置修改后的结果,目前通过重启来解决。
(7)动态刷新
手动版:
在pom引入actuator监控:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
修改bootstrap.yml,暴露监控端点:
management: endpoints: web: exposure: include: "*"
在Controller类添加注解:
@RefreshScope
执行命令:curl -X POST "http://localhost:3355/actuator/refresh"
3、Bus消息总线
3.1 概述
Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新。
(1)是什么
Bus支持两种消息代理:RabbitMQ和Kafka。
什么是总线:
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
基本原理:
COnfigClient实例都监听MQ中同一个topic(默认是springcloudbus)。当一个服务刷新数据的时候,它会把这个消息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。
3.2 RabbitMQ环境配置
不会的请自行百度,这里提供一个windows环境下的安装方法:RabbitMQ安装
3.3 SpringCloud Bus动态刷新全局广播
(1)配置好RabbitMQ环境
(2)以3355项目为模板再制作一个3366
新建项目:
cloud-config-client3366
pom文件:
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fx.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Config -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
yml文件:
server: port: 3366 spring: application: name: config-client cloud: # config 客户端配置 config: label: master # 分支名称 name: config # 配置文件名称 profile: dev # 读取后缀名称 uri: http://localhost:3344/ # 配置中心地址 # 类似读取http://localhost:3344/master/config-dev.yml # 服务注册到Eureka eureka: client: service-url: defaultZone: http://localhost:7001/eureka/ # 暴露监控端点 management: server: port: 3366 endpoints: web: exposure: include: "*"
controller:
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${server.port}")
private String serverPort;
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return "ServerPort:" + serverPort + ",configInfo:" + configInfo;
}
}
(3)设计思想
1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置。如图一
2)利用消息总线触发一个服务端ConfigServer的bus/refresh端点,而刷新所有客户端的配置。如图二
为什么图二的架构更加适合,图一不适合?
打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
破坏了微服务各节点的对等性。
有一定的局限性。如:微服务在迁移时,它的网络地址常常发生变化,此时如果想要做到自动刷新那就回增加更多的修改。
(4)动态刷新全局广播
1)给cloud-config-center3344配置中心服务端添加信息总线支持
pom文件:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
yml文件:
# rabbitmq相关配置,暴露bus刷新配置的端点
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
management:
endpoints: # 暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
2)给客户端cloud-config-client3355/3366
pom文件:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
yml文件:
# 配置RabbitMQ
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
访问测试:
配置中心:http://localhost:3344/master/config-dev.yml
客户单:http://localhost:3355/configInfo,http://localhost:3366/configInfo
一次发起:curl -X POST "http://localhost:3344/actuator/bus-refresh"
(5)动态刷新定点通知
只通知3355不通知3366,案例实现:
公式:http://localhost:配置中心端口号/actuator/bus-refresh/{destination}
解释:请求不再发送到具体的服务器实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例
j具体执行:curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"