1、Spring Cloud概述
1.1、Spring Cloud是什么
1.2、Cloud和Boot的关系
2、Hello World
在进行开发的时候,需要注意cloud和对应的boot的版本,在这里本篇文章使用 H SR6版的cloud 和 2.2.5版的Boot进行讲述
首先先构建一个maven项目,这个项目用来作为所有项目的父项目,用来对cloud和boor进行管理,我们只需要在pom文件当中进行导入相对应的依赖即可。
父项目依赖:用来统一管理boot和cloud的版本,以及后续子组件的使用直接继承这个父项目即可进行使用boot和cloud的组件。
可配合代码浏览master分支 https://github.com/lizuoqun/springcloud_parent.git
<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">
<groupId>com.lzqgroupId>
<artifactId>springcloud_parentartifactId>
<packaging>pompackaging>
<version>0.0.1-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
parent>
<properties>
<spring.cloud-version>Hoxton.SR6spring.cloud-version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring.cloud-version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
3、服务注册中心
3.1、Eureka
Eureka包含两个组件:EurakaServer 和 Eureka Clinet
Eureka的自我保护机制
3.1.1、Eureka Server
首先创建一个maven项目,maven项目的父项目使用前面的父项目依赖,再给这个项目进行添加eureka server相关依赖,
<parent>
<artifactId>springcloud_parentartifactId>
<groupId>com.lzqgroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>com.lzqgroupId>
<artifactId>eureka_serverartifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
并且添加了springboot的依赖,之后我们添加一个spring boot的启动类,在类上添加@EnableEurekaServer
注解。以及给eureka server进行配置,添加配置文件。
# 使用eureka默认端口
server.port=8761
# 服务名(不能出现下划线)
spring.application.name=eureka-lzq
# 服务暴露端口
eureka.client.service-url.defaultZone = http://localhost:8761/eureka
# 关闭eclient立即注册
eureka.client.fetch-registry=false
# 不注册当前应用
eureka.client.register-with-eureka=false
# 不进行自我保护
eureka.server.enable-self-preservation=false
# 超时
eureka.server.eviction-interval-timer-in-ms=3000
启动项目,访问 http://localhost:8761 进行验证,Eureka Server是作为服务端,这个时候8761上面并没有服务注册上来,之后我们可以通过Eureka Client作为项目单独的小模块来进行注册。
3.1.2、Eureka Client
同样的创建一个maven项目,使用前面的父项目,导入eureka client 相关依赖。
<parent>
<artifactId>springcloud_parentartifactId>
<groupId>com.lzqgroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>erueka_clientartifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
在进行添加一个启动类,启动类加上一个@EnableEurekaClient
注解,该注解表示开启Eureka 客户端,之后添加配置文件,用来指定服务注册的注册中心以及其他的配置。
server.port=8989
spring.application.name=Eureka-Client
# 指定服务注册地址
eureka.client.service-url.defaultZone = http://localhost:8761/eureka
# 默认最大时间
eureka.instance.lease-expiration-duration-in-seconds=10
# 发一次心跳
eureka.instance.lease-renewal-interval-in-seconds=5
这样之后我们启动这个eureka的客户端,同样的访问8761,这个时候我们可以看到当前这个客户端的服务就已经被我们注册上来了。
3.2、Consul
consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。首先还是在windows下进行使用consul这个服务注册中心,consul官网,首先将consul下载下来。在安装目录下打开cmd,执行consul agent -dev
命令运行,consul会默认提供一个访问的8500端口。这里启动起来的consul也就是对应的服务端,之后我们还是通过cloud来进行开发对应的客户端,首先继承之前的父项目,添加consul相关依赖。
<parent>
<artifactId>springcloud_parentartifactId>
<groupId>com.lzqgroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>consul_clientartifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
dependencies>
添加一个boot的启动类,加上@EnableDiscoveryClient
注解。以及配置文件
server.port=8991
spring.application.name=Consul-Client
# 指定服务注册地址
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=A
# 关闭健康检查
# spring.cloud.consul.discovery.register-health-check=false
最后启动该项目,在8500端口暴露的服务进行查看,可以看到A这个服务就被注册上来了
RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。
在这里我们还是在父项目下新加两个springboot项目,添加boot的相关依赖后,对每一个项目都添加一个请求,那我们如何在第一个项目当中去调用另一个项目的请求呢?,可以使用到RestTemplate,如下:
@GetMapping("/order")
public String getOrder(){
RestTemplate restTemplate = new RestTemplate();
String res =restTemplate.getForObject("http://localhost:8888/getUser",String.class);
System.out.println("res = " + res);
return "getOrder" + " ==== " + res;
}
使用RestTemplate存在的问题:
解决方案——负载均衡
4、负载均衡
4.1、Ribbon
这里接上面的问题进行阐述,也就是说两个服务之间的通信,使用ribbon进行负载均衡,在这里使用到之前注册到consul上面的两个服务,在consul依赖引入的时候实惠依赖ribbon的依赖,这里就不需要再添加依赖了。参照user 和 order两个项目
在这里进行处理,主要有三种方式:
// 方法一
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("Users");
serviceInstances.forEach(serviceInstance -> {
System.out.println("host = "+ serviceInstance.getHost() +", port ="+ serviceInstance.getPort() +", url ="+ serviceInstance.getUri());
});
// 方法二
ServiceInstance serviceInstances1 = loadBalancerClient.choose("Users");
System.out.println("url = " + serviceInstances1.getUri()+", port = "+serviceInstances1.getPort() + " ,host = "+serviceInstances1.getHost());
在这里都是直接new一个RestTemplate对象,但是在开发过程当中,还是应该避免这种new对象的方式,尽量将对象交由bean工厂进行统一管理。之后进行使用的时候只需要将getRestTemplate这个Bean对象进行注入进来即可。
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
4.2、Ribbon负载均衡原理
首先上来先分析一下源码,如下图:
从最开始接口进行获取服务,根据服务标识得到对应的单个服务,也就是choose
ServiceInstance serviceInstances1 = loadBalancerClient.choose("Users");
之后ctrl进入该方法,可以看到会进入到一个接口 interface ServiceInstanceChooser
查看该接口的实现类 RibbonLoadBalancerClient,该类重写了choose方法,重写方法调用当前了的choose方法,如下:
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, null);
}
public ServiceInstance choose(String serviceId, Object hint) {
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
而当前类的choose方法的getServer就根据服务id进行返回服务,所以我们追踪到getServer方法。
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
继续进入到chooseServer,还是一个接口,interface ILoadBalancer
,到这个接口,就比较男判断后续到底会进入到哪一个实现类,这里可以进行打断点进行查看,到最后进入到BaseLoadBalancer
实现类,在这个类当中进行判断,通过rule.choose(key);
而这个rule是一个变量,可以看到
private final static IRule DEFAULT_RULE = new RoundRobinRule();
protected IRule rule = DEFAULT_RULE;
到这里我们就找到了最终的策略的指定,也就是到这个IRule类。我们可以查看这个类的关系图。
Orders.ribbon.NFloadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
总结:ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。
4.3、OpenFeign
在前面提出的问题,我们使用到Ribbon进行负载均衡处理,但是代码当中的请求地址还是写死的,这个时候又如何处理呢?这个时候就需要使用到Fegin了。
4.3.1、Feign 和 Ribbon
4.3.2、Feign 可以用来做什么
4.3.3、Feign 和 OpenFeign 的区别
4.3.4、使用 OpenFeign 进行服务通信
在这里我们使用服务还是注册到consul上,首先添加 openfegin 的依赖,参照feign_pay 和 feign_puduct两个项目
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
在进行使用的时候我们需要在启动类上加上 @EnableFeignClients 开启fegin客户端调用的注解,到最后我们新加一个关于fegin调用的包,在这里也就是当前服务需要调用的服务对应的接口,
package com.lzq.feginclient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 调用 Product 服务
* @author lizuoqun
* */
@FeignClient(value="Product")
public interface ProductClient {
@GetMapping("/getProduct")
String getProduct();
}
而到最后在当前服务的controller当中我们直接把这个类进行注入进来直接进行调用即可,这个时候我们就可以在当前服务调到getProduct这个接口了。这样也就完成了服务直接的调用了,也就避免了之前说到的问题,关于调用的地址在代码当中写死的问题。
4.3.5、使用 OpenFeign 进行服务通信关于参数传递
关于参数的传递,主要分为以下几种参数的传递:
4.3.5.1、零散参数传递
零散参数传递通常有两种方式进行,一种就是通过 ? 加参数进行传递,第二种就是通过 / 占位符 进行传递,
首先我们定义接口,并且给改接口传入两个参数用来进行测试,
之后我们在ProductClient当中也添加这个方法,也就是为了feign进行调用服务,同样的需要给定参数,最后直接在当前服务通过ProductClient直接对另一个服务的接口进行调用,传递两个参数过去,这个时候我们直接启动项目,哦豁,启动报错:参数太长?然后把两个参数改成一个参数,又可以了,这是为什么呢?难道就只能传一个参数嘛?当然不是,这是因为在这里openfeign是一个伪HTTP服务,真正调用请求的还是RestTemplate,而我们直接给参数,openfeign跟不久无法判断当前参数是通过?还是 / 进行拼接处理,所以在这里进行使用参数的时候需要加上 @RequestParam 或者 @PathVariable用来告诉openfeign到底是通过那种方式来进行拼接参数。
4.3.5.2、对象参数传递
相同的,我们进行传递对象参数的时候,可以从零散参数对象得到启发,零散参数使用@RequestParam注解,而在Spring当中对象的参数传递使用@RequestBody进行修改对象即可。
4.3.5.3、数组参数传递
对于数组参数传递,还是使用@RequestParam对入参进行修饰即可。
4.3.6、OpenFeign 的相关配置
4.3.6.1、默认超时时间
在使用OpenFeign进行服务调用通信,服务之间调用返回时间默认在1秒之内,在一秒之内没有返回openfeign会抛出异常。
在pay当中进行配置
feign.client.config.服务id.connectTimeout=5000
feign.client.config.服务id.readTimeout=5000
# 修改默认读取和超时时间
# feign.client.config.default.connectTimeout=5000
# feign.client.config.default.readTimeout=5000
4.3.6.2、日志
在构建@FeignClient注解修饰的服务客户端时,会为每一个客户端都创建一个Feign.Logger实例,可以利用该日志对象的Debug模式来分析Feign的请求细节。
Feign的Level日志级别配置默认是:NONE,不要跟log日志混淆。
日志级别枚举类Logger.Level | 说明 |
---|---|
NONE | 不输出日志 |
BASIC | 输出请求方法、URL、响应状态码、执行时间 |
HEADERS | 基本信息以及请求和响应头 |
FULL | 请求和响应的heads、body、metadata,建议使用这个级别 |
# 对指定的服务日志监控
feign.client.config.服务id.loggerLevel=full
# 显示openfeign日志
logging.level.com.lzq.feignClient=DEBUG
5、服务熔断
5.1、服务雪崩与服务熔断
服务雪崩:
服务A的流量波动很大,流量经常会突然性增加!那么在这种情况下,就算服务A能扛得住请求,服务B和服务C未必能扛得住这突发的请求。此时,如果服务C因为抗不住请求,变得不可用。那么服务B的请求也会阻塞,慢慢耗尽服务B的线程资源,服务B就会变得不可用。紧接着,服务A也会不可用,这一过程如下图所示:
服务熔断:
当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
开启熔断
熔断恢复
5.2、Hystrix
先构建一个maven项目,将consul服务注册中心的依赖导入以及consul对应的配置文件进行配置,之后其余的代码和之前的单个springboot服务一致进行配置,首先我们给这个服务添加一个接口,使用浏览器进行访问,如下接口所示:之后通过传入的id设置为一个负数来进行模拟服务请求抛出异常,
@GetMapping(value = "/demo")
public String demo(Integer id) {
if (id < 0) {
throw new Error("id 不合法");
}
return "demo ok";
}
之后进行Hystrix依赖引入:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
在前面所说的,当我们在浏览器当中一直发送请求导致抛出异常,在一定的界限下,该服务就会被熔断,这个如何进行配置呢?,首先给springboot启动器添加一个开启熔断器的注解:@EnableCircuitBreaker
,之后在方法上添加 @HystrixCommand(fallbackMethod = demoFallBack)
用来当该服务被熔断之后 fallback 回调使用到的方法,这里的demoFallBack的入参、返回值需要和demo方法一致;
public String fallbackDemo(Integer id) {
return "服务已被熔断";
}
整体流程:
当Hystrix监控到对该服务接口调用触发两个阏值时,会在系统中自动触发熔断器,在熔断器打开期间内,任何到该接口请求均不可用,同时在断路器打开5s后断路器会处于半开状态,此时断路器允许放行一个请求到该服务接口,如果该请求执行成功,断路器彻底关闭,如果该请求执行失败断路器重新打开
5.3、服务降级 Hystrix 与 OpenFeign
服务降级:
客户端从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用此时在客户端,我们可以准备一个FallbackFactory,返回一个默认的值(缺省值),整体的服务水平下降了~ 但是好歹能用
在前面使用Hystrix进行服务降级,是单独只访问量这一个服务,但是在项目当中服务之间是进行相互通信的,当我们当前服务宕机了,其他的服务却通过openfeign还在继续访问,这个时候页面所展现的就是该网站无法访问,
同样的构建一个springboot项目,对consul注册中心、openfeign进行配置,我们使用这个项目通过openfeign来进行调用前面的Hystrix项目。之后我们对openfeign配置对应的client,将前面的demo接口进行写过来,进行feign调用,在这里openfeign下已经依赖了Hystrix,这里我们就不需要再引入Hystix的依赖了,
再配置文件当中开启openfeign对Hystrix的支持
feign.hystix.enabled=true
在FeignClient这个接口当中的 @FeignClient(value="HYSTRIX",fallback=接口实现类.class )
这里的fallback用来指定当前调用服务不可用时,默认的备选方案,而后定义一个类继承FeignClient这个接口,实现demo方法,并且给实现类加上一个@Configuration
注解。
5.4、Hystrix Dashboard仪表盘
作用:
监控每一个@HystrixCommond注解创建一组度量,构建一组信息,然后通过图形化方式展示当前方法@Hystrixcomnond的状态信息
构建仪表盘
首先还是构建一个springboot项目,进行依赖引入,springboot web、Hystrix DashBoard
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
最后在启动类上加上@EnableHystrixDashboard
注解,用表开启仪表盘应用
启动项目,使用localohost+端口+/hystrix
进行访问
问题解决
问题一:仪表盘让监控的application地址一直在loadding
在启动类当中添加:
// /**
// * 入口类配置 你要监控那个服务,就把这个放在那个服务去
// *
// * @return ServletRegistrationBean
// */
@Bean
public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
//registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
问题二:版本不一致,JQ版本导致前端报错。
找到对应的jar包,之后在自己的maven仓库当中找到jar包,给里面的monitor.tlh文件进行修改。
hystrix.dashboard.proxy-stream-allow-list=""
6、服务网关
6.1、网关概述
6.1.1、网关是什么:
API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。
6.1.2、网关的作用:
- 网关统一所有微服务入口
- 网关可以实现请求路由转发(router dispatcher)以及请求过程负载均街
- 访问服务的身份认证
- 防报文重放与防数据算改
- 功他调用的业务答.
- 响应数据的脱敏
- 流量与并发控制,甚至基于冲工调用的计量或者计费等等
6.1.3、核心概念
路由:路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配
断言:Java8中的断言函数。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。
过滤器:一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。
6.2、Gateway
首先构建一个maven项目,导入springboot和网关的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
之后需要编写网关的配置文件:(主要是后面的spring.cloud.gateway.routes
这一块的配置,这个就是将其他的服务在这里通过网关来进行转发)在之前做RestTemplate新加的两个项目:在这里就使用这两个项目来进行网关转发。
官网 yml 配置:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#fully-expanded-arguments
server:
port: 7979
spring:
application:
name: GATEWAY
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: GATEWAY
gateway:
routes:
- id: user
uri: http://localhost:8888
predicates:
- Path=/getUser
- id: order
uri: http://localhost:9999
predicates:
- Path=/order
这里之后我们就可以直接使用网关对后面加的user、order服务进行转发访问了。
路由配置:
通配符网关配置
?
单个字符*
任意多个字符,不包含多级路径**
任意多个字符,包含多级路径通过java代码进行路由转发
官网Java配置网关:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#the-weight-route-predicate-factory
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user", r -> r.path("/getUser"),
uri("http://localhost:8888")).build();
}
}
网关的负载均衡
存在问题,在前面的配置当中,请求地址是写死的,这样的话就无法进行负载均衡。
uri改成 lb://服务id,也就是 uri: lb://user-lzq
6.3、Gateway的断言和过滤
6.3.1、断言
官网地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#the-before-route-predicate-factory
6.3.1.1、基于DateTime
可以直接使用Java8之后的ZonedDateTime 获取时间。
public static void main(String[] args) {
ZonedDateTime dateTime = ZonedDateTime.now();
System.out.println(dateTime);
}
6.3.1.2、基于远程地址
接收一个IP地址段,判断请求主机地址是否在地址段中
6.3.1.3、基于Cookie
接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。
6.3.1.4、基于请求头Header
接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。
6.3.1.5、基于Method请求方法
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
6.3.1.6、基于Path请求路径
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
最常见的一种断言
6.3.1.6、基于Query请求参数
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。
6.3.2、过滤器
官网地址:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories
6.3.2.1、过滤器是干什么的?
过滤器就是在请求的传递过程中,对请求和响应做一些修改
6.3.2.2、过滤器的生命周期
客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。
6.3.2.3、局部过滤器
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 | 无 |
secureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 Header名称, | 修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大 小。如果请求包大小超过设置的值,则返回 413 Payload TooLarge | 请求包大小,单位为字节, |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
6.3.2.4、内置局部过滤器的使用
routes:
- id: user
uri: lb://user
predicates:
- Path=/user/**, /*/edu/**
filters:
- SetStatus=250 # 修改返回状态码,其余具体过滤器工厂对应官网文档进行使用即可
6.3.2.5、网关路由规则web页面
在Gateway服务当中,默认会给我们开一个web页面,但是直接进行访问是访问不了的,需要我们进行配置,在yml当中添加配置
management:
endpoints:
web:
exposure:
include: "*"
直接进行访问:localhost:7979/actuator/gateway/routes
7、统一配置中心
7.1、配置中心概述
比如说有N个微服务的application.yml是相同的,我们要是想修改application.yml需要修改N个这时候我们就需要ConfigServer来帮我们解决这个问题,在配置中心修改一次众多个微服务生效。 SpringCloud Config为服务器架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
首先在configServer当中会集成Git,我们可以将对应的配置文件放到远程仓库进行管理,这是因为在修改配置的时候Git会进行版本记录,方便后期版本回退,
在获取git仓库之后会加到本地缓存,这是为了避免当远程仓库宕机之后,本地微服务还是要得到统一配置。
7.2、远程仓库配置
在进行远程仓库的构建的时候,我们可以使用Github、Gitee、Gitlab等等, 这里还是选择Github,在进入GitHub之后我们创建一个远程仓库项目 config 。
7.3、Config Server
首先我们构建一个项目,继承父项目,之后我们引入相关的依赖,包含springboot、consul等相关依赖,对整个服务进行相关配置。
之后添加ConfigServer依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
在SpringBoot启动类当中添加@EnableConfigServer注解。最后对远程仓库地址配置:
# 远程仓库配置
spring.cloud.config.server.git.uri=XXX
spring.cloud.config.server.default-label=master
如果这个远程仓库是私有的话就需要配置对应的用户名和密码。
spring.cloud.config.server.git.username=您的账号
spring.cloud.config.server.git.password=您的密码
之后我们直接拉取远程仓库的配置文件进行测试。
远程仓库git地址: https://gitee.com/modify_lzq/config_server.git
在这里我们添加一些properties文件,用来表示拉取的文件
之后我们直接通过地址进行拉取文件:http://localhost:8880/configclient-xxx.properties
相同的再构建一个maven项目,这其实就是一个微服务,我们导入springboot、consul依赖,并且添加相对应的配置。并且在这里我们还需要引入config_client的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
在前面我们通过了ConfigServer获取到了远程仓库的配置文件,但是在client当中又该如何获取到呢?首先服务都注册到了注册中心,只需要获取到ConfigServer的服务id之后再去找对应的远程仓库即可,而在远程仓库当中又有很多的配置文件,在这里还需要指定获取到哪个配置文件。本地配置文件如下:
# 当前configclient 统一配置中心在注册中心的服务id
spring.cloud.config.discovery.service-id=Config_Server
# 开启当前configclient 根据服务id去注册中心找
spring.cloud.config.discovery.enabled=true
# 指定服务注册地址
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 获取哪个配置文件(Git分支、文件名、环境)
spring.cloud.config.label=master
spring.cloud.config.name=configclient
spring.cloud.config.profile=dev
并且在这里使用application.properties
作为配置文件名进行启动的时候是否报错的,在这里需要改成bootstrap.properties
或者bootstrap.yml
进行启动。
7.5、配置更新
在这里存在一个问题:就是当我们对远程仓库进行修改之后,这里的config_client服务必须进行重启才能够加载最新的配置文件,
解决方案:
7.5.1、手动刷新
当远端git仓库中配置发生变化时,不需要重启微服务就可以直接读取远墙修改之后配置信息,这种就叫手动配置刷新。
@RefreshScope
注解,表示不需要重启微服务,将当前scrop域中信息刷新为最新配置信息http://localhost:8881/actuator/refresh
management.endpoints.web.exposure.include="*"
8、消息总线
8.1、Bus概述
Spring cloud bus通过轻量消息代理连接各个分布的节点。这会用在广播状态的变化(例如配置变化)或者其他的消息指令。Spring bus的一个核心思想是通过分布式的启动器对spring boot应用进行扩展,也可以用来建立一个多个应用之间的通信频道。目前唯一实现的方式是用AMQP消息代理作为通道,同样特性的设置(有些取决于通道的设置)在更多通道的文档中。
大家可以将它理解为管理和传播所有分布式项目中的消息既可,其实本质是利用了MQ的广播机制在分布式的系统中传播消息,目前常用的有Kafka和RabbitMQ。
8.2、RabbitMQ搭建
RabbitMQ搭建 参考本文:消息中间件之RabbitMQ的安装及消息发送接收
8.3、Bus消息总线自动刷新
首先在ConfigServer当中添加bus依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
在这里的client同样要配置RabbitMQ的相关配置,这里我们放在远程仓库即可,并且运行启动时出错处理:
spring.cloud.config.fail-fast=true
之后启动项目,在这里遇到了几个坑,首先就是服务总是启动失败,在Client当中找不到Server服务,但是在进行排查的时候,是没有问题的,在最后进行解决的方法是:把当前maven项目进行clear之后再进行启动,人懵了,可能是缓存的问题吧。
再项目启动之后,我们修改远程仓库的配置之后,发送一个Post请求给Server,http://localhost:8880/actuator/bus-refresh
,在这里Server服务也需要暴露接口地址,使用management.endpoints.web.exposure.include=*
,并且我们也可以对单个服务进行发送Post请求进行更新,在前面的地址最后加上对应的服务ID即可,如:http://localhost:8880/actuator/bus-refresh/CONFIGCLIENT
8.4、WebHook
webhook与异步编程中"订阅-发布模型"非常类似,一端触发事件,一端监听执行。
简单来说:就是当git进行提交文件之后就执行一个Post请求,如下图:
这个时候可以看到在Gitee上我们直接添加本地Url地址,这很显然是不行的,这个时候就有了内网穿透。
8.5、内网穿透
首先:登录https://natapp.cn/,之后进行注册一个账号并且进行实名,实名之后进行购买隧道,购买一个免费隧道即可,之后对隧道进行配置,只需要指定一个本地要穿透的端口即可。
之后下载客户端(根据自己的电脑型号进行下载),下载完成之后通过cmd窗口进行启动,使用命令natapp -authtoken=自己的token
进行启动,启动之后本地的8880端口就被穿透给了http://i5pqp3.natappfree.cc
,使用这个地址替换掉localohost:8880进行访问也就实现了内网穿透。