微服务网关实战——Spring Cloud Gateway
导读
作为Netflix Zuul的替代者,Spring Cloud Gateway是一款非常实用的微服务网关,在Spring Cloud微服务架构体系中发挥非常大的作用。本文对Spring Cloud Gateway常见使用场景进行了梳理,希望对微服务开发人员提供一些帮助。
微服务网关SpringCloudGateway
1.概述
Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。
2.核心概念
网关提供API全托管服务,丰富的API管理功能,辅助企业管理大规模的API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等贡呢。一般来说网关对外暴露的URL或者接口信息,我们统称为路由信息。如果研发过网关中间件或者使用过Zuul的人,会知道网关的核心是Filter以及Filter Chain(Filter责任链)。Sprig Cloud Gateway也具有路由和Filter的概念。下面介绍一下Spring Cloud Gateway中几个重要的概念。
路由。路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配
断言。Java8中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。
过滤器。一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理
如上图所示,Spring cloudGateway发出请求。然后再由Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway web handler。Handler再通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。
快速入门
以Spring Boot框架开发为例,启动一个Gateway服务模块(以Consul作为注册中心),一个后端服务模块。client端请求经gateway服务把请求路由到后端服务。
前提条件:
Consul:版本1.5.0。
Spring bot:版本2.1.5。
Spring cloud:版本Greenwich.SR1。
Redis:版本5.0.5。
1.微服务开发
这里以使用Spring Boot框架开发微服务为例,启动一个服务并注册到Consul。
引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
注册服务到Consul,配置文件配置如下:
spring:
application:
name: service-consumer
cloud:
consul:
host: 127.0.0.1
port:
discovery:
service-name: service-consumer
如下定义RestController,发布HTTP接口。
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping(value = "/info")
public User info() {
return userService.info();
}
}
注:此为服务端配置,经Gateway把请求路由转发到该服务上。
2.网关配置
创建一个Gateway服务,引入以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
启动类配置如下:
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
Spring Cloud Gateway对client端请求起到路由功能,主要配置如下:
server:
port:
spring:
application:
name: service-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
consul:
host: 127.0.0.1 #注册gateway网关到consul
port:
discovery:
service-name: service-gateway
此时使用http://localhost:8089/service-consumer/user/info访问服务,网关即可对服务进行路由转发,把请求转发到具体后端服务上。此时,url中使用的url前缀service-consumer,是后端服务在Consul注册的服务名称转为小写字母以后的字符串。
最佳实践
01
Gateway网关配置
本文第二部分开发规范中定义了网关进行路由转发的配置,除了上述配置方式还可以使用下面的方式进行配置:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: service_consumer
uri: lb://service-consumer
predicates:
- Path= /consumer/**
filters:
- StripPrefix=1
在上面的配置中,配置了一个Path的predicat,将以/consumer/**开头的请求都会转发到uri为lb://service-consumer的地址上,lb://service-consumer(注册中心中服务的名称)即service-consumer服务的负载均衡地址,并用StripPrefix的filter 在转发之前将/consumer去掉。同时将spring.cloud.gateway.discovery.locator.enabled改为false,如果不改的话,之前的http://localhost:8081/service-consumer/user/info这样的请求地址也能正常访问,因为这时为每个服务创建了2个router。
本文第二部分和本节一共讲述了两种配置方式,两种配置都可以实现请求路由转发的功能。参数spring.cloud.gateway.discovery.locator.enabled为true,表明Gateway开启服务注册和发现的功能,并且Spring Cloud Gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。spring.cloud.gateway.discovery.locator.lowerCaseServiceId是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了)。
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
02
Gateway跨域访问
Spring Cloud Gateway还针对跨域访问做了设计,可以使用以下配置解决跨域访问问题:
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "https://docs.spring.io"
allowedMethods:
- GET
allowHeaders:
- Content-Type
在上面的示例中,允许来自https://docs.spring.io的get请求进行访问,并且表明服务器允许请求头中携带字段Content-Type。
03
Gateway 过滤器
Spring Cloud Gateway的filter生命周期不像Zuul那么丰富,它只有两个:“pre”和“post”:
pre:这种过滤器在请求被路由之前调用。可以利用这个过滤器实现身份验证、在集群中选择请求的微服务、记录调试的信息。
post:这种过滤器在路由到服务器之后执行。这种过滤器可用来为响应添加HTTP Header、统计信息和指标、响应从微服务发送给客户端等。
Spring Cloud gateway的filter分为两种:GatewayFilter和Globalfilter。GlobalFilter会应用到所有的路由上,而Gatewayfilter将应用到单个路由或者一个分组的路由上。
利用Gatewayfilter可以修改请求的http的请求或者是响应,或者根据请求或者响应做一些特殊的限制。更多时候可以利用Gatewayfilter做一些具体的路由配置。
下面的配置是AddRequestParameter Gatewayfilter的相关配置。
spring:
application:
name: service-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: parameter_route
uri: http://localhost:8504/user/info
filters:
- AddRequestParameter=foo, bar
predicates:
- Method=GET
上述配置中指定了转发的地址,设置所有的GET方法都会自动添加foo=bar,当请求符合上述路由条件时,即可在后端服务上接收到Gateway网关添加的参数。
另外再介绍一种比较常用的filter,即StripPrefix gateway filter。
配置如下:
spring:
cloud:
gateway:
routes:
- id: stripprefixfilter
uri: lb://service-consumer
predicates:
- Path=/consumer/**
filters:
- StripPrefix=1
当client端使用http://localhost:8098/consumer/user/info路径进行请求时,如果根据上述进行配置Gateway会将请求转换为http://localhost:8098/service-consumer/user/info。以此作为前端请求的最终目的地。
04
Gateway请求匹配
Gateway网关可以根据不同的方式进行匹配进而把请求分发到不同的后端服务上。
通过header进行匹配,把请求分发到不同的服务上,配置如下:
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://baidu.com
predicates:
- Header=X-Request-Id, \d+
通过curl测试:curl http://localhost:8080 -H "X-Request-Id:666666",返回页面代码证明匹配成功。
如果是以Host进行匹配,配置如下:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://baidu.com
predicates:
- Host=**.baidu.com
通过curl http://localhost:8098 -H "Host: www.baidu.com"进行测试,返回页面代码即转发成功。
可以通过POST、GET、PUT、DELTE等不同的方式进行路由:
spring:
cloud:
gateway:
routes:
- id: method_route
uri: http://baidu.com
predicates:
- Method=GET
通过 curl http://localhost:8098 进行测试,返回页面代码即表示成功。
上述是单个匹配进行路由,如果把多个匹配合在一起进行路由,必须满足所有的路有条件才会进行路由转发。
05
Gateway熔断
Spring Cloud Gateway也可以利用Hystrix的熔断特性,在流量过大时进行服务降级,同时项目中必须加上Hystrix的依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置后,Gateway将使用fallbackcmd作为名称生成HystrixCommand对象进行熔断处理。如果想添加熔断后的回调内容,需要添加以下配置:
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: lb://consumer-service
predicates:
- Path=/consumer/**
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
- StripPrefix=1
hystrix:
command:
fallbackcmd:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000 #超时时间,若不设置超时时间则有可能无法触发熔断
上述配置中给出了熔断之后返回路径,因此,在Gateway服务模块添加/fallback路径,以作为服务熔断时的返回路径。
@RestController
public class GatewayController {
@RequestMapping(value = "/fallback")
public String fallback(){
return "fallback nothing";
}
}
fallbackUri: forward:/fallback配置了 fallback 时要会调的路径,当调用 Hystrix 的 fallback 被调用时,请求将转发到/fallback这个 URI,并以此路径的返回值作为返回结果。
06
Gateway重试路由器
通过简单的配置,Spring Cloud Gateway就可以支持请求重试功能。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://localhost:8504/user/info
predicates:
- Path=/user/**
filters:
- name: Retry
args:
retries: 3
status: 503
- StripPrefix=1
Retry GatewayFilter通过四个参数来控制重试机制,参数说明如下:
retries:重试次数,默认值是 3 次。
statuses:HTTP 的状态返回码,取值请参考:org.springframework.http.HttpStatus。
methods:指定哪些方法的请求需要进行重试逻辑,默认值是 GET 方法,取值参考:org.springframework.http.HttpMethod。
series:一些列的状态码配置,取值参考:org.springframework.http.HttpStatus.Series。符合的某段状态码才会进行重试逻辑,默认值是 SERVER_ERROR,值是 5,也就是 5XX(5 开头的状态码),共有5个值。
使用上述配置进行测试,当后台服务不可用时,会在控制台看到请求三次的日志,证明此配置有效。
07
Gateway 限流操作
Spring Cloud Gateway本身集成了限流操作,Gateway限流需要使用Redis,pom文件中添加Redis依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置文件中配置如下:
spring:
cloud:
gateway:
routes:
- id: rate_limit_route
uri: lb://service-consumer
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter
args:
key-resolver: "#{@hostAddrKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
- StripPrefix=1
consul:
host: 127.0.0.1
port: 8500
discovery:
service-name: service-gateway
instance-id: service-gateway-233 redis:
host: localhost
port: 6379
在上面的配置问价中,配置了Redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:
BurstCapacity:令牌桶的总容量。
replenishRate:令牌通每秒填充平均速率。
Key-resolver:用于限流的解析器的Bean对象的名字。它使用SpEL表达式#{@beanName}从Spring容器中获取bean对象。
注意:filter下的name必须是RequestRateLimiter。
Key-resolver参数后面的bean需要自己实现,然后注入到Spring容器中。KeyResolver需要实现resolve方法,比如根据ip进行限流,则需要用hostAddress去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。还可以根据uri限流,同hostname限流是一样的。例如以ip限流为例,在gateway模块中添加以下实现:
public class HostAddrKeyResolver implements KeyResolver { @Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
} public HostAddrKeyResolver hostAddrKeyResolver() {
return new HostAddrKeyResolver();
}
}
把该类注入到spring容器中:
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication { public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
} @Bean
public HostAddrKeyResolver hostAddrKeyResolver(){
return new HostAddrKeyResolver();
}
}
基于上述配置,可以对请求基于ip的访问进行限流。
08
自定义Gatewayfilter
Spring Cloud Gateway内置了过滤器,能够满足很多场景的需求。当然,也可以自定义过滤器。在Spring Cloud Gateway自定义过滤器,过滤器需要实现GatewayFilter和Ordered这两个接口。
下面的例子实现了Gatewayfilter,它可以以log日志的形式记录每次请求耗费的时间,具体实现如下:
public class RequestTimeFilter implements GatewayFilter, Ordered {
private static final Log log = LogFactory.getLog(GatewayFilter.class);
private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
if (startTime != null) {
log.info("请求路径:"+exchange.getRequest().getURI().getRawPath() + "消耗时间: " + (System.currentTimeMillis() - startTime) + "ms");
}
})
);
}
@Override
public int getOrder() {
return ;
}
} 上述代码中定义了自己实现的过滤器。Ordered的int getOrder()方法是来给过滤器定优先级的,值越大优先级越低。还有一个filter(ServerWebExchange exchange, GatewayFilterChain chain)方法,在该方法中,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器。然后再chain.filter()的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。 接下来将该过滤器注册到router中,代码如下。 @Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path("/user/**")
.filters(f -> f.filter(new RequestTimeFilter())
.addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
.uri("http://localhost:8504/user/info")
.order()
.id("customer_filter_router")
)
.build();
}
除了上述代码的方式配置我们自定义的过滤器的方式之外,也可以在application.yml文件中直接配置,这里不再赘述。
启动程序,通过curl http://localhost:8098/user/info控制台会打印出请求消耗时间,日志如下:
....
-- ::31.221 INFO --- [ctor-http-nio-] o.s.cloud.gateway.filter.GatewayFilter : 请求路径:/user/info消耗时间: 54ms
...
-- ::23.785 INFO --- [ctor-http-nio-] o.s.cloud.gateway.filter.GatewayFilter : 请求路径:/user/info3消耗时间: 5ms
....
09
自定义GlobalFilter
Spring Cloud Gateway根据作用范围分为GatewayFilter和GlobalFilter,二者区别如下:
GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上。
GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
在上一小节中定义的是Gatewayfilter,下面实现的是Globalfilter:
public class TokenFilter implements GlobalFilter, Ordered {
Logger logger= LoggerFactory.getLogger( TokenFilter.class );
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (token == null || token.isEmpty()) {
logger.info( "token 为空,无法进行访问." );
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
} @Override
public int getOrder() {
return ;
}
}
上述代码实现了Globalfilter,具体逻辑是判断请求中是否含参数token,如果没有,则校验不通过,对所有请求都有效。如果含有token则转发到具体后端服务上,如果没有则校验不通过。
通过curl http://localhost:8098/user/info进行访问,因为路径中不含有参数token,则无法通过校验,打印日志如下:
-- ::11.078 INFO --- [ctor-http-nio-] com.song.gateway.TokenFilter : token 为空,无法进行访问.
...
通过curl http://localhost:8098/user/info?token=123进行访问时,则可以获取到后端服务返回结果。
微服务网关实战——Spring Cloud Gateway的更多相关文章
- 微服务架构之spring cloud gateway
Spring Cloud Gateway是spring cloud中起着非常重要的作用,是终端调用服务的入口,同时也是项目中每个服务对外暴露的统一口径,我们可以在网关中实现路径映射.权限验证.负载均衡 ...
- Spring Cloud 微服务五:Spring cloud gateway限流
前言:在互联网应用中,特别是电商,高并发的场景非常多,比如:秒杀.抢购.双11等,在开始时间点会使流量爆发式地涌入,如果对网络流量不加控制很有可能造成后台实例资源耗尽.限流是指通过指定的策略削减流量, ...
- Spring Cloud与微服务构建:Spring Cloud简介
Spring Cloud简介 微服务因该具备的功能 微服务可以拆分为"微"和"服务"二字."微"即小的意思,那到底多小才算"微&q ...
- .net core下,Ocelot网关与Spring Cloud Gateway网关的对比测试
有感于 myzony 发布的 针对 Ocelot 网关的性能测试 ,并且公司下一步也需要对.net和java的应用做一定的整合,于是对Ocelot网关.Spring Cloud Gateway网关做个 ...
- SpringCloud Gateway微服务网关实战与源码分析-上
概述 定义 Spring Cloud Gateway 官网地址 https://spring.io/projects/spring-cloud-gateway/ 最新版本3.1.3 Spring Cl ...
- SpringCloudGateway微服务网关实战与源码分析 - 中
实战 路由过滤器工厂 路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应.路由过滤器的作用域是特定的路由.SpringCloud Gateway包括许多内置的GatewayFilter ...
- 微服务架构-选择Spring Cloud,放弃Dubbo
Spring Cloud 在国内中小型公司能用起来吗?从 2016 年初一直到现在,我们在这条路上已经走了一年多. 在使用 Spring Cloud 之前,我们对微服务实践是没有太多的体会和经验的.从 ...
- 微服务架构集大成者—Spring Cloud (转载)
软件是有生命的,你做出来的架构决定了这个软件它这一生是坎坷还是幸福. 本文不是讲解如何使用Spring Cloud的教程,而是探讨Spring Cloud是什么,以及它诞生的背景和意义. 1 背景 2 ...
- 微服务架构之spring cloud 介绍
在当前的软件开发行业中,尤其是互联网,微服务是非常炽热的一个词语,市面上已经有一些成型的微服务框架来帮助开发者简化开发工作量,但spring cloud 绝对占有一席之地,不管你是否为java开发,大 ...
随机推荐
- VC-基础:MFC单文档程序架构解析
MFC单文档程序架构解析 这里我以科院杨老师的单文档程序来分析一下MFC单文档的程序架构,纯属个人见解,不当之处烦请指教! 首先我们了解到的是 图(一) theApp 是唯一一个在程序形成的时候就存在 ...
- 多数据源连接Oracle报错,linux熵池耗尽问题
最近碰到了个很有意思的问题,springboot加载多数据源,遇到了在启动时数据库连接报错的问题. 报错信息: The error occurred while executing a query 然 ...
- Linux安全调优1:CentOS防火墙的设置与优化
CentOS防火墙的设置与优化 时间:2014-09-11 02:11来源:blog.csdn.net 作者:成长的小虫 的BLOG 举报 点击:4908次 一.设置主机防火墙. 开放: 服务器的:w ...
- 2018 CCF NOIP提高组&&普及组答案
答案: 这是今年的答案大家觉得能进到复赛吗? 下一篇文章将会为大家推荐我自己出的复赛题!!!
- perl学习之:匹配修饰符/s /m
m 是将字符串作为多行处理,s是将字符串作为单行处理,如果是s在字符串中出现的\n就相当于普通字符. 6.6. Matching Within Multiple Lines6.6.1. Problem ...
- 【php】关于尾部去除和分号问题
One thing to remember is, if you decide to omit the closing PHP tag, then the last line of the file ...
- js对象,数组,字符串的操作
循环绑定=>变量污染 for (var i = 0;i<lis.length;i++){ lis[i].index = i;#给页面元素对象添加一个任意属性(保留索引的属性index) # ...
- 我的第一个ajax脚本
代码如下 //创建XMLHttpRequest对象 var xmlHttp=null; function creatXMLHttp(){ try{ xmlHttp = new XMLHttpReque ...
- 4A. Just a Hook
4A. Just a Hook Time Limit: 2000ms Case Time Limit: 2000ms Memory Limit: 32768KB 64-bit integer IO ...
- ASP.NET(五):ASP.net实现真分页显示数据
导读:在上篇文章中,介绍了用假分页实现数据的分页显示 ,而避免了去拖动滚动条.但,假分页在分页的同时,其实是拖垮了查询效率的.每一次分页都得重新查询一遍数据,那么有没有方法可以同时兼顾效率和分页呢,那 ...