目录
SpringCloud开发实战(一):搭建SpringCloud框架
SpringCloud开发实战(二):通过RestTemplate实现远程调用
SpringCloud开发实战(三):集成Eureka注册中心
SpringCloud开发实战(四):Feign远程调用
SpringCloud开发实战(五):Feign的一些优化建议
SpringCloud开发实战(六):Feign的最佳实践
一、介绍
Spring Cloud Gateway 是一个基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 的API网关。它是Spring官方推出的第二代API网关产品,旨在为微服务架构提供一种高效、灵活且强大的路由和网关服务。
主要特点:
- 动态路由:能够根据预先定义的规则,将请求路由到不同的微服务上。
- 过滤器功能:支持丰富的请求和响应处理能力,如修改请求/响应头、日志记录、断言等。
- 限流:可以设置请求速率限制,防止恶意攻击或意外的流量高峰导致服务崩溃。
- 断路器:集成了断路器功能,可以在后端服务不可用时快速响应,避免长时间等待。
- 协议代理:支持HTTP、HTTPS和WebSocket等多种协议。
- 易于配置:可以通过多种方式进行配置,如YAML文件、属性文件、环境变量或Spring Cloud Config。
- 集成性:与Spring生态系统中的其他组件紧密结合,如Spring Security、Spring Cloud Netflix等。
二、为什么我们需要网关
-
统一接口
统一网关提供了一个单一的入口点来访问所有的微服务。这意味着客户端不需要知道每个微服务的具体位置和如何调用,而是通过网关来访问,简化了客户端的逻辑。 -
协议转换
网关可以处理协议之间的转换工作,使得前端应用可以使用一种通用的协议(如HTTP)来访问后端服务,即使这些服务内部可能使用了不同的协议。 -
安全控制
网关可以集中实现认证、鉴权等安全机制,确保只有经过验证的请求才能到达后端服务。这样可以避免每个微服务都需要单独实现安全功能,降低了复杂度。 -
负载均衡
通过网关可以实现负载均衡,将请求分发到多个相同的服务实例上,从而提高系统的可用性和响应速度。 -
限流
为了保护后端服务不被过多的请求压垮,网关可以实施限流策略,限制单位时间内某个客户端或某个API接口的请求次数。 -
缓存
网关可以作为缓存层,存储常用或静态的数据,减少对后端服务的直接请求,从而提高整体性能。 -
监控与追踪
网关可以收集请求的元数据,如请求频率、响应时间等,帮助监控系统的健康状态,以及在出现问题时进行故障排查。 -
版本管理
通过网关,可以更容易地实现API版本管理,使得新旧版本的API可以在同一系统中共存,而不干扰现有的客户端。 -
跨域资源共享(CORS)
网关可以处理跨域请求,使前端应用可以从不同的源访问后端服务,简化了跨域访问的处理。 -
统一错误处理
网关可以统一处理来自各个微服务的错误响应,向客户端提供一致的错误消息格式,提升用户体验。
总结
统一网关通过集中处理诸如路由、安全、负载均衡等任务,简化了客户端与微服务的交互,并提高了系统的可维护性和扩展性。它不仅增强了系统的安全性,还提升了整体性能,是微服务架构不可或缺的一部分。
三、搭建网关
1. 创建gateway服务,引入依赖
创建服务
引入依赖:
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2. 编写启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3. 编写基础配置和路由规则
创建application.yml文件,内容如下:
server:
port: 10010 # 网关端口
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
spring:
application:
name: gateway # 服务名称
cloud:
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8082 # 路由的目标地址 http就是固定地址
uri: lb://userService # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
我们将符合Path
规则的一切请求,都代理到 uri
参数指定的地址。
本例中,我们将 /user/**
开头的请求,代理到lb://userService
,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。
4. 测试
启动网关,访问http://localhost:10010/user/1
时,符合/user/**
规则,请求转发到uri:http://userservice/user/1
,得到了结果:
5.网关路由的流程图
四、断言工厂
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。例如Path=/user/**是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个:
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=.somehost.org,.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
五、过滤器工厂
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
5.1 路由过滤器的种类
Spring提供了31种不同的路由过滤器工厂。我们就说几个常用的:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
5.2 请求头过滤器
下面我们以AddRequestHeader 为例来讲解,需求:给所有进入userService的请求添加一个请求头:token=this is test token
我们只需要修改gateway服务的application.yml文件,添加路由过滤即可:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userService
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=token,this is test token # 添加请求头
当前过滤器写在usersService路由下,因此仅仅对访问userService的请求有效。
然后我们修改UserController的/user/id
请求,增加一个新的参数。
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader("token") String token) {
System.out.println(token);
return userService.queryById(id);
}
让我们来测试一下,重启gateway和userService。我们请求一次userService的接口,就能看到控制台输出了token信息
5.3 默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userService
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=token, this is test token
六、全局过滤器
上面我们说到,gateway网关给我们提供了31种过滤器,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。所以我们需要一个全局过滤器,作用是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口。
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
在filter中编写自定义逻辑,我们可以实现下列功能:
- 登录状态判断
- 权限校验
- 请求限流等
比如下面我们来定义一个全局过滤器。拦截请求,判断请求的参数是否满足下面条件:
-
参数中是否有token,
-
token参数值是否为admin
如果同时满足则放行,否则拦截
实现
我们定义一个过滤器,实现GlobalFilter
接口
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.获取token参数
String auth = params.getFirst("token");
// 3.校验
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.拦截
// 4.1.禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
测试
我们重启gateway服务,发现现在请求http://localhost:10010/user/1
已经提示当前无法使用此页面,并且状态码是401
然后我们添加token参数,发现请求成功了。
过滤器执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
排序的规则是什么呢?
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
我们通过RouteDefinitionRouteLocator
的getFilters()方法可以看到,会去先加载defaultFilters,然后再加载某个route的filters,然后合并。
然后我们通过源码发现:org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链
七、跨域问题
什么是跨域
跨域:域名不一致就是跨域,主要包括:
-
域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
-
域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。后端也可以通过网关配置来解决这个问题。我们在gateway服务的application.yml文件中,添加下面的配置:
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求 也可以用*代表所有
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 3600 # 这次跨域检测的有效期