技术标签: Gateway SpringCloud Spring Cloud 学习
GateWay 是 Spring 生态系统之上构建的API网关服务, 基于 Spring5,SpringBoot 2 和Project Reactor 等技术。
Gateway 旨在提供一种简单而有效的方式来对 API 进行路由, 以及提供一些强大的过滤功能, 例如 熔断, 限流, 重试等。
SpringCloud Gateway 是SpringCloud 的一个全新项目,基于Spring 5 和 Spring Boot 2.0 Project Reactor 等技术开发的网关, 他是为了微服务提供一种简单有效的API路由管理方式
SpriingCloud Gateway 作为 Spring Cloud 生态系统中的网关, 目标是替代 Zuul,在Spring Cloud 2.0以上版本, 没有新版本的 Zuul2.0 以上最新版本的进行集成,仍然还是使用Zuul 1.x 非Reactor 模式的老版本, 而为了提升网关的性能, Spring Cloud Gateway 是基于 WebFlux 框架实现的, 而WebFlux 框架底层则使用了高性能的Reactor 模式通信的框架 Netty
SpringCloud Gateway 的目标是提供统一的路由方式且基于 Filter 链的方式提供了网关的基本功能, 例如: 安全, 监控、 限流,
传统的Web 框架,比如说 struts2, SpringMVC 等都是基于 Servlet API和servlet 容器基础之上,但是,在Servlet 3.1 之后有了异步非阻塞的支持,而WebFlux 是一个典型的非阻塞异步的框架,他的核心是基于 Reactor 的相关API实现的,相对于传统的 web 框架来说, 他可以在Netty, Undertow 及 支持Servlet 3.1 的容器上, 非阻塞式 + 函数式编程
Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于SpringMVC, 他不需要依赖 Servlet API, 他是完全异步非阻塞的。 并且基于 Reactor来实现响应式规范。
官方示意图
客户端向Gateway 发出请求, 然后在Gateway Handler Mapping 中找到请求相匹配的路由, 将其发送到Gateway Web Handler
Handler 在通过制定的过滤器链来讲请求发送到我们实际的服务执行业务逻辑,然后返回,过滤器链之间用虚线分开是因为过滤器可能会在发送代理之前 (“pre”) 或之后 (“post”)
sgg-gateway-api9527
引入依赖
<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-gateway</artifactId>
</dependency>
<dependency>
<groupId>cn.fllday</groupId>
<artifactId>sgg-api-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
server:
port: 9527
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true # 开启动态路由
routes:
- id: payment-service # 路由唯一id
uri: lb://SGG-PAYMENT-SERVICE # lb 代表 负载均衡loadBalances , 后面跟着的是微服务名称 在 eureka 中注册的名字
predicates: # 断言
- Path=/** # 判断是否匹配
eureka:
instance:
hostname: cloud-gateway-service
instance-id: cloud-gateway-service-${
server.port}
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
@SpringBootApplication
@EnableEurekaClient
public class Gateway9527App {
public static void main(String[] args) {
SpringApplication.run(Gateway9527App.class, args);
}
}
顺序启动 7001, 7002,8001, 8002, 9527 。
通过我们的网关访问我们的payment 提供的接口
http://localhost:9527/SGG-PAYMENT-SERVICE/payment/111
。 SGG-PAYMENT-SERVICE
就是我们配置的 lb 负载均衡 的服务名 后面就匹配我们的 -Path
断言
可以看到我们通过网关访问, 网关顺利转发到真正的服务接口上。 同时还具有负载均衡的效果。
都可以试试。
有兴趣的可以自己尝试一下。 官网介绍
我们可以看一下其他的断言类。发现都是继承自 AbstractRoutePredicateFactory
我们也来尝试自己定义一个 断言工厂
注意项:
创建 PortPredicateFactory.java
@Component
@Slf4j
public class PortPredicateFactory extends AbstractRoutePredicateFactory<PortPredicateFactory.Config> {
public PortRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return serverWebExchange -> {
ServerHttpRequest request = exchange.getRequest();
ApplicationContext applicationContext = exchange.getApplicationContext();
if (config.getPort().equals(new Integer("8001"))) {
return true;
}
return false;
};
}
@Data
public class Config {
private Integer port;
}
}
上面的意思就是如果 端口号为 8001 就可以执行, 不等于 8001 就不执行。 可以看到 通过 exchange
参数我们可以获取到很多东西。 这个时候就可以通过这些东西做很多事情。 具体的我就不演示了。 现在配置yml
文件试试吧。
修改 yml 文件
routes:
- id: payment-service
uri: lb://SGG-PAYMENT-SERVICE
predicates:
- Path=/**
- Port=8001
重启服务访问 接口。
使用 8001 的时候,访问没有问题。 我们修改配置类将 -Port=8002
试试
一下都是转自 芋道源码
Gateway 内置了许多种 Route Filter 实现。 对请求进行拦截 实现自定义的功能, 比如说,限流。熔断等功能,并且, 多个 Route Filter 可以组合实现, 满足我们大多数的处理逻辑
创建AuthGatewayFilterFactory
认证filter工厂
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
public AuthGatewayFilterFactory() {
super(AuthGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config) {
Map<String, Integer> tokenMap = new HashMap<>();
tokenMap.put("gss", 1);
// 创建 gatewayfilter 对象
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst(config.getTokenHeaderName());
// 如果没有 token 不进行认证
if (!StringUtils.hasText(token)) {
return chain.filter(exchange);
}
// 认证开始
ServerHttpResponse response = exchange.getResponse();
Integer userId = tokenMap.get(token);
// 通过 token 获取不到 userId 说明认证不通过
if (userId == null) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
DataBuffer wrap = exchange.getResponse().bufferFactory().wrap("认证未通过".getBytes());
return response.writeWith(Flux.just(wrap));
}
request = request.mutate().header(config.getUserIdHeaderName(), String.valueOf(userId)).build();
return chain.filter(exchange.mutate().request(request).build());
}
};
}
public static class Config {
private static final String DEFAULT_TOKEN_HEADER_NAME = "token";
private static final String DEFAULT_HEADER_NAME = "user-id";
private String tokenHeaderName = DEFAULT_TOKEN_HEADER_NAME;
private String userIdHeaderName = DEFAULT_HEADER_NAME;
public static String getDefaultTokenHeaderName() {
return DEFAULT_TOKEN_HEADER_NAME;
}
public static String getDefaultHeaderName() {
return DEFAULT_HEADER_NAME;
}
public String getTokenHeaderName() {
return tokenHeaderName;
}
public void setTokenHeaderName(String tokenHeaderName) {
this.tokenHeaderName = tokenHeaderName;
}
public String getUserIdHeaderName() {
return userIdHeaderName;
}
public void setUserIdHeaderName(String userIdHeaderName) {
this.userIdHeaderName = userIdHeaderName;
}
}
}
通过@Compoent
注解 保证 Gateway 在加载所有的 GatewayFilterFactory Bean 的时候,能够加载到我们的自定义AuthGatewayFilterFactory
, 有没有发现和自定义断言的时候 一毛一样 哈哈哈
继承 AbstractGatewayFilterFactory
抽象类。 并将泛型参数<C>
设置为我们自己定义的 AuthGatewayFilterFactory.Config
配置类, 这样,Gateway 在解析的时候,会转换成 Config 对象
注意:在AuthGatewayFilterFactory 构造方法中,需要传递 Config 类给父级构造方法,保证能够正确创建出Config 对象。在Config 类中我们定义了两个属性。
- tokenHeaderName
: 认证token 的 header 的名字, 默认值为 token
- userIdHeaderName
: 认证后的UserId 的 header 名字, 默认为 user-id
在方法 apply(Config config)
中, 我们通过内部类定义了需要创建的GatewayFilter。
spring:
cloud:
gateway:
default-filters:
- name: Auth
args:
token-header-name: access-token
spring.cloud.gateway.default-filters
配置项, Gateway 默认过滤器,对所有的路由都生效。对应 FilterDefinition 数组, 在这里我们配置之了一个自定义的 Filter 配置
- name
: 过滤器名称,这里我们设置为Auth
, 因为 Gateway 默认使用的 XXXGatewayFilterFactory
的前缀 XXX
名字。 因为 AuthGatewayFilterFactory
就是 Auth
- args
: 过滤器的参数配置, 对应 Config 类,这里我们设置 token-header-name
配置项 access-token
, 表示从请求头 access-token
中获取认证 token
使用 PostMan 请求接口
使用 access-token 为 gss 时候
Gateway 内置 RequestRateLimiterGatewayFilterFactory
提供请求限流功能。 该 Filter
是基于 Token Bucket Algorithm
令牌桶算法。 同时搭配 redis 实现分布式限流。
限流需要使用到 redis 我们先启动一下 redis, 然后引入redis 的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置 yml 文件
spring:
redis:
host: 127.0.0.1
port: 6379
cloud:
gateway:
default-filters:
- name: Auth
args:
token-header-name: access-token
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶的每秒放的数量
redis-rate-limiter.burstCapacity: 2 # 令牌桶的最大令牌数
key-resolver: "#{@ipKeyResolver}" # 获取限流key 的bean 的名字
说一下。 yml 文件配置是增量的。 在上面的基础上添加。 不是删除掉。在添加这个
再次添加一个 default-filters
配置项 。 添加了限流过滤器 RequestRateLimiter
配置参数:
- redis-rate-limiter.replenishRate
: 1 # 令牌桶的每秒放的数量
- redis-rate-limiter.burstCapacity
: 2 # 令牌桶的最大令牌数
- key-resolver
: 获取限流key 的Bean 的名字
burstCapacity 参数: 可以理解为每秒最大的请求数,因此每请求一次,都会从桶里获取一块令牌
ReplenishRate 参数: 可以近似理解为每秒平均的请求数,如果令牌桶为空的情况下,一秒最多放这么多令牌书, 所以最大请求数当然也是这么多
实际上,在令牌桶满的情况下, 每秒最大请求书是 burstCapacity + ReplenishRate
在 GatewayConfig 配置类下创建获取限流Key 的Bean
@Bean
public KeyResolver ipKeyResolver(){
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
};
}
创建的 ipKeyResolver Bean 是通过解析请求的来源 IP 作为限流 KEY,这样我们就能实现基于 IP 的请求限流。我们想要实现基于用户的请求限流,那么我们可以创建从请求中解析用户身份的 KeyResolver Bean。也就是说,通过自定义的 KeyResolver 来实现不同粒度的请求限流
使用浏览器,疯狂访问 http://localhost:9527/SGG-PAYMENT-SERVICE/payment/1
就会被限流。 出现 429 页面。
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
创建 FallbackController
,提供 /fallback
接口。 用于Hystrix fallback 的重定向。
@RestController
@Slf4j
public class FallbackController {
@GetMapping(value = "fallback")
public String fallback(ServerWebExchange exchange) {
Object requestURL = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
Object error = exchange.getAttribute(ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR);
log.error("fallback 发生异常: [ {} ], [ {} ]", error, requestURL);
return "服务降级。。。" + error;
}
}
通过 exchange.getAttribute(ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR)
可以获取具体的fallback 异常。
配置 yml 文件
spring:
redis:
host: 127.0.0.1
port: 6379
cloud:
gateway:
default-filters:
- name: Hystrix
args:
name: fallbackcmd # 对应 Hystrix Command 名字
fallbackUri: forwad:/fallback # 处理 Hystrix fallback 的情况, 重定向到指定地址
在 filters 中配置项,添加了 Hystrix 过滤器。 其配置参数如下。:
name
: 对应的 Hystrix Command 名字,后续可以通过 hystrix.command.{name} 配置项, 来设置 name 对应的 Hystrix Command 的配置, 比如说超时时间,隔离策略等。fallbackUri
: 处理 Hystrix fallback 的情况。 重定向到指定地址,主要 要么为空 要么必须以forward:
开头
访问 timeout 超时接口。 可以看到出现异常了就会转发到我们的 fallback
在Gateway 中, 有两类过滤器
- Route Filter 路由过滤器 对应GatewayFilter
接口
- Global Filter 全局过滤器 对应 GlobalFilter
接口
两者基本是等价的,不同的是 Route Filter不是全局,可以配置在 指定路由上。 绝大多数情况, RouteFilter 能满足我们的拓展需求的情况下,优先使用它, 并且如果想要作用到所有的路由上。可以使用 spring.cloud.gateway.default-filters
配置上。另外 Global Filter 可能在未来的版本有一定的变化。
Gateway的过滤器的执行分成了前置 pre 和 后置 post 两个阶段,其中低排序值的过滤器在pre 阶段先执行, 高排序值得过滤器在post阶段限制性。 示例代码 。 创建了三个 filter
@Component
@Order(1)
@Slf4j
public class FGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("[F, [pre]]");
return chain.filter(exchange).then(Mono.<Void>fromRunnable(()->log.info("[F, [post]]")));
}
}
@Component
@Order(2)
@Slf4j
public class SGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("[S, [pre]]");
return chain.filter(exchange).then(Mono.<Void>fromRunnable(()->log.info("[S, [post]]")));
}
}
@Component
@Order(3)
@Slf4j
public class TGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("[T, [pre]]");
return chain.filter(exchange).then(Mono.<Void>fromRunnable(()->log.info("[T, [post]]")));
}
}
重启接口,发现打印和我们之前说的一样
2020-09-13 01:24:05.068 INFO 16968 --- [ioEventLoop-4-1] cn.fllday.filter.FGlobalFilter : [F, [pre]]
2020-09-13 01:24:05.070 INFO 16968 --- [ioEventLoop-4-1] cn.fllday.filter.SGlobalFilter : [S, [pre]]
2020-09-13 01:24:05.070 INFO 16968 --- [ioEventLoop-4-1] cn.fllday.filter.TGlobalFilter : [T, [pre]]
2020-09-13 01:24:05.198 INFO 16968 --- [ctor-http-nio-4] cn.fllday.filter.TGlobalFilter : [T, [post]]
2020-09-13 01:24:05.198 INFO 16968 --- [ctor-http-nio-4] cn.fllday.filter.SGlobalFilter : [S, [post]]
2020-09-13 01:24:05.198 INFO 16968 --- [ctor-http-nio-4] cn.fllday.filter.FGlobalFilter : [F, [post]]
Gateway的 actuate
模块, 基于 SpringBoot Actuator , 提供了 自动以监控断点 gateway
, 提供了 Gateway 的各种监控管理的功能
路径 | 用途 |
---|---|
GET /globalfilters | 获取所有的 GlobalFilter |
GET /routefilters | 获取所有GatewayFilterFactory |
GET /routepredicates | 后去所有的RoutePredicateFactory |
GET /routes | 获取所有的路由 |
GET /routes/{id} | 获取指定路由 |
GET /routes/{id}/combinedfilters | 获得指定路由的过滤器 |
POST /routes/{id} | 新增或修改,参数为 RouteDefinition |
DELETE /routes/{id} | 删除指定路由 |
POST /refresh | 刷新路由缓存 |
注意: 所有的基础路径为 /actuator/gateway
,所以 /globalfilters
对应的完整路径是 /actuator/gateway/globalfilters
引入 pom 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
修改 yml 文件 , 配置 Spring Boot Actuator
management:
endpoints:
web:
exposure:
include: '*' # 需要开放的端点,默认只开放 health 和 info 两个端点,通过 * 开放所有的端点。
endpoint:
health:
enabled: true # 是否开启, 默认true 开启
show-details: always # 何时显示完整的健康信息。 默认 never 都不展示。 可选择 WHEN_AUTHORIZED 当经过授权的用户, 可选 always 总是显示
获取 filter 和 predicate
GlobalFilter: http://localhost:9527/actuator/gateway/globalfilters
GatewayFilterFactory: http://localhost:9527/actuator/gateway/routefilters
Predicates: http://localhost:9527/actuator/gateway/routepredicates
路由管理
使用 PostMan 请求GET /actuator/gateway/routes/{id}
获取一个路由详情
然后根据这个 我们新建一个路由
POST /actuator/gateway/routes/{id}
添加完成之后,我们通过 使用 PostMan 请求GET /actuator/gateway/routes/baidutieba
获取一个路由详情
如果不行的话,记得使用refresh 刷新一下 POST /actuator/gateway/refresh
然后不要重启服务。 直接访问 http://localhost:9527/
就会直接跳转到百度贴吧咯
修改 yml 文件。配置日记级别如下
logging:
level:
reactor.netty: DEBUG
org.springframework.cloud.gateway: TRACE
这样,SpringCloudGateway 和 Reactor Netty 可以打印更多的日志
重启网关。
Gateway日志。 其他 Reactor Netty 日志太多。 就不截图了。
Reactor Netty 提供了 Wiretap 窃听功能。 让 Reactor Netty 打印包含请求和响应信息的日志, 比如说请求和响应的Header Body 等等, 开启 Reactor Netty 的Wiretap 功能一共有三个配置项
reactor.netty
的配置项为DEBUG 和 TRACEspring.cloud.gateway.httpserver.wiretap
配置项为 true
开启 HttpServer Wiretap 功能spring.cloud.gateway.httpclient.wiretap
配置项为 true
开启 Http Client wiretap 功能 httpserver: # 配置 Reactor Netty 相关配置
wiretap: true
httpclient:
wiretap: true
好多东西。截图放着了。
感谢B站尚硅谷老师哈哈 ~~~ 本文参考了
芋道源码 感谢!@!!
文章浏览阅读6.9k次。最近使用react官方脚手架create-react-app建立项目的时候发现在ie浏览器打开时显示空白,在主流的chrome、fireFox等浏览器显示是正常的。打开控制台显示如下既然提示了语法错误,那么猜想应该是兼容性的问题,看了下浏览器的版本号是ie11。首先我翻了下create-react-app的文档,从中看到了正好有对ie9、ie10、ie11的兼容性问题解决的一个方案。这时需要..._react ie浏览器下路由视图空白
文章浏览阅读122次。网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就很关键。因此文件信息的管理计算机化,系统化是必要的。设计开发网盘管理系统不仅会节约人力和管理成本,还会安全保存庞大的数据量,对于文件信息的维护和检索也不需要花费很多时间,非常的便利。网盘管理系统是在MySQL中建立数据表保存信息,运用Vue框架和Java语言编写。并按照软件设计开发流程进行设计实现。系统具备友好性且功能完善。_ssm实现网盘功能
文章浏览阅读2k次。#xxl-job文档地址1.https://www.xuxueli.com/xxl-job/#Elastic-job 文档地址2.http://elasticjob.io/index_zh.html3.建议使用xxl-job,原因如下:偏重量级框架; 依赖Spring,mysql,maven手动编译; 提供的demo众多; GUI编写任务代码; GUI发布任务; 提供..._xxljob elsticjob
文章浏览阅读94次。http://nshipster.cn转载于:https://www.cnblogs.com/Seeulater/p/5236805.html
文章浏览阅读3.2k次,点赞8次,收藏25次。这里分析linemod数据集预处理的代码,位置在datasets/linemod/dataset.py我们一行一行的分析。import torch.utils.data as dataclass PoseDataset(data.Dataset): def __init__(self, mode, num, add_noise, root, noise_trans, refine): self.objlist = [1, 2, 4, 5, 6, 8, 9, 10, 11,_line mod 数 据集
文章浏览阅读972次。// 流式布局 话不多说,比较简单,注释都写的很清楚import java.util.ArrayList;import java.util.List;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGr_andorid 使用流式布局实现便签页面
文章浏览阅读1.6k次。chm手册打开空白的解决方法_php chm没有内容
文章浏览阅读1.4k次。特征点是什么SLAM需要根据路标来计算当前相机的位置和姿态。而视觉SLAM的路标就是图像中的特征点了。注意:只要谈到图像中的特征点你就得记得它包含两个内容关键点和描述子。关键点指的特征点在图像中的位置,而描述子是指的是关键点的朝向和周围像素信息。相同特征..._orbslam特征点 描述子
文章浏览阅读603次。文章目录Java为数据结构中的映射定义了一个接口java.util.Map。它包含三个类:HashMap、HashTable和TreeMap。Map是用来存储键值对的数据结构,在数组中通过数组下标来对其内容索引的,而在Map中,则是通过对象进行索引,用来索引的对象称作是Key,其对应的对象叫做value。HashMap是最常用的Map,它根据键值对的HashCode值存储数据,根据键可以直接获..._hashmap和treemap和hashtable的区别 面试题
文章浏览阅读7.9k次,点赞7次,收藏63次。asreml中,用于比较模型的LRT检验,会给出P值,但是这只能表示两个模型达到显著与否,而不能表示哪个模型优秀。常用的参数有AIC,BIC,loglikelihood,本篇介绍一下这几个参数的含义,以及是如何计算的1. AIC的解释赤池信息准则(Akaike Information Criterion,AIC)AIC是衡量统计模型拟合优良性的一种标准,由日本统计学家赤池弘次在1974年提出,它建立在熵的概念上,提供了权衡估计模型复杂度和拟合数据优良性的标准。通常情况下,AIC定义为:AIC=−2_log likelihood越大越好还是越小越好
文章浏览阅读1.1k次。使用注解WebServlet配置Servlet报404错误的原因 Servlet3.0之后新增了注解,用于简化Servlet、Filter及Listener的声明,这样就在配置Servlet的时候多了一个选择。Servlet3.0的部署描述文件web.xml的顶层标签<web-app>有一个metadata-complete属性,该属性为true,则容器在部署时只依赖部..._servlet 注释 远程404
文章浏览阅读321次。对于奇偶有了更好的写法,如果是偶数i=n+1,如果是奇数就为i=n_$j=2; for( $i=7; $i>0; $i-=2 ){ $j*=2;}