微服务网关服务
认识 gateway 微服务网关组件
Spring Cloud GateWay 是 spring 官方推出的一款 基于 springframework5,Project Reactor和 spring boot2 之上开发的网关,其性能,高吞吐量,将代替zuul称为新一代的网关,用于给微服务提供 统一的api管理方式
与第一代的区别
和第一代网关zuul 相比 不同的事 gateway 是异步非阻塞的 (netty+webflux实现); zuul是同步阻塞请求的,性能上有这很大的差异
Gateway 组成部分
工作模型
请求发送到网关,由分发器将请求匹配到响应的 handlerMapping(这里的handlermapping不是MVC的那个,可以理解为匹配url的网关处理器)
请求和处理器之间有一个映射,路由到网关处理程序, web Handler他最用是把请求放入过滤器链路中,
执行特定的请求和过滤器链路,(我们自定义的)依次执行过滤器
最终到达代理微服务
思考
可以看到我们这个模型图 都是双向剪头的 那么找到了对应的 服务 返回的结果是如何回来的呢?
首先网关有相关的代理服务,然后把请求交给对应的代理服务处理,处理完后,将结果返回到Gateway客户端。
这里 filter 可以看到时 用虚线隔开的
pre filter : 请求必须要执行完pre filter并且执行完毕之后才会到对应的代理服务中处理,
post filter :对应的代理服务执行完处理完之后,才会执行 psot filter中的过滤器
模块搭建 三部曲
我们创建 gateway服务项目
<dependencies> <!-- spring cloud alibaba nacos discovery 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.5.0.RELEASE</version> </dependency> <dependency> <groupId>com.hyc.ecommerce</groupId> <artifactId>e-commerce-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
编写配置
server: port: 9001 servlet: context-path: /imooc spring: application: name: e-commerce-gateway cloud: nacos: discovery: enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可 server-addr: 127.0.0.1:8848 # Nacos 服务器地址 namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6 metadata: management: context-path: ${server.servlet.context-path}/actuator # 静态路由 # gateway: # routes: # - id: path_route # 路由的ID # uri: 127.0.0.1:8080/user/{id} # 匹配后路由地址 # predicates: # 断言, 路径相匹配的进行路由 # - Path=/user/{id} kafka: bootstrap-servers: 127.0.0.1:9092 producer: retries: 3 consumer: auto-offset-reset: latest zipkin: sender: type: kafka # 默认是 web base-url: http://localhost:9411/ main: allow-bean-definition-overriding: true # 因为将来会引入很多依赖, 难免有重名的 bean # 这个地方独立配置, 是网关的数据, 代码 GatewayConfig.java 中读取被监听 nacos: gateway: route: config: data-id: e-commerce-gateway-router group: e-commerce # 暴露端点 management: endpoints: web: exposure: include: '*' endpoint: health: show-details: always
谓词 Predicate 的原理与应用
想要 理解gateway 的Predicate ,首先我们要 理解 java 8 中的Predicate
我们先去看看 java8 的predicate吧
java8 predicate
由 java 8 引入位于 package java.util.function;包中 是一个 函数式接口
@FunctionalInterface // 是一个函数式接口
public interface Predicate<T>
来看这个 test 方法
需要输入一个的参数 返回 boolean 类型 通常用于 stream的filter 中 表示是否满足过滤条件
可能到这里,都是比较懵的。上手编写 java8 predicate 的效果
理解一下
Java 8 Predicate 常用的一些方法 执行测试看到效果就会 理解很多,这里提供 Test code
/** * @author : 冷环渊 * @date : 2021/12/6 * @context: java8 predicate 使用方法与思想 * @params : null * @return : * @return : null */ @SpringBootTest @Slf4j @RunWith(SpringRunner.class) public class PredicateTest { public static List<String> MICRO_SERVER = Arrays.asList( "nacos", "authority", "gateway", "ribbon", "feign", "Hystrix", "e-comerce" ); /* * test 方法主要是 用于参数符不符合规则 返回值事 boolean * */ @Test public void testPredicateTest() { Predicate<String> letterlengthLimit = s -> s.length() > 5; MICRO_SERVER.stream().filter(letterlengthLimit).forEach(System.out::println); } /* * and 方法 等同于 我们的逻辑 与 && 存在短路 特性 不符合就全部返回 false 需要所有条件都满足 * */ @Test public void testPredicateAnd() { Predicate<String> letterlengthLimit = s -> s.length() > 5; Predicate<String> letterStartWith = s -> s.startsWith("gate"); MICRO_SERVER.stream().filter( letterlengthLimit.and(letterStartWith) ).forEach(System.out::println); } /* * Or 等同于 逻辑 或 || 只要满足 其中一个条件 就可以通过过滤 * */ @Test public void testPredicateOr() { Predicate<String> letterlengthLimit = s -> s.length() > 5; Predicate<String> letterStartWith = s -> s.startsWith("gate"); MICRO_SERVER.stream().filter( letterlengthLimit.or(letterStartWith) ).forEach(System.out::println); } /* * 等同于 逻辑 非 ! * */ @Test public void testPredicateNegate() { Predicate<String> letterStartWith = s -> s.startsWith("gate"); MICRO_SERVER.stream().filter( letterStartWith.negate() ).forEach(System.out::println); } /* * is Equal 类似于 Eqeual() 区别在于 先判断对象是否为 null * 部位 null 在使用 equals 来比较 * */ @Test public void testPredicateIsEqual() { Predicate<String> equalGateway = s -> Predicate.isEqual("gateway").test(s); MICRO_SERVER.stream().filter( equalGateway ).forEach(System.out::println); } }
简单了解了一下,Predicate 这里我们去查看一下,gateway的 路径匹配路由工厂PathRoutePredicateFactory
从名字我们可以看出,这个工厂是负责 路径匹配的
看到 apply方法
他其实就是集成了 java8 的predicate
这里我们看到返回的GatewayPredicate,这里其实就是对请求的url
首先这个方法先获得了 path方法获取到当前请求的路径信息
之后和我们的配置进行一个匹配(正则表达式)返回匹配,否则就在去寻找
Tips:
这里我们首先要理解 Predicate 的几个方法
之后去分析一个 Gateway 的一个 Predicate 实现 查看一下 Gateway是如何实现的
@Override //这里config 其实就是我们的路由配置 public Predicate<ServerWebExchange> apply(Config config) { final ArrayList<PathPattern> pathPatterns = new ArrayList<>(); synchronized (this.pathPatternParser) { pathPatternParser.setMatchOptionalTrailingSeparator( config.isMatchOptionalTrailingSeparator()); config.getPatterns().forEach(pattern -> { PathPattern pathPattern = this.pathPatternParser.parse(pattern); pathPatterns.add(pathPattern); }); } return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { PathContainer path = parsePath( exchange.getRequest().getURI().getRawPath()); Optional<PathPattern> optionalPathPattern = pathPatterns.stream() .filter(pattern -> pattern.matches(path)).findFirst(); if (optionalPathPattern.isPresent()) { PathPattern pathPattern = optionalPathPattern.get(); traceMatch("Pattern", pathPattern.getPatternString(), path, true); PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path); putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables()); return true; } else { traceMatch("Pattern", config.getPatterns(), path, false); return false; } } @Override public String toString() { return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(), config.isMatchOptionalTrailingSeparator()); } }; }
alibaba nacos 实现动态路由配置
这里其实动态静态的配置,就是是否放到nacos上的区别
静态路由 配置写在配置文件中 (yml 或者 proprieties文件中),端点,是 spring.cloud,gateway,缺点是每次更改都系要网关重新部署
动态其实就是,从nacos上获取到配置,我们需要创建配置在nacos web端,之后相关服务启动的时候,我们需要配置 config 定义要去哪里获取到配置,我们在gateway解析配置,监听变化,如果有变化就刷新配置就好了