Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。
优点:
性能强劲:是第一代网关Zuul的1.6倍
功能强大:内置了很多实用的功能,例如转发、监控、限流等
设计优雅,容易扩展
缺点:
其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行
需要Spring Boot 2.0及以上的版本,才支持
路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:
id,路由标识符,区别于其他 Route。
uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
filter,过滤器用于修改请求和响应信息。
创建新的服务
shop-apiGateway-hdw
<!--加入gateway的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
server:
port: 7000
spring:
application:
name: api-gateway
# 配置api
cloud:
gateway:
routes:
- id: product_route # 路由的唯一标识,只要不重复都可以,如果不写默认会通过UUID产生,一般写成被路由的服务名称
uri: http://localhost:8082/ # 被路由的地址
order: 1 #表示优先级 数字越小优先级越高
predicates: #断言: 执行路由的判断条件
- Path= /product_serv/**
filters: # 过滤器: 可以在请求前或请求后作一些手脚
- StripPrefix=1
启动shop-product和shop-apigateway,访问
http://localhost:7000/product_serv/product/productById/2
加入nacos+gateway
<!--加入gateway的依赖-->
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-alibaba-nacos-discovery
application.yml
#简写版
server:
port: 7000
spring:
application:
name: api-gateway
# 配置api
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true
启动类
@SpringBootApplication
@EnableDiscoveryClient //nacos服务发现的注解
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class,args);
}
}
通过网关访问
http://localhost:7000/shop-product/product/productById/3
这时候,就发现只要按照网关地址/微服务/接口的格式去访问,就可以得到成功响应
Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。
断言就是说: 在 什么条件下 才能进行路由转发
基于Datetime类型的断言工厂
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
基于远程地址的断言工厂 RemoteAddrRoutePredicateFactory:
接收一个IP地址段,判断请求主机地址是否在地址段中
-RemoteAddr=192.168.1.1/24
基于Cookie的断言工厂
CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求
cookie是否具有给定名称且值与正则表达式匹配。
-Cookie=chocolate, ch.
基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否
具有给定名称且值与正则表达式匹配。
-Header=X-Request-Id, \d+
基于Host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
-Host=**.testhost.org
基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
-Method=GET
基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
-Path=/foo/{segment}基于Query请求参数的断言工厂
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具
有给定名称且值与正则表达式匹配。
-Query=baz, ba.
基于路由权重的断言工厂
WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发
routes:
-id: weight_route1 uri: host1 predicates:
-Path=/product/**
-Weight=group3, 1
-id: weight_route2 uri: host2 predicates:
-Path=/product/**
-Weight= group3, 9
写一个年龄大于18小于60的断言
server:
port: 7000
spring:
application:
name: api-gateway
# 配置gateway路由规则
cloud:
gateway:
discovery:
locator:
enabled: true # 运行从注册中心拉去服务清单。
# 你的请求地址必须: http://localhost:7000/shop-product/product/findById/1
routes: # 路由: 把客户的符合断言的请求 转发到指定的微服务。 在到达路由前可以适当修改一下请求地址
- id: product-router #路由的唯一标识: 如果没有赋值默认会通过UUID 生成一个唯一标识
uri: lb://shop-product # 路由转发的地址《目的地微服务的地址》
order: 0 # 路由的优先级 值越小优先级越高
predicates: # 断言: 条件 把符合断言的请求 才可以被转发
- Path=/product-serv/**
- Age=18,60
#- After=2021-12-31T23:59:59.789+08:00[Asia/Shanghai]
filters: # 过滤器: 在到达路由地址前可以对请求地址进行修改
- StripPrefix=1 # 截取请求地址的第一层
# - SetStatus=250 -
nacos:
discovery:
server-addr: http://localhost:8848
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@Component
//AfterRoutePredicateFactory
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
public static final String DATETIME_KEY = "datetime";
public AgeRoutePredicateFactory() {
super(AgeRoutePredicateFactory.Config.class);
}
// 读取配置文件中内容,把他赋值给Config类的属性
public List<String> shortcutFieldOrder() {
return Arrays.asList("minAge","maxAge");
}
// 断言的业务逻辑
public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
//获取请求地址的age参数值.
//localhost:8001/findbyid?age=18&name=zs&age=18
String age = serverWebExchange.getRequest().getQueryParams().getFirst("age");
if(StringUtils.isNotEmpty(age)){
if(Integer.parseInt(age)>=config.getMinAge()&&Integer.parseInt(age)<=config.getMaxAge()){
return true;
}
}
return false;
}
};
}
@Data
public static class Config {
private Integer minAge;
private Integer maxAge;
}
}
访问
http://localhost:7000/product-serv/product/productById/2?age=30
api网关开启,直接访问controller中RequestMapping路径
server:
port: 7000
spring:
application:
name: api-gateway
# 配置gateway路由规则
cloud:
gateway:
discovery:
locator:
enabled: true # 运行从注册中心拉去服务清单。
# 你的请求地址必须: http://localhost:7000/shop-product/product/findById/1
routes: # 路由: 把客户的符合断言的请求 转发到指定的微服务。 在到达路由前可以适当修改一下请求地址
- id: product-router #路由的唯一标识: 如果没有赋值默认会通过UUID 生成一个唯一标识
uri: lb://shop-product # 路由转发的地址《目的地微服务的地址》
order: 0 # 路由的优先级 值越小优先级越高
predicates: # 断言: 条件 把符合断言的请求 才可以被转发
# - Path=/product-serv/**
- Age=18,60
#- After=2021-12-31T23:59:59.789+08:00[Asia/Shanghai]
# filters: # 过滤器: 在到达路由地址前可以对请求地址进行修改
# - StripPrefix=1 # 截取请求地址的第一层
# - SetStatus=250 -
nacos:
discovery:
server-addr: http://localhost:8848
依赖导入
<!--引入spring-cloud-gateway和sentinel整合的jar 网关限流-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
修改yml配置文件
server:
port: 7000
spring:
application:
name: api-gateway
# 配置gateway路由规则
cloud:
gateway:
discovery:
locator:
enabled: true # 运行从注册中心拉去服务清单。
# 你的请求地址必须: http://localhost:7000/shop-product/product/findById/1
routes: # 路由: 把客户的符合断言的请求 转发到指定的微服务。 在到达路由前可以适当修改一下请求地址
- id: product-router #路由的唯一标识: 如果没有赋值默认会通过UUID 生成一个唯一标识
uri: lb://shop-product # 路由转发的地址《目的地微服务的地址》
order: 0 # 路由的优先级 值越小优先级越高
predicates: # 断言: 条件 把符合断言的请求 才可以被转发
- Path=/product-serv/**
# - Age=18,60
#- After=2021-12-31T23:59:59.789+08:00[Asia/Shanghai]
filters: # 过滤器: 在到达路由地址前可以对请求地址进行修改
- StripPrefix=1 # 截取请求地址的第一层
# - SetStatus=250 -
nacos:
discovery:
server-addr: http://localhost:8848
写网关配置类
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 配置限流过滤器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
/**
* 配置初始化的限流参数
*/
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("product_service") //资源名称
.setCount(1) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
GatewayRuleManager.loadRules(rules);
}
}
基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可。
@PostConstruct定义初始化的加载方法,用于指定资源的限流规则。这里资源的名称为 order-service ,统计时间是1秒内,限流阈值是1。表示每秒只能访问一个请求。
多次访问http://localhost:7000/product_service/product/findById/2,发现被限流