8.Spring Cloud Gateway网关
8.1简介
8.2案例
需求:将包含有 /user 的请求 路由到 http://127.0.0.1:9091/user/{id}
新建子项目gateway
pom.xml添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
设置启动类
编写配置文件
server:
port: 10010
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
# 路由id,可以随意写
- id: user-service-route
# 代理的服务地址
uri: http://127.0.0.1:9091
# 路由断言,可以配置映射路径
predicates: #http://127.0.0.1:10010/user/1 -> http://127.0.0.1:9091/user/1
- Path=/user/**
eureka:
client:
service-url:
defaultZone: HTTP://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
测试:http://localhost:10010/user/1
8.3面向服务的路由
上面的方式把路径写死了,这样是不对的。如果同一服务有多个实例的话,这样做显然不合理。 应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由。
修改配置文件:
gateway将使用LoadBalancerClient把user-service通过eureka解析为实际的主机和端口,并进行ribbon负载均衡
8.4路由前缀
当微服务的地址与客户端的地址不一致时,可以配置路径过滤器对路径的前缀进行增加或删除
比如:
提供服务的地址:http://127.0.0.1:9091/user/1
添加前缀路径/user :访问http://127.0.0.1:10010/1就可以得到同样的效果
去除前缀路径/api:访问http://127.0.0.1:10010/api/user/1也可以得到同样的效果
修改配置文件
添加前缀
去除前缀
8.5路由过滤器
8.5.1简介
Gateway网关一个重要的功能就是请求的鉴权,这个功能往往需要网关提供的过滤器来实现,上面路由前缀的处理就是使用过滤器来实现的。
常见的过滤器有:
过滤器使用的时候是有规律的,比如之前使用的“StripPrefix”就是来自“StripPrefixGatewayFilterFactory”,直接把后面的“GatewayFilterFactory”来使用
过滤器的生命周期
Gateway 的 Filter 的生命周期也类似 Spring MVC 的拦截器有两个: “pre” 和 “post” 。 “pre” 和 “post”分别会在请求被执行前调用和被执行后调用这里的 pre 和 post 可以通过过滤器的 GatewayFilterChain 执行fifilter方法前后来实现
使用场景
- 请求鉴权:GatewayFilterChain 执行fifilter方法前,如果发现没有访问权限,直接就返回空。
- 异常处理:GatewayFilterChain 执行fifilter方法后,记录异常并返回。
- 服务调用时长统计: GatewayFilterChain 执行fifilter方法前后根据时间统计。
过滤器类型
- 局部过滤器:通过 spring.cloud.gateway.routes.fifilters 配置在具体路由下,只作用在当前路由上;如果配置spring.cloud.gateway.default-fifilters 上会对所有路由生效也算是全局的过滤器;但是这些过滤器 的实现上都是要实现GatewayFilterFactory接口。
- 全局过滤器:不需要在配置文件中配置,作用在所有的路由上;实现 GlobalFilter 接口即可。
8.5.2全局默认过滤器
8.5.3自定义过滤器
1、自定义局部过滤器
我们通过一个需求来定义自己的过滤器
需求:
- 在过滤器中将http://localhost:10010/api/user/1?name=vv中的参数name的值获取到并输出到控制台
- 并且参数名是可变的,也就是不一定每次都是name,需要可以通过配置过滤器的时候做到配置参数名
创建MyParamGatewayFilterFactory.class
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
static final String PARAM_NAME = "param";
public MyParamGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_NAME);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// http://localhost:10010/api/user/1?name=vv config.param = name(yml中配置的name)
//获取请求参数中param对应的参数名 的参数值
ServerHttpRequest request = exchange.getRequest();
if(request.getQueryParams().containsKey(config.param)) {
request.getQueryParams().get(config.param)
.forEach(value -> System.out.printf("------------局部过滤器--------%s = %s- -----", config.param, value));
}
return chain.filter(exchange);//执行请求
};
}
//读取过滤器配置的参数
public static class Config {
//对应配置application.yml配置文件中过滤器参数
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
}
修改配置文件:
测试后:
2、自定义全局过滤器
需求:
编写全局过滤器,在过滤器中检查请求中是否携带token请求头。
- 如果token请求头存在则放行;
- 如果token为空或者不存在则设置返回的状态码为:未授权也不再执行下去。
创建MyGlobalFilter.class
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("--------------全局过滤器MyGlobalFilter------------------");
String token = exchange.getRequest().getHeaders().getFirst("token");
if(token==null || token.equals("")) {
//设置响应状态码为未授权
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
//实现Ordered,确定执行的顺序
@Override
public int getOrder() {
//值越小越先执行
return 1;
}
}
测试:使用postman来模拟,因为浏览器请求无法设置token
正常访问浏览器时:
当添加token时
8.6Gateway跨域设置
在js 请求访问中,如果访问的地址与当前服务器的域名、 ip 或者端口号不一致则称为跨域请求。若不解决则不能获取到对应地址的返回结果。 如:从在 http://localhost:9090 中的 js 访问 http://localhost:9000 的数据,因为端口不同,所以也是跨域请求。 在访问Spring Cloud Gateway 网关服务器的时候,出现跨域问题的话,可以在网关服务器中通过配置解决,允许哪些服务是可以跨域请求的;具体配置如下:8.7 Gateway 和 Feign 区别
- Gateway 作为整个应用的流量入口,接收所有的请求,如PC、移动端等,并且将不同的请求转发至不同的处理微服务模块,其作用可视为nginx;大部分情况下用作权限鉴定、服务端流量控制
- Feign则是将当前微服务的部分服务接口暴露出来,并且主要用于各个微服务之间的服务调用
9.Spring Cloud Confifig分布式配置中心
在分布式系统中,由于服务数量非常多,配置文件分散在不同的微服务项目中,管理不方便。为了方便配置文件集中管理,需要分布式配置中心组件。在Spring Cloud中,提供了 Spring Cloud Confifig ,它支持配置文件放在配置服务的本地,也支持放在远程Git 仓库配置中心本质上也是一个微服务,同样需要注册到Eureka服务注册中心!
9.1创建远程仓库
9.2创建配置文件
配置文件的命名方式 : {application}-{profifile}.yml 或 {application}-{profifile}.properties- application为应用名称
- profifile用于区分开发环境,测试环境、生产环境等
如user-dev.yml,表示用户微服务开发环境下使用的配置文件。
我们这里将user-service工程的配置文件application.yml文件的内容复制作为user-dev.yml文件的内容
9.3搭建配置中心微服务
添加依赖
<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-config-server</artifactId>
</dependency>
</dependencies>
启动类上添加开启服务注解
修改配置文件
server:
port: 12000
spring:
application:
name: config-server
cloud:
config:
server:
git:
#项目地址
uri: https://gitee.com/vvupup/my-config1.git
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
启动测试:http://localhost:12000/user-dev.yml
9.4获取配置中心配置
在user-service中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
删除application.yml
创建bootstrap.yml
spring:
cloud:
config:
# 要与仓库中的配置文件的application保持一致
name: user
# 要与仓库中的配置文件的profile保持一致
profile: dev
# 要与仓库中的配置文件所属的版本(分支)一样
label: master
discovery:
# 使用配置中心
enabled: true
# 配置中心服务名
service-id: config-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
bootstrap.yml 文件也是 Spring Boot 的默认配置文件,而且其加载的时间相比于 application.yml 更早。 application.yml 和 bootstrap.yml 虽然都是 Spring Boot 的默认配置文件,但是定位却不相同。 bootstrap.yml可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。application.yml 可以用来定义应用级别的参数,如果搭配 spring cloud confifig 使用, application.yml 里面定义的文件可以实现动态替换。 总结就是:
- bootstrap.yml文件相当于项目启动时的引导文件,内容相对固定
- application.yml文件是微服务的一些常规配置参数,变化比较频繁
测试:http://localhost:9091/user/1(要先启动配置中心再启动user-service)
10.Spring Cloud Bus服务总线
引入问题:
如果我们更新Git仓库中的配置文件,那用户微服务是否可以及时接收到新的配置信息并更新呢
首先新增一个数据,在配置中心运行期间修改该值,看能否得到修改后的值。
经过测试后发现,配置中心能够动态的获取值,但是user-service只有重新启动后才能拿到修改后的值
如果有很多个用户微服务节点,那就要重新很多次才能都拿到更新后的值,这样就很麻烦。
10.1Spring Cloud Bus简介
Spring Cloud Bus是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件的更改或者服务的监控管理。也就是消息总线可以为微服务做监控,也可以实现应用程序之间相互通信。 Spring Cloud Bus 可选的消息代理有RabbitMQ 和 Kafka。需要注意的是 Spring Cloud Bus 底层是基于 RabbitMQ 实现的,默认使用本地的消息队列服务,所以需要提前启动本地RabbitMQ 服务
在消息队列中添加了“配置改变”的消息,当各个服务监听到消息改变时,就会刷新本地配置缓存 ,重新从配置中心获取配置
10.2 改变配置中心
在config-server中进行修改
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
修改配置文件
10.3 修改用户服务
在user-service中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
修改user-service中的bootstrap.yml
修改user-service 项目的UserController
修改git上面配置文件数据库连接的后缀,不然测试的时候可能会报错
url: jdbc:mysql://localhost:3306/hande?serverTimezone=GMT%2B8&useSSL=false
10.4 测试
- 依次启动注册中心 eureka-server 、配置中心 confifig-server 、用户服务 user-service
- 访问用户微服务http://localhost:9091/user/1;查看IDEA控制台输出结果
- 修改Git仓库中配置文件 user-dev.yml 的 test.name 内容
- 使用Postman或者RESTClient工具发送POST方式请求访问地址http://127.0.0.1:12000/actuator/bus-refresh
- 第五步:访问用户微服务系统控制台查看输出结果