cloud网关

Gateway--服务网关

网关简介

大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。

所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服 务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。

在业界比较流行的网关,有下面这些:

l Ngnix+lua

使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用

lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本

l Kong

基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。

问题:

只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。

l Zuul

Netflflix开源的网关,功能丰富,使用JAVA开发,易于二次开发

问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx

l Spring Cloud Gateway

Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。

注意:SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway来做网关

Gateway简介

Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。

优点:

l 性能强劲:是第一代网关Zuul的1.6倍

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

l 设计优雅,容易扩展

缺点:

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

l 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行

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

Gateway快速入门

 实现以下功能 网关的微服务模块所需的全部依赖

 
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        

        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

        
        
            cn.hutool
            hutool-all
            5.8.16
        


        
            org.springframework.cloud
            spring-cloud-starter-loadbalancer
        

要求: 通过浏览器访问api网关,然后通过网关将请求转发到商品微服务

在微服务中创建一个gateway的工程模块并加入依赖



    org.springframework.cloud
    spring-cloud-starter-gateway

修改配置文件(yml)

server:
  port: 8088
spring:
  application:
    name: api-gateway
  # 配置api
  cloud:
    gateway:
      routes:
        - id: product_route   # 路由的唯一标识,只要不重复都可以,如果不写默认会通过UUID产生,一般写成被路由的服务名称
          uri: http://localhost:8472/  # 被路由的地址
          order: 1                     #表示优先级  数字越小优先级越高
          predicates:                  #断言: 执行路由的判断条件
            - Path=/product_serv/**
          filters:                     # 过滤器: 可以在请求前或请求后作一些手脚
            - StripPrefix=1
properties文件的写法
#设置一下当前的应用的名字
spring.application.name=testgateway
#将当前的微服务注册到注册中心
spring.cloud.nacos.server-addr=localhost:8848
server.port=8088
#开始设置路由
#设置一下id
spring.cloud.gateway.routes[0].id=protest
#设置一下优先级
spring.cloud.gateway.routes[0].order=1
#断言serviceid为gate-pro的路径是以/pro为开头的
#友情提示 P是大写 而且P前面一定有空格 否则,会出错
spring.cloud.gateway.routes[0].predicates[0]= Path=/produ/**
#指向的微服务是什么
spring.cloud.gateway.routes[0].uri=http://localhost:8076
#内置过滤器    访问的真实的路径需要截取的是什么
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1

但是通常情况下我们配置的nacos实例并不止一个,对于多个实例,这样的配置显然不太方便,此时我们需要加依赖因为 cloud2021版本剔除了 ribbon

导入loadbalancer 负载均衡的依赖

  
        org.springframework.cloud
        spring-cloud-starter-loadbalancer
    

默认是轮询的

要改为随机需要加 config 文件

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class LoadBalancerConfig {
    @Bean
    ReactorLoadBalancer randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); //loadbalancer.client.name
        // 对应的需要进行负载均衡的名字是什么
        System.out.println("======"+name);
        // product
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

使用负载均衡的时候需要在启动类上设置信息
@SpringBootApplication
// 所有需要用到负载均衡的服务使用的策略全部都是随机
@LoadBalancerClients(defaultConfiguration= LoadBalancerConfig.class)
public class GateWayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GateWayApplication.class);
    }

}

最后修改 yml 文件 Path 即可

server:
  port: 8088
spring:
  application:
    name: api-gateway
  # 配置api
  cloud:
    gateway:
      routes:
        - id: product_route   # 路由的唯一标识,只要不重复都可以,如果不写默认会通过UUID产生,一般写成被路由的服务名称
          uri: lb://product  # 被路由的地址 lb代表负载均衡  loadbalanced 
          order: 1                     #表示优先级  数字越小优先级越高
          predicates:                  #断言: 执行路由的判断条件
            - Path=/product_serv/**
          filters:                     # 过滤器: 可以在请求前或请求后作一些手脚
            - StripPrefix=1
    nacos:
      discovery:
        server-addr: localhost:8848

用于上面的 yml 文件的配置我们会感觉特别的麻烦,每加了新的微服务模块我们就需要通过-的标识加新的对象,这样极其不方便,所以可以采用以下的简写版方式进行配置

简写版

yml
server:
  port: 8088
spring:
  application:
    name: api-gateway
  # 配置api
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true
 
properties
#使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true
#服务路由名小写
spring.cloud.gateway.discovery.locator.lower-case-service-id=true

spring.cloud.gateway.discovery.locator.enabled为true,表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。spring.cloud.gateway.discovery.locator.lowerCaseServiceId是将请求路径上的服务名配置为小写比如以/service-hi/*的请求路径被路由转发到服务名为service-hi的服务上。

访问的形式:

ip:

端口(网关的端口)/

服务的名字(nacos发现配置的名字spring.application.name)/

请求的路径

网关核心架构展开讲讲(断言,过滤...)

Gateway核心架构

基本概念

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

l id,路由标识符,区别于其他 Route。唯一 不写 默认的唯一的值

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

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

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

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

内置路由断言工厂

SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配体如下:

l 基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:

AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期

BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期

BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]

l 基于远程地址的断言工厂 RemoteAddrRoutePredicateFactory:

接收一个IP地址段,判断请求主机地址是否在地址段中

-RemoteAddr=192.168.1.24

l 基于Cookie的断言工厂

CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求

cookie是否具有给定名称且值与正则表达式匹配。

-Cookie=chocolate, ch.

l 基于Header的断言工厂

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否

具有给定名称且值与正则表达式匹配。

-Header=X-Request-Id, \d+

l 基于Host的断言工厂

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

-Host=**.testhost.org

l 基于Method请求方法的断言工厂

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

-Method=GET

l 基于Path请求路径的断言工厂

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

-Path=/foo/{segment}基于Query请求参数的断言工厂

QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具

有给定名称且值与正则表达式匹配。

-Query=baz, ba.

l 基于路由权重的断言工厂

WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发

自定义路由断言工厂

routes:
    predicates:
        - 路径
        - 自定义断言的类名

实例

@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory {
    public static final String MIN_AGE = "minAge";
    public static final String MAX_AGE = "maxAge";

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

    public List shortcutFieldOrder() {
        return Arrays.asList("minAge", "maxAge");
    }

    public Predicate apply(final Config config) {
       // Assert.isTrue(config.getDatetime1().isBefore(config.getDatetime2()), config.getDatetime1() + " must be before " + config.getDatetime2());
        return new GatewayPredicate() {
            public boolean test(ServerWebExchange serverWebExchange) {
                // 年龄是不是在最小值和最大值之间
                // age 作为一个参数传过来
                ServerHttpRequest request = serverWebExchange.getRequest();
                List age = request.getQueryParams().get("age");
                System.out.println("________________________"+age);
                String s = age.get(0);
                int i = Integer.parseInt(s);
                // 》=minAge&&<=maxAge



                return i>=config.getMinAge()&&i<= config.getMaxAge(); // true   false
            }

//            public String toString() {
//                return String.format("Between: %s and %s", config.getDatetime1(), config.getDatetime2());
//            }
        };
    }

    @Validated
    public static class Config {
        private @NotNull Integer minAge;
        private @NotNull Integer maxAge;

        public Config() {
        }

        public Integer getMinAge() {
            return minAge;
        }

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

        public Integer getMaxAge() {
            return maxAge;
        }

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

过滤器

三个知识点:

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

2 生命周期: Pre Post

3 分类: 局部过滤器(作用在某一个路由上) 全局过滤器(作用全部路由上)

在Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。

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

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

Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。

l GatewayFilter:应用到单个路由或者一个分组的路由上。

l GlobalFilter:应用到所有的路由上。

5.6.1 局部过滤器

局部过滤器是针对单个路由的过滤器。

5.6.1.1 内置局部过滤器

在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。

https://www.cnblogs.com/zhaoxiangjun/p/13042189.html

过滤器工厂

作用

参数

AddRequestHeader

为原始请求添加Header

Header的名称及值

AddRequestParameter

为原始请求添加请求参数

参数名称及值

AddResponseHeader

为原始响应添加Header

Header的名称及值

DedupeResponseHeader

剔除响应头中重复的值

需要去重的Header名称及去重策略

Hystrix

为路由引入Hystrix的断路器保护

HystrixCommand的名称

FallbackHeaders

为fallbackUri的请求头中添加具体的异常信息

Header的名称

PrefixPath

为原始请求路径添加前缀

前缀路径

PreserveHostHeader

为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host

RequestRateLimiter

用于对请求限流,限流算法为令牌桶

keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus

RedirectTo

将原始请求重定向到指定的URL

http状态码及重定向的url

RemoveHopByHopHeadersFilter

为原始请求删除IETF组织规定的一系列Header

默认就会启用,可以通过配置指定仅删除哪些Header

RemoveRequestHeader

为原始请求删除某个Header

Header名称

RemoveResponseHeader

为原始响应删除某个Header

Header名称

RewritePath

重写原始的请求路径

原始路径正则表达式以及重写后路径的正则表达式

RewriteResponseHeader

重写原始响应中的某个Header

Header名称,值的正则表达式,重写后的值

SaveSession

在转发请求之前,强制执行WebSession::save操作

secureHeaders

为原始响应添加一系列起安全作用的响应头

无,支持修改这些安全响应头的值

SetPath

修改原始的请求路径

修改后的路径

SetResponseHeader

修改原始响应中某个Header的值

Header名称,修改后的值

SetStatus

修改原始响应的状态码

HTTP 状态码,可以是数字,也可以是字符串

StripPrefix

用于截断原始请求的路径

使用数字表示要截断的路径的数量

Retry

针对不同的响应进行重试

retries、statuses、methods、series

RequestSize

设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large

请求包大小,单位为字节,默认值为5M

ModifyRequestBody

在转发请求之前修改原始请求体内容

修改后的请求体内容

ModifyResponseBody

修改原始响应体的内容

修改后的响应体内容

Default

为所有路由添加过滤器

过滤器工厂名称及值

自定义局部过滤器

实例

在配置文件中,添加一个Log的过滤器配置

filters:                     # 过滤器: 可以在请求前或请求后作一些手脚
  - StripPrefix=1
  - SetStatus=2000
  - Log=true,false
第2步:自定义一个过滤器工厂,实现方法
package com.yyl.config;
/**
 * @author yuyongli
 * @Date 2021/5/28
 */
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.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
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 {
    public LogGatewayFilterFactory() {
        super(LogGatewayFilterFactory.Config.class);
    }
    public List shortcutFieldOrder() {
        return Arrays.asList("consoleLog","cacheLog");
    }


    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                if(config.cacheLog){
                    System.out.println("开启缓存日志");
                }
                if(config.consoleLog){
                    System.out.println("开启控制台日志");
                }
                return chain.filter(exchange);
            }
        };
    }
    public static class Config {
        private Boolean consoleLog;
        private Boolean cacheLog;
        public Config() {
        }
        public Boolean getConsoleLog() {
            return consoleLog;
        }
        public void setConsoleLog(Boolean consoleLog) {
            this.consoleLog = consoleLog;
        }
        public Boolean getCacheLog() {
            return cacheLog;
        }
        public void setCacheLog(Boolean cacheLog) {
            this.cacheLog = cacheLog;
        }
    }
}
 

全局过滤器

全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。

内置全局过滤器

SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:

全局过滤器

作用

Combined Global Filter and GatewayFilter Ordering

对过滤器执行顺序进行排序

Forward Routing Filter

用于本地forward,也就是将请求在Gateway服务内进行转发,而不是转发到下游服务

Netty Routing Filter

使用Netty的 HttpClient 转发http、https请求

Netty Write Response Filter

将代理响应写回网关的客户端侧

RouteToRequestUrl Filter

将从request里获取的原始url转换成Gateway进行请求转发时所使用的url

Websocket Routing Filter

使用Spring Web Socket将转发 Websocket 请求

Gateway Metrics Filter

整合监控相关,提供监控指标

Marking An Exchange As Routed

防止重复的路由转发

自定义全局过滤器

内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的认证校验。

开发中的鉴权逻辑:

l 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)

l 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证

l 以后每次请求,客户端都携带认证的token

l 服务端对token进行解密,判断是否有效。

实例

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        /**
         * token
         * 有 放行
         * 没有 json数据  code 500  msg ”“ ,data:null
         */
        String token = exchange.getRequest().getHeaders().getFirst("token");
        // token不为空
        if(StringUtils.isNotBlank(token)){
            // 放行
          return  chain.filter(exchange);// 走下面的过滤器
        }else{
            // 返回json数据
            Map map =new HashMap<>();
            map.put("code",500);
            map.put("msg","错误!!!");
            map.put("data","sdfjdvhgs");
            // 转化为json数据
            String s = JSONUtil.toJsonStr(map);
            // 返回json数据
            ServerHttpResponse response = exchange.getResponse();
            //
//            Mono voidMono = response.setComplete();
            DataBuffer dataBuffer;
            try {
                byte[] bytes = s.getBytes("utf8");
                dataBuffer = response.bufferFactory().wrap(bytes);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
            return response.writeWith(Mono.just(dataBuffer));

        }

        //return null;
    }

    /**
     *
     * @return
     */

    @Override
    public int getOrder() {
        return 0;
    }
}

@Component注解概述

@Component 注解是 Spring Framework 中的核心注解之一,它标识一个类作为 Spring 容器中的组件,允许 Spring 在运行时自动发现、创建和管理这些组件。借助 @Component 注解,开发者可以实现松耦合的组件化开发,从而更好地管理应用程序的结构和功能。

你可能感兴趣的:(JAVA,微服务,java,gateway)