三 Gateway服务网关组件
网关的作用:解除客户端与微服务的耦合,方便微服务访问地址的维护;处理鉴权认证跨域问题。
网关也是一个微服务注册在nacos上,作为客户端和其他微服务的中转点,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
- 相关技术栈有:
- Nginx+lua :使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用;lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本
- Kong : 基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。
问题:只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。 - Zuul : Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发
问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如NginxSpring Cloud Gateway - Gateway : Spring公司为了替换Zuul而开发的网关服务;设计优雅,容易扩展
注意: SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway来做网关
Gateway配置详解
- id:自定义不重复
- uri:转发的目标地址
- order:可能有很多路由的path一样,这时候根据order值选择先去哪个路由。
- predicates:断言,这里可以配置很多规则,判断path值是否符合规则,如果符合则转发请求。
- filters:可以对请求做一些手脚(增加headers,修改请求uri)
1 测试:通过网关访问商品微服务
<dependencies>
<!--gateway网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
server:
port: 7000
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 让gateway从nacos中获取服务信息
routes: #路由数组〔路由就是指当请求满足什么样的条件的时候转发到哪个微服务上]
- id: product_route #当前路由发的标识,要求唯—
uri: http://localhost:8081 #lb://service-product #请求最终要被转发到的地址
order: 1 #路由的优先级,数字越小代表路由的优先级越高
predicates: #断言(条件判断,返回值是boolean转发请求要满足的条件)。集合
- Path=/product-serv/** #当请求路径满足Path指定的规则时,此路由信息才会正常转发(以product-serv开头)
# 上面路径是 uri+predicates=》http://localhost:7000/product-serv/product/1
filters: #过滤器(在请求传递过程中对请求做一些手脚)
- StripPrefix=1 #在请求转发之前去掉第一层路径=》http://localhost:8081/product/1
测试:原测试接口:http://localhost:8081/product/1;使用网关后访问http://localhost:7000/product-serv/product/1如果能访问通即整合成功
不写gateway.routes配置,默认 uri: lb://service-product==》http://localhost:7000/service-product/product/1
---------------------------------
2 请求最终要被转发到的地址,写死:http://localhost:8081=>从nacos获取lb://
1,注册nacos=> cloud.nacos.discovery.server-addr: localhost:8848
2,改写规则=> gateway.routes.uri: lb://service-product #从nacos获取lb://
server:
port: 7000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 将gateway注册到nacos 等同 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true # 让gateway从nacos中获取服务信息
routes: #路由数组〔路由就是指当请求满足什么样的条件的时候转发到哪个微服务上]
- id: product_route #当前路由发的标识,要求唯—
uri: lb://service-product #请求最终要被转发到的地址,写死:http://localhost:8081=>从nacos获取lb://
order: 1 #路由的优先级,数字越小代表路由的优先级越高
predicates: #断言(条件判断,返回值是boolean转发请求要满足的条件)。集合
- Path=/product-serv/** #当请求路径满足Path指定的规则时,此路由信息才会正常转发(以product-serv开头)
# 上面路径是 uri+predicates=》http://localhost:7000/product-serv/product/1(访问)
filters: #过滤器(在请求传递过程中对请求做一些手脚)
- StripPrefix=1 #在请求转发之前去掉第一层路径=》http://localhost:8081/product/1
- id: order_route
uri: lb://service-order #lb是负载均衡,后面是微服务在nacos上的标识
order: 1
predicates:
- Path=/order-serv/**
filters:
- StripPrefix=1
测试:原测试接口:http://localhost:8081/product/1;使用网关后访问http://localhost:7000/product-serv/product/1如果能访问通即整合成功
测试:原测试接口:http://localhost:8091/order/prod/1;使用网关后访问http://localhost:7000/service-order/order/prod/1如果能访问通即整合成功
端口统一了
----------------------------
3 predicates内置断言工厂
Predicate(断言,谓词)用于进行条件判断,只有断言都返回真,才会真正执行路由。
断言就是说:在什么条件下才能进行路由转发
- 基于Datetime类型的断言工厂,此类型的断言根据时间做判断,主要有三个:
- AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
- BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
- BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
- After=2021-08-01T23:59:59.789+08:00[Asia/Shanghai] #在2020-08-01时间后才可以访问
- Before=2021-08-25T23:59:59.789+08:00[Asia/Shanghai] #在2020-08-01时间后之前才可以访问
- Between=2020-08-03T23:59:59.789+08:00[Asia/Shanghai],2023-08-05T23: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
-------------------------------
1 测试设置断言
server:
port: 7000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #localhost:8848 # 将gateway注册到nacos 等同 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true # 让gateway从nacos中获取服务信息
routes: #路由数组〔路由就是指当请求满足什么样的条件的时候转发到哪个微服务上]
- id: product_route #当前路由发的标识,要求唯—
uri: lb://service-product #请求最终要被转发到的地址,写死:http://localhost:8081=>从nacos获取lb://
order: 1 #路由的优先级,数字越小代表路由的优先级越高
predicates: #断言(条件判断,返回值是boolean转发请求要满足的条件)。集合
- Path=/product-serv/** #当请求路径满足Path指定的规则时,此路由信息才会正常转发(以product-serv开头)
- Age=18,60 #年龄18,60
# 上面路径是 uri+predicates=》http://localhost:7000/product-serv/product/1(访问)
filters: #过滤器(在请求传递过程中对请求做一些手脚)
- StripPrefix=1 #在请求转发之前去掉第一层路径=》http://localhost:8081/product/1
2 predicates包AgeRoutePredicateFactory.java
//这是一个自定义的路由断言工厂类,要求有两个
//1 名字必须是 配置+RoutePredicateFactory
//2 必须继承AbstractRoutePredicateFactory<配置类>
//@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
//构造函数
public AgeRoutePredicateFactory() {
super(Config.class);
}
//读取配置文件的中参数值 给他赋值到配置类中的属性上
public List<String> shortcutFieldOrder() {
//这个位置的顺序必须跟配置文件中的值的顺序对应
return Arrays.asList("minAge", "maxAge");
}
//断言逻辑
public Predicate<ServerWebExchange> apply(Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
//1 接收前台传入的age参数
String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");
//2 先判断是否为空
if (StringUtils.isNotEmpty(ageStr)) {
//3 如果不为空,再进行路由逻辑判断
int age = Integer.parseInt(ageStr);
if (age < config.getMaxAge() && age > config.getMinAge()) {
return true;
} else {
return false;
}
}
return false;
}
};
}
//配置类,用于接收配置文件中的对应参数
@Data
@NoArgsConstructor
public static class Config {
private int minAge;//18
private int maxAge;//60
}
}
3 访问网址 http://localhost:7000/product-serv/product/2?age=25
--------------------------------
gateway过滤器
生命周期: Pre Post
PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。
GatewayFilter:应用到单个路由或者一个分组的路由上。
GlobalFilter:应用到所有的路由上。
局部过滤器
1 内置过滤器
1.1直接添加yaml
filters: #过滤器(在请求传递过程中对请求做一些手脚)
- SetStatus=250
1.2访问 http://localhost:7000/product-serv/product/2 =》F12 =》 status=250
-------------------------
2 自定义过滤器
2.1 添加配置
filters: #过滤器(在请求传递过程中对请求做一些手脚)
- Log=true ,false #控制日志是否开启
2.2 自定义配置类
//测试 自定义局部过滤器 @Component 没开等于没用
@Component
public class LogGatewayFilterFactory
extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
//构造函数
public LogGatewayFilterFactory() {
super(Config.class);
}
//读取配置文件中的参数 赋值到 配置类中
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog", "cacheLog");
}
//过滤器逻辑
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (config.isCacheLog()) {
System.out.println("cacheLog已经开启了....");
}
if (config.isConsoleLog()) {
System.out.println("consoleLog已经开启了....");
}
return chain.filter(exchange);
}
};
}
//配置类 接收配置参数
@Data
@NoArgsConstructor
public static class Config {
private boolean consoleLog;
private boolean cacheLog;
}
}
2.3访问 http://localhost:7000/product-serv/product/2 =》F12 =》 status=250
-------------------------------------
全局过滤器
1 内置过滤器
uri: lb://service-product
lb就是负载均衡过滤器
2 自定义过滤器
//自定义全局过滤器需要实现GlobalFilter和Ordered接口 ,作用是统一鉴权
@Component //@Component 没开等于没用
public class AuthGlobalFilter implements GlobalFilter, Ordered {
//完成判断逻辑
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (!StringUtils.equals(token, "admin")) {
System.out.println("鉴权失败");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//调用chain.filter继续向下游执行
return chain.filter(exchange);
}
//顺序,数值越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}
只有访问token才有结果 http://localhost:7000/product-serv/product/1?token=admin
------------------------------------------
网关限流
Sentinel提供了SpringCloud Gateway的适配模块,提供两种限流维度
- route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeld
- 自定义API维度:用户可以利用Sentinel提供的APl来自定义一些API分组
依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
1 route维度
1.1 配置类GatewayConfiguration
主要编写initGatewayRules方法设置限流规则,编写initBlockHandlers方法自定义异常界面。
@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;
}
// 初始化一个限流的过滤器
@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_route") //资源名称,对应路由id
.setCount(1) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
GatewayRuleManager.loadRules(rules);
}
// 配置限流的异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
// 自定义限流异常页面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
1.2在一秒钟内多次访问http://localhost:7000/product-serv/product/1就可以看到限流启作用了。
------------------------
2 自定义API维度
2.1 配置类GatewayConfiguration
主要编写initGatewayRules方法设置限流规则,编写initBlockHandlers方法自定义异常界面。
@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;
}
// 初始化一个限流的过滤器
@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_api1").setCount(1).setIntervalSec(1));
rules.add(new GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
// 配置限流的异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
// 自定义限流异常页面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
//自定义API分组
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
// 以/product-serv/product/api1 开头的请求
add(new ApiPathPredicateItem().setPattern("/product-serv/product/api1/**").
setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("product_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
// 以/product-serv/product/api2/demo1 完成的url路径匹配
add(new ApiPathPredicateItem().setPattern("/product-serv/product/api2/demo1"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
2.2 在shop-product中多加几个test方法
@RestController
@Slf4j
public class ProductController {
@RequestMapping("/product/api1/demo1")
public String demo1() {
return "demo";
}
@RequestMapping("/product/api1/demo2")
public String demo2() {
return "demo";
}
@RequestMapping("/product/api2/demo1")
public String demo3() {
return "demo";
}
@RequestMapping("/product/api2/demo2")
public String demo4() {
return "demo";
}
}
2.3 在一秒钟内多次访问http://localhost:7000/product-serv/product/api2/demo2 不会限流。
在一秒钟内多次访问http://localhost:7000/product-serv/product/api1/demo1 就可以看到限流启作用了。
在一秒钟内多次访问http://localhost:7000/product-serv/product/api1/demo2 就可以看到限流启作用了。
在一秒钟内多次访问http://localhost:7000/product-serv/product/api2/demo1 就可以看到限流启作用了。
版权声明:本文为qq_44897733原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。