【Spring Cloud Alibaba】Gateway 服务网关

  • 1 架构图
  • 2 Predicate 断言
  • 3 路由
    • 3.1 静态路由
    • 3.2 动态路由
    • 3.3 Nacos 配置
  • 4 过滤器
    • 4.1 全局过滤器
    • 4.2 局部过滤器

1 架构图

2 Predicate 断言

Predicate 断言:这是一个Java 8 Function Predicate。输入类型是 Spring Framework ServerWebExchange。这允许开发人员可以匹配来自HTTP请求的任何内容,例如Header或参数。

Predicate 由Java8引入,位于java.util.function包中,是一个FunctionalInterface (函数式接口)

/*** Evaluates this predicate on the given argument.** @param t the input argument* @return {@code true} if the input argument matches the predicate,* otherwise {@code false}*/boolean test(T t);

通常用在stream的filter 中,表示是否满足过滤条件

3 路由

Route 路由:gateway的基本构建模块。它由ID、目标URI、断言集合和过滤器集合组成。如果聚合断言结果为真,则匹配到该路由。

3.1 静态路由

yaml配置

spring:cloud:# 静态路由gateway:discovery:locator:enabled: true # gateway可以通过开启以下配置来打开根据服务的serviceId来匹配路由,默认是大写routes:- id: 163  # 路由 ID,保持唯一uri: http://www.163.com/ # uri指目标服务地址,lb代表从注册中心获取服务predicates: # 路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)- Path=/163- id: good1  # 路由 ID,保持唯一uri: http://localhost:8800/ # uri指目标服务地址,lb代表从注册中心获取服务predicates: # 路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)- Path=/good/**,#  若无StripPrefix过滤器时,gateway 发送请求到后台服务producer的url就是http://producer/producerInEureka/hello#  若有StripPrefix过滤器时,gateway会根据StripPrefix=1所配的值(这里是1)去掉URL路径中的部分前缀(这里去掉一个前缀,即去掉producerInEureka)filters:- StripPrefix=1- id: good2  # 路由 ID,保持唯一uri: lb://e-commerce-demo-good # uri指目标服务地址,lb代表从注册中心获取服务predicates: # 路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)- Path=/g/**,filters:- StripPrefix=1# 它的作用和StripPrefix正相反,是在URL路径前面添加一部分的前缀 对应工程设置了server.servlet.context-path- PrefixPath=/demo-good

3.2 动态路由

GatewayConfig :配置类, 读取 Nacos 相关的配置项, 用于配置监听器

package cn.flowboot.e.commerce.config;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;/*** <h1>配置类, 读取 Nacos 相关的配置项, 用于配置监听器</h1>* */
@Configuration
public class GatewayConfig {/** 读取配置的超时时间 */public static final long DEFAULT_TIMEOUT = 30000;/** Nacos 服务器地址 */public static String NACOS_SERVER_ADDR;/** 命名空间 */public static String NACOS_NAMESPACE;/** data-id */public static String NACOS_ROUTE_DATA_ID;/** 分组 id */public static String NACOS_ROUTE_GROUP;@Value("${spring.cloud.nacos.discovery.server-addr}")public void setNacosServerAddr(String nacosServerAddr) {NACOS_SERVER_ADDR = nacosServerAddr;}@Value("${spring.cloud.nacos.discovery.namespace}")public void setNacosNamespace(String nacosNamespace) {NACOS_NAMESPACE = nacosNamespace;}@Value("${nacos.gateway.route.config.data-id}")public void setNacosRouteDataId(String nacosRouteDataId) {NACOS_ROUTE_DATA_ID = nacosRouteDataId;}@Value("${nacos.gateway.route.config.group}")public void setNacosRouteGroup(String nacosRouteGroup) {NACOS_ROUTE_GROUP = nacosRouteGroup;}
}

DynamicRouteServiceImpl :事件推送 Aware: 动态更新路由网关 Service

package cn.flowboot.e.commerce.config;import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;import java.util.List;/*** 事件推送 Aware: 动态更新路由网关 Service* */
@RequiredArgsConstructor
@Slf4j
@Service
@SuppressWarnings("all")
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {/** 写路由定义 */private final RouteDefinitionWriter routeDefinitionWriter;/** 获取路由定义 */private final RouteDefinitionLocator routeDefinitionLocator;/** 事件发布 */private ApplicationEventPublisher publisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {// 完成事件推送句柄的初始化this.publisher = applicationEventPublisher;}/*** <h2>增加路由定义</h2>* */public String addRouteDefinition(RouteDefinition definition) {log.info("gateway add route: [{}]", definition);// 保存路由配置并发布routeDefinitionWriter.save(Mono.just(definition)).subscribe();// 发布事件通知给 Gateway, 同步新增的路由定义this.publisher.publishEvent(new RefreshRoutesEvent(this));return "success";}/*** <h2>更新路由</h2>* */public String updateList(List<RouteDefinition> definitions) {log.info("gateway update route: [{}]", definitions);// 先拿到当前 Gateway 中存储的路由定义List<RouteDefinition> routeDefinitionsExits =routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {// 清除掉之前所有的 "旧的" 路由定义routeDefinitionsExits.forEach(rd -> {log.info("delete route definition: [{}]", rd);deleteById(rd.getId());});}// 把更新的路由定义同步到 gateway 中definitions.forEach(definition -> updateByRouteDefinition(definition));return "success";}/*** <h2>根据路由 id 删除路由配置</h2>* */private String deleteById(String id) {try {log.info("gateway delete route id: [{}]", id);this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();// 发布事件通知给 gateway 更新路由定义this.publisher.publishEvent(new RefreshRoutesEvent(this));return "delete success";} catch (Exception ex) {log.error("gateway delete route fail: [{}]", ex.getMessage(), ex);return "delete fail";}}/*** <h2>更新路由</h2>* 更新的实现策略比较简单: 删除 + 新增 = 更新* */private String updateByRouteDefinition(RouteDefinition definition) {try {log.info("gateway update route: [{}]", definition);this.routeDefinitionWriter.delete(Mono.just(definition.getId()));} catch (Exception ex) {return "update fail, not find route routeId: " + definition.getId();}try {this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();this.publisher.publishEvent(new RefreshRoutesEvent(this));return "success";} catch (Exception ex) {return "update route fail";}}
}

DynamicRouteServiceImplByNacos :通过 nacos 下发动态路由配置, 监听 Nacos 中路由配置变更

package cn.flowboot.e.commerce.config;import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;/*** <h1>通过 nacos 下发动态路由配置, 监听 Nacos 中路由配置变更</h1>* */
@Slf4j
@RequiredArgsConstructor
@Component
@DependsOn({"gatewayConfig"})
public class DynamicRouteServiceImplByNacos {/** Nacos 配置服务 */private ConfigService configService;private final DynamicRouteServiceImpl dynamicRouteService;/*** <h2>Bean 在容器中构造完成之后会执行 init 方法</h2>* */@PostConstructpublic void init() {log.info("gateway route init....");try {// 初始化 Nacos 配置客户端configService = initConfigService();if (null == configService) {log.error("init config service fail");return;}// 通过 Nacos Config 并指定路由配置路径去获取路由配置String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP,GatewayConfig.DEFAULT_TIMEOUT);log.info("get current gateway config:\n {}", configInfo);List<RouteDefinition> definitionList =JSON.parseArray(configInfo, RouteDefinition.class);if (CollectionUtils.isNotEmpty(definitionList)) {for (RouteDefinition definition : definitionList) {log.info("init gateway config: [{}]", definition.toString());dynamicRouteService.addRouteDefinition(definition);}}} catch (Exception ex) {log.error("gateway route init has some error: [{}]", ex.getMessage(), ex);}// 设置监听器dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);}/*** <h2>初始化 Nacos Config</h2>* */private ConfigService initConfigService() {try {Properties properties = new Properties();properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE);return configService = NacosFactory.createConfigService(properties);} catch (Exception ex) {log.error("init gateway nacos config error: [{}]", ex.getMessage(), ex);return null;}}/*** <h2>监听 Nacos 下发的动态路由配置</h2>* */private void dynamicRouteByNacosListener(String dataId, String group) {try {// 给 Nacos Config 客户端增加一个监听器configService.addListener(dataId, group, new Listener() {/*** <h2>自己提供线程池执行操作</h2>* */@Overridepublic Executor getExecutor() {return null;}/*** <h2>监听器收到配置更新</h2>* @param configInfo Nacos 中最新的配置定义* */@Overridepublic void receiveConfigInfo(String configInfo) {log.info("start to update config: [{}]", configInfo);List<RouteDefinition> definitionList =JSON.parseArray(configInfo, RouteDefinition.class);log.info("update route: [{}]", definitionList.toString());dynamicRouteService.updateList(definitionList);}});} catch (NacosException ex) {log.error("dynamic update gateway config error: [{}]", ex.getMessage(), ex);}}
}

3.3 Nacos 配置

4 过滤器

Filter 过滤器:这些是使用特定工厂构建的 Spring FrameworkGatewayFilter实例。所以可以在返回请求之前或之后修改请求和响应的内容。

  • SpringCloud Gateway基于过滤器实现,同zuul类似,有pre和post两种方式的filter,分别处理前置逻辑和后置逻辑
  • 客户端的请求先经过pre类型的filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过post类型的filter处理,最后返回响应到客户端
  • Filter一共有两大类∶全局过滤器和局部过滤器

4.1 全局过滤器

gateway 自带的全局过滤器 如:RouteToRequestUrlFilter,实现了GlobalFilter

实现全局运行日志

package cn.flowboot.e.commerce.filter;import com.alibaba.nacos.shaded.com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.concurrent.TimeUnit;/*** <h1>全局运行日志</h1>** @version 1.0* @author: Vincent Vic* @since: 2022/03/04*/
@Slf4j
@Component
public class GlobalElapsedLogFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//前置逻辑StopWatch sw = StopWatch.createStarted();String uri = exchange.getRequest().getURI().getPath();return chain.filter(exchange).then(Mono.fromRunnable(()->{log.info("[{}] elapsed: [{}ms]",uri,sw.getTime(TimeUnit.MILLISECONDS));}));}@Overridepublic int getOrder() {return HIGHEST_PRECEDENCE;}
}

4.2 局部过滤器

gateway 自带的局部过滤器 如:PrefixPathGatewayFilterFactory,StripPrefixGatewayFilterFactory

spring:cloud:# 静态路由gateway:- id: good2  uri: lb://e-commerce-demo-good predicates: - Path=/g/**,filters:# 去除前缀- StripPrefix=1# 添加前缀- PrefixPath=/demo-good

自定义局部过滤器
HeaderTokenGatewayFilter

package cn.flowboot.e.commerce.filter;import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** <h1>Http携带Token验证过滤器</h1>** @version 1.0* @author: Vincent Vic* @since: 2022/03/04*/
public class HeaderTokenGatewayFilter implements GatewayFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//从Http Header 中寻找 key为 token,value的键值对String value = exchange.getRequest().getHeaders().getFirst("token");//这里仅仅判断token是否存在if (StringUtils.isNotBlank(value)) {return chain.filter(exchange);}//不存在标记没有权限拦截exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}@Overridepublic int getOrder() {return 0;}
}

token 局部过滤器工厂
Gateway 过滤器命名规则: 功能名称 + GatewayFilterFactory

package cn.flowboot.e.commerce.filter.factory;import cn.flowboot.e.commerce.filter.HeaderTokenGatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;/*** <h1>token 局部过滤器 </h1>* <h2>Gateway 过滤器命名规则: 功能名称 + GatewayFilterFactory  </h2>* GatewayFilterFactory 是必须为类名称结尾* @version 1.0* @author: Vincent Vic* @since: 2022/03/04*/
@Component
public class HeaderTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Overridepublic GatewayFilter apply(Object config) {return new HeaderTokenGatewayFilter();}
}

使用

spring:cloud:# 静态路由gateway:- id: good2  uri: lb://e-commerce-demo-good predicates: - Path=/g/**,filters:# 使用自定义过滤器- HeaderToken

参考:Spring Cloud Gateway 2.1.0 中文官网文档

【Spring Cloud Alibaba】Gateway 服务网关相关推荐

  1. Spring Cloud入门-Gateway服务网关(Hoxton版本)

    文章目录 Spring Cloud入门系列汇总 摘要 Gateway 简介 相关概念 创建 api-gateway模块 在pom.xml中添加相关依赖 两种不同的配置路由方式 使用yml配置 使用Ja ...

  2. 从0到1手把手搭建spring cloud alibaba 微服务大型应用框架(十五) swagger篇 : gateway 集成swagger 与 knife4j实现在线api文档并嵌入到自己项目内

    背景 我们日常开发中基本都是协同开发的,当然极个别的项目整体前后端都是一个人开发的,当多人协作时,尤其是前后端人员协同开发时 必然会面临着前端需要了解后端api接口的情况,两个选择,提前设计好文档,然 ...

  3. 防止内卷和被潜规则,Spring Cloud Alibaba微服务架构实战派(上下册)|35岁程序员那些事

    目录 1 写书缘由 2 本书上册核心内容 2.1 Spring Cloud Alibaba基础实战 2.1.1 主要内容 2.1.2 MyBatis-Plus实现多租户架构的核心原理 2.2 分布式服 ...

  4. Spring Cloud Alibaba - Gateway 入门案例(二)(Gateway 整合 nacos /(非阿里组件))

    Spring Cloud Alibaba - Gateway 入门案例(二)(Gateway 整合 nacos)(非阿里组件) 回溯 Gateway 整合 nacos 方式一(复杂/灵活/常用) 方式 ...

  5. 从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(三) (mini-cloud) 搭建认证服务(认证/资源分离版) oauth2.0 (中)

    本文承接上文<从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(三) (mini-cloud) 搭建认证服务(认证/资源分离版) oauth2.0 (上)> ...

  6. Spring Cloud Alibaba | Sentinel: 服务限流高级篇

    Spring Cloud Alibaba | Sentinel: 服务限流高级篇 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如无特殊说明 ...

  7. Spring Cloud Alibaba 微服务开发实践

    作者:禅与计算机程序设计艺术 1.简介 Spring Cloud Alibaba 是阿里巴巴开源的基于 Spring Cloud 的微服务框架.该项目从最初孵化到现在已经历经十多年的发展,得到了广泛的 ...

  8. Spring Cloud Alibaba 微服务详细笔记

    文章目录 SpringCloud 一.微服务概述 1.1.什么是微服务? 1.2.为什么是微服务? 1.3.架构演变 1.4.微服务的解决方案 二.什么是SpringCloud 2.1.官方定义 2. ...

  9. Spring Cloud Alibaba微服务组件快速上手

    文章目录 Nacos 什么是Nacos Nacos的启动 将项目注册到Nacos 项目pom依赖 yaml配置 Nacos心跳机制 Dubbo 什么是RPC 什么是Dubbo Dubbo服务的注册与发 ...

  10. Spring Cloud Alibaba gateway ribbon 自定义负载均衡规则。发散灰度发布,金丝雀测试等

    上一篇介绍了,ribbon的组件.本篇要自己写一个灰度方案.其实就是一个很简单的思维扩散. 需求 前端header请求携带version字段.路由服务根据version去需要对应版本的服务集合,进行或 ...

最新文章

  1. 科学小世界,婚姻大殿堂
  2. mysql 插入当前时间_MySql优化之前期探索
  3. 数易云备开启虚拟机备份新时代
  4. 二分算法php,使用PHP实现二分查找算法代码分享
  5. 谷歌技术三宝之MapReduce(转)
  6. 如何制作自己的R包?
  7. 开源 画图_[软件使用05] 快速使用 Deeptools 对 ChIP-seq 数据画图!
  8. 最大子数组问题,分治策略基础,百度面试题
  9. Sqoop架构(四)
  10. asp.net中commandname应用
  11. 获取某一日期的毫秒数
  12. Grid++Report报表开发工具介绍
  13. Linux安装RPM、YUM
  14. 判断两个圆相切或相交
  15. sqldatasource oracle,asp.net – ORA-01036:非法变量名/号C#(SqlDataSource)Oracle 11g
  16. 回顾过去 展望未来(写给自己)
  17. 人人都在用的机器学习算法-决策树
  18. 【Unity】游戏打包
  19. linux 查看磁盘使用情况或清空回收站命令
  20. 小米AI实验室入选《麻省理工科技评论》中国“2021人工智能创新研究院”

热门文章

  1. Creo,SolidWorks,Freecad,QCad,DraftSight,QutoCad
  2. 软件测试(白盒测试与黑盒测试)
  3. OS第二章五大经典PV
  4. Kali2019安装ShuiZe|水泽-自动化信息收集工具安装
  5. python模拟用户登录爬取阳光采购平台数据
  6. Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day19】—— 集合框架3
  7. 学习Qt的资源论坛博客等
  8. uni-app 自定义相机拍照录像,可设置分辨率、支持横竖屏(ios、android)
  9. MRAM学习笔记——4.SOT-hall器件的测试
  10. 【单调栈】洛谷_2947 向右看齐Look Up