三 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>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
server:
port: 7000
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: product_route
uri: http://localhost:8081
order: 1
predicates:
- Path=/product-serv/**
filters:
- StripPrefix=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:
discovery:
locator:
enabled: true
routes:
- id: product_route
uri: lb://service-product
order: 1
predicates:
- Path=/product-serv/**
filters:
- StripPrefix=1
- id: order_route
uri: lb://service-order
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
gateway:
discovery:
locator:
enabled: true
routes:
- id: product_route
uri: lb://service-product
order: 1
predicates:
- Path=/product-serv/**
- Age=18,60
filters:
- StripPrefix=1
2 predicates包AgeRoutePredicateFactory.java
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) {
String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");
if (StringUtils.isNotEmpty(ageStr)) {
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;
private int maxAge;
}
}
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
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 自定义过滤器
@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();
}
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.cspgroupId>
<artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
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")
.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);
}
}
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);
}
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/product-serv/product/api1/**").
setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("product_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
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 就可以看到限流启作用了。