百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

微服务改造之整合gateway(微服务 gateway)

liuian 2025-03-19 14:13 5 浏览

1.概述

Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。

为啥改造使用网关

前提条件:

  • Consul:版本1.5.0。
  • Spring boot:版本2.1.5。
  • Spring cloud:版本Greenwich.SR1。
  • Redis:版本5.0.5。

三大核心概念

  • 1、Route(路由) 路由是由构建万股干的基本模块,他由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  • 2、Predicate(断言) 参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头和请求参数),如果请求与断言相匹配则进行路由
  • 3、Filter(过滤) 指的是Spring框架中的GatewayFilter的实例,使用过滤器,可以在请求被路由钱或者而之后对请求进行修改

gateway负载特点

  • 服务实例均衡分配:微服务负载均衡确保来自客户端的请求均匀分布到不同的服务实例上,以防止某些实例过载而其他实例处于空闲状态。
  • 多种负载均衡策略:微服务负载均衡可以采用不同的策略,如轮询、随机、最少连接等,以选择目标服务实例。每种策略有其用途,根据具体情况选择合适的策略。
  • 健康检查:负载均衡器定期检查每个服务实例的健康状态,以确定哪些实例可以接收请求。如果某个实例不健康或不可用,负载均衡器将停止将请求路由到该实例。
  • 自动扩展:微服务系统的负载均衡应该支持自动扩展。当系统负载增加时,可以自动添加新的服务实例以处理更多请求。这有助于应对流量波动和系统的横向扩展。
  • 故障转移:如果某个服务实例出现故障,负载均衡器应该自动将流量转移到其他健康的实例,以确保系统的可用性。
  • 会话粘附:在某些情况下,需要确保同一客户端的多个请求被路由到同一服务实例,以维护会话一致性。这称为会话粘附,一些负载均衡器支持此功能。

网关作用

  • 路由功能:网关可以根据目标地址的不同,选择最佳的路径将数据包从源网络路由到目标网络。它通过维护路由表来确定数据包的转发方向,并选择最优的路径。
  • 安全控制(统一认证授权):网关可以实施网络安全策略,对进出的数据包进行检查和过滤。它可以验证和授2权来自源网络的数据包,并阻止未经授权的访问。防火墙是一种常见的网关设备,用于过滤和保护网络免受恶意攻击和未经授权的访问。
  • 协议转换:不同网络使用不同的通信协议,网关可以进行协议转换,使得不同网络的设备可以互相通信。例如,例如将 HTTPS 协议转换成 HTTP 协议。
  • 网络地址转换(NAT):网关还可以执行网络地址转换,将内部网络使用的私有IP 地址转换为外部网络使用的公共 IP 地址,以实现多台计算机共享一个公共 IP 地址出去上网。

依赖及配置代码展示

@Bean
@Order(-1)  // 确保它比默认的错误处理器优先级高
public ErrorWebExceptionHandler myExceptionHandler() {
    return new JsonErrorWebExceptionHandler();
}
public class JsonErrorWebExceptionHandler implements ErrorWebExceptionHandler {
    @Override
    public Mono handle(ServerWebExchange exchange, Throwable ex) {
        exchange.getResponse().setStatusCode(HttpStatus.BAD_GATEWAY);
        // 设置响应体等
        return exchange.getResponse().writeWith(Mono.just(...));
    }
}
//跨域配置
@Configuration
public class CorsConfig {
    private static final String MAX_AGE = "18000L";
 
    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            // 使用SpringMvc自带的跨域检测工具类判断当前请求是否跨域
            if (!CorsUtils.isCorsRequest(request)) {
                return chain.filter(ctx);
            }
            HttpHeaders requestHeaders = request.getHeaders();       // 获取请求头
            ServerHttpResponse response = ctx.getResponse();          // 获取响应对象
            HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();  // 获取请求方式对象
            HttpHeaders headers = response.getHeaders();           // 获取响应头
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());   // 把请求头中的请求源(协议+ip+端口)添加到响应头中(相当于yml中的allowedOrigins)
            headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
            if (requestMethod != null) {
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());    // 允许被响应的方法(GET/POST等,相当于yml中的allowedMethods)
            }
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); // 允许在请求中携带cookie(相当于yml中的allowCredentials)
            headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*"); // 允许在请求中携带的头信息(相当于yml中的allowedHeaders)
            headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);     // 本次跨域检测的有效期(单位毫秒,相当于yml中的maxAge)
            if (request.getMethod() == HttpMethod.OPTIONS) {      // 直接给option请求反回结果
                response.setStatusCode(HttpStatus.OK);
                return Mono.empty();
            }
            return chain.filter(ctx);         // 不是option请求则放行
        };
    }
 
}
//创建启动类
package com.atguigu.ceshi.apigateway;
@SpringBootApplication
@EnableDiscoveryClient  # 加上这个
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}
// pom.xml

    
    
        org.springframework.cloud
        spring-cloud-starter-gateway
    
    
    
        com.alibaba.cloud
        spring-cloud-starter-alibaba-nacos-discovery
    
//application.yml
server:
  port: 8888
spring:
  profiles:
    active: dev # 环境设置
  application:
    name: cloud-gateway #服务名
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
          - id:  payment_route #payment_route  路由的id,没有固定规则但要求唯一,建议配合服务名
            #uri: http://localhost:8001 #匹配后提供服务的路由地址
            #cloud-payment-service为eureka注册的服务名 lb是url的协议,表示启用Gateway的负载均衡功能
            uri: lb://cloud-payment-service #微服务提供服务的路由地址()
            predicates:
              - Path=/payment/get/**  #断言,路径相匹配的进行路由
 
          - id: payment_route2 #payment_route2  路由的id,没有固定规则但要求唯一,建议配合服务名
            #uri: http://localhost:8001 #匹配后提供服务的路由地址
            uri: lb://cloud-payment-service #匹配后提供服务的路由地址
            predicates:
              - Path=/payment/lb/**  #断言,路径相匹配的进行路由
@Configuration
public class GateWayConfig {
    /**
     * 配置了一个Id为route-name的路由规则,方访问地址http://localhost:8888
     * /guonei是会自动转发到地址
     */
    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_route_xwb", r -> r.path("/guonei").uri("http://news.baidu.com/guonei"))
          .build();
        return routes.build();
    }
/**
 * Gateway的Filter过滤连
 * 总的全局过滤器,档在所有微服务前面,进行校验
 */
package com.atguigu.guli.infrastructure.apigateway.filter;

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("全局过滤器");
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        //校验用户必须登录
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            List tokenList = request.getHeaders().get("token");

            //没有token
            if(null == tokenList) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response);
            }

            //token校验失败
            Boolean isOK = JwtHelper.checkToken(tokenList.get(0));
            if(!isOK) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response);
            }
        }

        //放行
        return chain.filter(exchange);
    }

    //定义当前过滤器的优先级,值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }

    private Mono out(ServerHttpResponse response) {

        JsonObject message = new JsonObject();
        message.addProperty("success", ResultCodeEnum.LOGIN_AUTH.getSuccess());
        message.addProperty("code", ResultCodeEnum.LOGIN_AUTH.getCode());
        message.addProperty("data", "");
        message.addProperty("message", ResultCodeEnum.LOGIN_AUTH.getMessage());
        byte[] bytes = message.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        //输出http响应
        return response.writeWith(Mono.just(buffer));
    }
}
// 其他微服务
server:
  port: 8090
 
spring:
  application:
    name: testService01
  cloud:
    nacos:
      server-addr: 127.0.0.1:8082
      discovery:
        group: testGroup
        namespace: Dev
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
 
//其他微服务启动类
@SpringBootApplication
@EnableDiscoveryClient // nacos注册中心配置
public class TestServiceDemo01Application {
    public static void main(String[] args) {
        SpringApplication.run(TestServiceDemo01Application.class, args);
    }
}

gateway运行流程-草图

过滤流程

@Component
//@Order(-1) //过滤器的优先级,越小越高
public class AuthorizeFilter implements GlobalFilter , Ordered {
    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap queryParams = request.getQueryParams();
        //2.获取参数中的authorization参数
        String authorization = queryParams.getFirst("authorization");
        //3.判断参数值是否等于admin
        if("admin".equals(authorization)) {
            //4.是,放行
          return chain.filter(exchange);
        }
        //5.否,拦截
        //5.1设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

改造示例:

查看linux的nacos是否启动,如果没有启动先启动(nacos之前文档有)

一、新建gateway子工程

子工程

二、引入依赖

下面开始吐槽这个依赖引入的坑,很耗时间去解决,各种报错。

注意坑点(希望下次我遇到时,可以快速解决报错吧):

  • 1、gateway模块的pom不要去继承父工程的pom,父工程的pom依赖太多,极大可能会导致运行报错,新建gateway子工程后,pom父类就采用默认的spring-boot-starter-parent即可。
  • 2、在gateway自己的pom文件引入依赖,如下:

截图展示清晰

  • 3、在gateway自己的pom文件不要引用MVC的依赖包,不然会报错。
  • 4、网上根据报错查了很多解决方案,比如在pom文件里额外添加feign的依赖,或者更换cloud和springboot的版本,或者排除什么依赖,结果都会出现“解决当前问题,出现新的问题”。

如上解决方案,本人可行。(再次总结需要注意点:不要集成父工程的pom,不要引入MVC的依赖,只引入nacos-discovery和gateway依赖即可)。

三、改造gateway子模块

依赖问题解决后,就可以成功运行gateway模块了,前提是nacos已经开启了。然后开始改造gateway,使之与其他模块构成关联,具体如下:


  • 在gateway模块启动类上加上@EnableDiscoveryClient注解。
  • 修改yml配置文件,可以交给nacos管理,此处为了方便,直接写在本地yml配置里,如果不知道怎么使用nacos上的配置文件。接着说,修改yml文件后,结果如上图

此处配置将当前模块跑到nacos注册中心,然后开启gateway的注册中心路由功能

四、演示开启nacos注册中心路由功能效果

gateway可以通过routes和nacos的路由功能实现路由功能,现在展示一下nacos的路由功能效果。通过如下方法开启的路由功能:

gateway: discovery: locator: enabled: true #开启注册中心路由功能

五、演示自定义路由配置效果

上面第四点演示了默认通过注册中心实现网关路由的功能,下面第五点演示不通过第四点,而是自己通过routes配置自定义的路由策略,如下:

注意:若要使用自定义配置,则“不能开启注册中心的路由功能”,否则自定义的策略会失效,学习时给我坑惨了,百度也没找到问题,解决注释了开启注册中心的路由功能的配置,自定义的配置就生效了。

先写一个配置,再进行描述,配置如下:

yml

四个属性:

  • id:可以理解为是这组配置的一个id值,请保证他的唯一的,可以设置为和服务名一致。
  • uri:可以理解为是通过条件匹配之后需要路由到(跳转,重定向)到的新的服务地址。
  • predicates:可以理解为是编写条件,满足条件才进行uri。
  • filters:可以理解为是在路由前对请求的地址进行额外的其他操作,例如拼接或者裁减等。

六、启动主启动类

启动主类后就会看到 gateway被注册到nacos中了

然后就可以通过我们的gateway服务访问我们nacos中的服务了.示例到此就结束了.

常用方法

  • Forward Routing Filter:用于路由请求到后端服务。
  • LoadBalancerClient Filter:通过LoadBalancerClient执行负载均衡请求。
  • AddRequestHeader Filter:在请求中添加头信息。
  • AddRequestParameter Filter:在请求中添加查询参数。
  • RewritePath Filter:重写请求路径,用于修改请求的路径。
  • SetStatus Filter:设置HTTP响应状态码。
  • AddResponseHeader Filter:在响应中添加头信息。
  • Hystrix Filter:用于Hystrix断路器的支持。
  • WebSockets Filter:用于WebSocket代理。
  • ModifyResponseBody Filter:修改响应体内容。
  • PreserveHostHeader Filter:保留原始主机头信息。
  • RequestRateLimiter Filter:实现请求速率限制。

相关推荐

HR必备Excel函数:4个与日期相关的计算函数。

提到日期函数,很多人首先会想到“today”,它可以显示当天的日期,并且每次打开表格时都会自动更新。但是,对于前天、昨天、明天和后天的日期,就不能用yesterday或者tomorrow等这些英文了,...

这篇文章有点长,但可以让你十分钟玩转Excel的时间函数

日期与时间函数——TODAY、NOW、YEAR、MONTH、DAY!如何用WORKDAY函数查询距离某天的第20个工作日是哪一天?如何用NETWORKDAYS函数查询员工工作了多少个工作日?如何用WE...

Excel2020年日历套装,表格设计,农历显示,查阅套打轻松应用

Hello大家好,我是帮帮。今天跟大家分享一组Excel2020年日历套装,表格设计,自带农历控件,查阅套打轻松应用。有个好消息!为了方便大家更快的掌握技巧,寻找捷径。请大家点击文章末尾的“了解更多”...

巧用NETWORKDAYS函数计算两个日期之间工作日的天数

带有日期的单元格是我们日常使用EXCEL的时候经常见到的,有的时候我们需要求出两个日期之间间隔的天数,可以直接用结束日期减去开始日期即可,这是个非常简单的减法公式。不过这个单纯的减法公式会默认去掉开始...

Excel按工作日、休息日进行汇总

1、按周六日/其它时间汇总为了区分一周的周六日和其它时间,可以使用WEEKDAY函数,把WEEKDAY函数的第2个参数指定为2,如WEEKDAY(A3,2),则周一返回1,周二返回2,…,周六返回...

如何计算每月应出勤天数,如有法定假期和调休,如何计算

本文介绍如何计算每月的应出勤天数。第一部分介绍正常双休制下计算应出勤天数;第二部份介绍当月有法定假期和调休的情况下计算应出勤天数。一、计算正常双休制的应出勤天数如下图所示,要求计算各员工2021年3月...

《Excel一键生成工作日历:让会议排期更轻松!》

每当需要安排会议时,总要翻看日历确认工作日,再逐个标注会议时间,既耗时又容易出错。今天教大家用Excel快速生成工作日历表,让会议排期变得简单高效!一、快速生成日历框架创建基础日期:在A1单元格输入月...

如何计算指定日期区间内,有多少工作日和休息日?

大家好,今天咱们要解决的问题是如何计算给定的一段日期内,正常工作日有多少天,放假时间有多少天?比如咱们要计算2025年3月份工作日一共有多少天,又有多少天放假,如下图所示:通过肉眼我们可以数清楚,20...

如何如何在表格中自动突出显示双休日?

现在不少人喜欢用Excel来制作备忘录或安排工作事项。在表格中输入日期后,可以使用条件格式突出显示双休日,避免在休息日安排了工作。具体方法是这样的:第1步:选择要设置条件格式的日期单元格区域;在“开始...

excel函数技巧:networkdays.intl判断节假日

如图,想知道6月的每一天是否是节假日,公式如下:=NETWORKDAYS.INTL(A2,A2,1,$E$2:$E$28)这个函数既可以判断当前日期(一参=二参)是否是周末及工作日(三参、四参)还可得...

仅需3步,让考勤表根据实际休息日,自动地填充颜色

Hello,大家好,之前跟大家分享了我们如何让考勤表根据单休与双休自动的填充颜色,最近有粉丝问到:能不能让考勤表根据实际的休息日自动的填充颜色呢?可以是可以,只不过因为牵扯到假期调休,我们每年的休息日...

5步搞定动态考勤表!标记节假日、调休日?Excel自动变色!

今天教你用「动态考勤表」一招解决所有问题!只需输入月份,自动变色、自动更新节假日,从此告别加班,效率翻倍!动态考勤表的优势:自动变色:节假日、双休日一键标记,颜色分明。一表多用:修改月份即可...

一起用python做个炫酷音乐播放器,想听啥随便搜

前言前段时间写的Python自制一款炫酷音乐播放器,有不少小伙伴私信我,对播放器提了不少改进建议,让我完善播放器的功能。今天音乐播放器2.0版本完成了,大家一起来看看是如何用python自制一款炫酷的...

用Python做个“冰墩墩雪容融”桌面部件(好玩又有趣)

桌面太单调?今天就带大家,一起用Python的PyQt5开发一个有趣的自定义桌面动画挂件,看看实现的动画挂件效果!下面,我们开始介绍这个自定义桌面动画挂件的制作过程。一、核心功能设计实现将动态图gif...

Python串口调试助手源码分享

以下是一个基于Python和PyQt5实现的串口调试助手示例,包含核心功能实现代码:pythonimportsysimportserialfromPyQt5.QtCoreimportQTim...