Nepxion Discovery学习笔记8 Gateway网关

Spring Cloud Gateway

简介:

是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

它的目标是替代Netflflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控,限流。

优点:

性能强劲:是第一代网关Zuul1.6

功能强大:内置了很多实用的功能,例如转发、监控、限流等

设计优雅,容易扩展

缺点:

其实现依赖NettyWebFlux,不是传统的Servlet编程模型,学习成本高

不能将其部署在TomcatJettyServlet容器里,只能打成jar包执行

需要Spring Boot 2.0及以上的版本,才支持

 


笔记1:

基本概念:

路由(Route) gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个 信息:

id,路由标识符,区别于其他 Route

 

uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。

order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。

predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。

fifilter,过滤器用于修改请求和响应信息。


笔记2:

执行流程:

1. Gateway ClientGateway Server发送请求

2. 请求首先会被HttpWebHandlerAdapter进行提取组装网关上下文

3. 然后网关的上下文会传递到DispatcherHandler,它负责分发请求给 RoutePredicateHandlerMapping

4. RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用

5. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用

6. 请求会依次经过PreFilter--微服务--PostFilter的方法,最终返回响应


笔记3:

断言工厂:

1.内置路由断言工厂的使用:

主要介绍 uri 和  predicates (断言)中 -Path 和  filters 的用法.
依赖:
        <!--nacos客户端,引入依赖即可自动注册微服务-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery
            </artifactId>
        </dependency>
        <!--gateway网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>
配置文件:
server:
  port: 7000
spring:
  application:
    name: service-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway: # 配置网关
      discovery:
        locator:
          enabled: true # 让gateway可以发现nacos中的微服务
      routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
      - id: product_route # 当前路由的id标识, 要求唯一
#          uri: http://localhost:8081 # 请求要转发到的地址(写死不好,利用nacos服务名可以实现动态转发)
        uri: lb://service-product # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
        order: 1 # 路由的优先级,数字越小级别越高
        predicates: # 断言(就是路由转发要满足的条件)
        - Path=/product-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发
#此时http://localhost:7000/service-product/product/1或http://localhost:7000/product-serv/product/1皆可访问
#        - Before=2019-11-28T00:00:00.000+08:00 #当请求时间在2019-11-28之前
#        - Method=POST #当请求方式为POST
        filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
        - StripPrefix=1 # 转发之前去掉1层路径
#        predicates:
#        - Path=/product-serv/**
#        - Age=18,60 # 限制年龄只有在18到60岁之间的人能访问
#        filters: - Strip

测试接口:

 

总结规律可知:

输入(http://localhost:7000/product-serv/product/1)

满足以 /product-serv 为第一层地址:自动触发路由product_route.访问微服务service-product(http://localhost:7000/service-product);

再拼上输入的地址(http://localhost:7000/service-product+/product-serv/product/1);

并减去1层路径,最终就访问了(http://localhost:7000/service-product/product/1).

2.自定义断言工厂:

创建断言工厂类:

package com.stephen.shopgateway.impl;

import org.apache.commons.lang3.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
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {

	public AgeRoutePredicateFactory() {
		super(AgeRoutePredicateFactory.Config.class);
	}

	//用于从配置文件中获取参数值赋值到配置类中的属性上
	@Override
	public List<String> shortcutFieldOrder() {
		//这里的顺序要跟配置文件中的参数顺序一致
		return Arrays.asList("minAge", "maxAge");
	}

	//断言
	@Override
	public Predicate<ServerWebExchange> apply(Config config) {
		return serverWebExchange -> {
			//从serverWebExchange获取传入的参数
			String ageParam = serverWebExchange.getRequest().getQueryParams().getFirst("age");

			if( StringUtils.isNotBlank(ageParam)){
				Integer age = Integer.valueOf(ageParam);
				if(age>= config.getMinAge() && age<= config.getMaxAge()){
					// 判断请求参数中的age 》=minAge and age <=maxAge
					return true;
				}

			}
			return false;
		};
	}

	public static class Config {
		private int minAge;
		private int maxAge;

		public int getMinAge() {
			return minAge;
		}

		public void setMinAge(int minAge) {
			this.minAge = minAge;
		}

		public int getMaxAge() {
			return maxAge;
		}

		public void setMaxAge(int maxAge) {
			this.maxAge = maxAge;
		}
	}
}

配置完访问的时候发生报错:

这是因为Config必须是static修饰的,才能完成初始化:

测试接口:


笔记4:

过滤器:

1 作用: 过滤器就是在请求的传递过程中,对请求和响应做一些处理

2 生命周期:

     Pre: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

     Post: 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP、Header、收集统计信息和指标、将响应从微服务发送给客户端等。

3 分类: 局部过滤器(GatewayFilter,作用在某一个/组路由上) 和 全局过滤器(GlobalFilter,作用全部路由上)

内置过滤器:只要在配置文件添加参数和值即可:

自定义局部过滤器:

package com.stephen.shopgateway.impl;


import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

//自定义局部过滤器
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
	//构造函数
	public LogGatewayFilterFactory() { super(LogGatewayFilterFactory.Config.class); }

	//读取配置文件中的参数 赋值到 配置类中
	@Override
	public List<String> shortcutFieldOrder() { return Arrays.asList("consoleLog", "cacheLog"); }

	//过滤器逻辑
	@Override
	public GatewayFilter apply(LogGatewayFilterFactory.Config config) {
		return new GatewayFilter() {
			@Override
			public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
				if ( config.isConsoleLog() ) {
					System.out.println("consoleLog已经开启了....");
				}
				if ( config.isCacheLog() ) {
					System.out.println("cacheLog已经开启了....");
				}
				return chain.filter(exchange);
			}
		};
	}

	//配置类 接收配置参数
	@Data
	@NoArgsConstructor
	public static class Config {
		private boolean consoleLog;
		private boolean cacheLog;
	}
}

修改配置文件:

自定义全局过滤器:(常用语鉴权,加密或判断token)

package com.stephen.shopgateway.impl;

import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

//自定义全局过滤器需要实现GlobalFilter和Ordered接口
@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.isBlank(token) ) {
			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访问:

添加token:


笔记5:

网关限流:

网关是所有请求的公共入口,所以可以进行网关限流,而且限流的方式也很多,这里主要是用Sentinel组件来实现网关的限流。Sentinel支持对SpringCloud GatewayZuul等主流网关进 行限流。

限流的2种方式:

      1.route路由:即在Spring配置文件中配置的路由条目,资源名为对应的routeId.(粗限流)

      2.自定义API:用户可以利用Sentinel提供的API来自定义一些API分组.(细限流)

基于Sentinel Gateway限流技术是通过其提供的Filter来完成的,使用时只需注入对应的 SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可。

自定义API分组:

配置路由:

      #自定义api分组路由
      - id: product_api1
        uri: lb://service-product
        predicates:
        - Path=/product-service/product/api1/**
        filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
        - RewritePath=/product-service/product/api1/(?<segment>.*), /product/$\{segment} # 路径重写的过滤器
      - id: product_api2
        uri: lb://service-product
        predicates:
        - Path=/product-service/product/api2/demo1/**
        filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
        - RewritePath=/product-service/product/api2/demo1/(?<segment>.*), /product/$\{segment} # 路径重写的过滤器
package com.stephen.shopgateway.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

@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() {
		//根据路由id来限流
//		Set<GatewayFlowRule> rules = new HashSet<>();
//		rules.add(new GatewayFlowRule("product_route")//资源名称,对应路由id
//				.setCount(1)//限流阈值
//				.setIntervalSec(1)//统计时间窗口,单位是秒,默认是 1秒
//		);
//		GatewayRuleManager.loadRules(rules);//加载新的限流规则

		//创建API分组来细化限流
		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() {
//		BlockHandler异常处理
		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 开头的url路径请求
			//.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)推测是用于控制限流的,当出现限流后不再重启服务
			add(new ApiPathPredicateItem().setPattern("/product-service/product/api1/**"));
		}});
		ApiDefinition api2 = new ApiDefinition("product_api2").setPredicateItems(new HashSet<ApiPredicateItem>() {
			{
//				以 / product - serv / product / api2 / demo1 完全匹配的url路径请求
				//不添加 //.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)则服务在时间阈值后会重启服务
				add(new ApiPathPredicateItem().setPattern("/product-service/product/api2/demo1/**").setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
			}
		});
		definitions.add(api1);
		definitions.add(api2);
		GatewayApiDefinitionManager.loadApiDefinitions(definitions);
	}
}

快速测试接口发现:

http://localhost:7000/product-service/product/api1/1?age=20&token=12345 快速访问会限流,在统计时间阈值(1s)过后会恢复服务

http://localhost:7000/product-service/product/api2/demo1/1?age=20&token=12345 快速访问会限流,且之后不再恢复服务

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Nepxion Discovery【探索】使用指南,基于Spring Cloud Greenwich版、Finchley版和Hoxton版而 制作,对于Edgware版,使用者需要自行修改。使用指南主要涉及的功能包括: 基于Header传递的全链路灰度路由,网关为路由触发点。采用配置中心配置路由规则映射在网 关过滤器中植入Header信息而实现,路由规则传递到全链路服务中。路由方式主要包括版本和 区域的匹配路由、版本和区域的权重路由、基于机器IP地址和端口的路由 基于规则订阅的全链路灰度发布。采用配置中心配置灰度规则映射在全链路服务而实现,所有 服务都订阅某个共享配置。发布方式主要包括版本和区域的匹配发布、版本和区域的权重发布 全链路服务隔离。包括注册隔离、消费端隔离和提供端服务隔离,示例仅提供基于Group隔 离。除此之外,不在本文介绍内的,还包括: 注册隔离:黑/白名单的IP地址的注册隔离、最大注册数限制的注册隔离 消费端隔离:黑/白名单的IP地址的消费端隔离 全链路服务限流熔断降级权限,集成阿里巴巴Sentinel,有机整合灰度路由,扩展LimitApp的 机制,通过动态的Http Header方式实现组合式防护机制,包括基于服务名、基于灰度组、基于 灰度版本、基于灰度区域、基于机器地址和端口等防护机制,支持自定义任意的业务参数组合 实现该功能。支持原生的流控规则、降级规则、授权规则、系统规则、热点参数流控规则 全链路灰度调用链。包括Header方式和日志方式,Header方式框架内部集成,日志方式通过 MDC输出(需使用者自行集成) 同城双活多机房切换支持。它包含在“基于Header传递的全链路灰度路由”里 数据库灰度发布。内置简单的数据库灰度发布策略,它不在本文的介绍范围内 灰度路由和发布的自动化测试 license Apache 2.0 maven central v5.4.0 javadoc 5.4.0 build passing Docker容器化和Kubernetes平台的无缝支持部署

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值