6、服务网关

微服务网关

什么是微服务网关

微服务网关产品

Zuul网关技术

配置方法

1、创建网关微服务,添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- 导入Eureka的客户端依赖包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、启动类添加eureka客户端、启动网关服务、springboot启动类注解

@SpringCloudApplication
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
}

3、配置application.yml文件,添加其他微服务路由

#配置端口
server:
  port: 10000
#配置项目名称
spring:
  application:
    name: zuul
#配置eureka注册路径
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka
#配置路由
zuul:
  routes:
    #路由ID/名称(可以随便写)
    micro-base-server:
      #访问路径(不能随便写,它需要对应微服务中Controller发布的接口)
      path:
        /auction/**
        /market/**
      #在eureka中注册的微服务名称
      serverId: micro-base-server
    micro-user-server:
      path:
        /feign/**
        /rent/**
      serverId: micro-user-server

4、进行访问

url格式:网关微服务ip:网关微服务端口/路由ID/对应微服务Controller路径

image-20210819102736505

5、路由配置简化

zuul:
  routes:
  	#eureka中微服务id: 访问路径
    micro-base-server: /auction/**,/market/**
    micro-user-server: /feign/**,/rent/**

过滤器

Zuul网关过滤器

过滤的结构图

img

PRE过滤器范例

//需要被spring容器管理
@Component
public class MyZuulFilter extends ZuulFilter {

    /**
     * 用来指定过滤器的类型
     * pre:路由之前
     * routing:路由之时
     * post: 路由之后
     * error:发送错误调用
     */
    public String filterType() {
        return "pre";
    }
    /**
     * 过滤器的顺序
     * @return 返回的Int越小,越先执行
     * 每个过滤器,默认都有一个中间值5
     */
    public int filterOrder() {
        return 0;
    }
    /**
     * 用来判断哪些路径,是需要做过滤处理的
     * @return true 需要做过滤(就是需要执行run())  false 不需要做过滤(就是不需要执行run())
     */
    public boolean shouldFilter() {
        return true;
    }
    /**
     * 具体的过滤器规则
     */
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        System.out.println("进入路由的过滤器 "+request.getServletPath());
        //true:成功响应,false:拒绝响应
        context.setSendZuulResponse(true);
        context.setResponseStatusCode(200);
        return null;
    }
}

Gateway(SpringCloud)

Gateway的特点

简单使用

1、新建modules,添加依赖

不要添加spring-boot-starter-web,因为gateway使用的是webFlux,和springboot的web场景启动器会有冲突

<dependencies>
    <dependency>
        <groupId>top.ygang</groupId>
        <artifactId>star-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
    </dependency>
</dependencies>

2、添加启动类

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}

3、编写配置文件

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    gateway:
      # 自定义配置路由规则
      routes:
        # 路由标识,必须唯一,默认是UUID
        - id: student-sys
          # 路由的目标地址
          uri: http://localhost:8080
          # 路由的优先级,数字越小代表路由的优先级越高
          order: 1
          # 断言
          predicates:
            # 路径断言,当请求路径匹配Path时,就会路由到uri
            - Path=/student-sys/**
          # 过滤器
          filters:
            # 转发之前,去掉1层路径
            - StripPrefix=1

4、进行访问

访问地址格式:http://gateway的ip:gateway的端口/请求路径Path/Controller路径

image-20210824120523201

Gateway和nacos结合

在简单使用的基础上,将Gateway添加到nacos上,通过微服务名称在nacos上查找微服务,一边实现访问微服务集群,实现负载均衡

1、添加依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2、启动类添加注解

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}

3、修改配置文件

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        # 将服务注册到nacos
        server-addr: http://localhost:8848
    gateway:
      discovery:
        locator:
          # 从nacos中获取服务信息,自动生成routes,开启会导致断言失效
          enabled: false
      # 自定义配置路由规则
      routes:
        # 路由标识,必须唯一,默认是UUID
        - id: student-sys
          # 路由的目标地址,lb(loadblance)表示使用负载均衡,其后是微服务的标识
          uri: lb://student-system
          # 路由的优先级,数字越小代表路由的优先级越高
          order: 1
          # 断言
          predicates:
            # 路径断言,当请求路径匹配Path时,就会路由到uri
            - Path=/student-sys/**
          # 过滤器
          filters:
            # 转发之前,去掉1层路径
            - StripPrefix=1

nacos配置的精简版

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        # 将服务注册到nacos
        server-addr: http://localhost:8848
    gateway:
      discovery:
        locator:
          # 让gateway从nacos中获取服务信息,自动生成routes,开启会导致断言失效
          enabled: true

Gateway核心架构

路由(Route)是Gateway中的组件之一,用来选择某一个具体的微服务。路由有以下的相关配置:

image-20200925113929580

大致的执行流程如下:

  1. Gateway Client向Gateway Server发送请求
  2. 请求首先会被HttpWebHandlerAdapter进行提取,进而组装成网关上下文
  3. 网关上下文被传递到DispatcherHandler,DispatcherHandler负责先找到能处理该请求的映射器处理器,这里它找到的映射器处理器是RoutePredicateHandlerMapping(没有分发给其他的HandlerMapping,是因为这个请求已经被封装进网关上下文了,该请求不是一般的请求,只能由特定的HandlerMapping处理,也就是RoutePredicateHandlerMapping)
  4. 路由断言处理映射器(RoutePredicateHandlerMapping)主要用于路由的查找,如何找呢?就是根据断言来找。
  5. 如果断言成功,由FilerWebHandler创建过滤器链并调用
  6. 请求会依次经过PreFilter、微服务、PostFilter的方法,最终返回响应

predicates断言

Predicate(断言)用于进行条件判断,只有断言都返回真,才会执行真正的路由。

内置路由断言工厂

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

自定义断言工厂

1、编写一个断言工厂的类

类名必须以RoutePredicateFactory为结束

继承AbstractRoutePredicateFactory

@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
    public AgeRoutePredicateFactory(){
        super(AgeRoutePredicateFactory.Config.class);
    }

    // 读取配置文件中的参数,并封装到Config内部类的对象中
    @Override
    public List<String> shortcutFieldOrder() {
        // 该集合中元素的顺序,必须与配置文件中参数的顺序一致
        return Arrays.asList("minAge", "maxAge");
    }

    @Override
    public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {

        return new Predicate<ServerWebExchange>(){

            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //通过路由获得传递的参数
                String age = serverWebExchange.getRequest().getQueryParams().getFirst("age");
                //判断是否有值
                if(!StringUtils.isEmpty(age)){
                    //转型
                    Integer a = Integer.parseInt(age);
                    return a>=config.getMinAge()&&a<=config.getMaxAge();
                }
                return false;
            }
        };
    }

    public static class Config {
        private Integer minAge;
        private Integer maxAge;
        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;
        }
    }
}

2、在配置文件中配置断言

image-20210824165406744

配置后,在启动gateway时,会对自定义断言工厂进行加载

image-20210824165628160

访问时,如果没有传age参数或age不符合配置中的范围,那么会404

image-20210824165822083

filters过滤器

过滤器的描述

过滤器的作用:在请求的处理过程中,对请求和响应进行加工。

在Gateway中,Filter的生命周期主要有两个阶段:"pre"和"post"

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

局部过滤器

内置局部过滤器
过滤器工厂 作用 参数
AddRequestHeader 为原始请求添加Header Header的名称和对应的值
AddRequestParameter 为原始请求添加请求参数 参数名和对应的值
AddResponseHeader 为原始响应添加Header Header的名称和对应的值
DedupeResponseHeader 剔除响应头中重复的值 需要去重的Header名称及去重策略
Hystrix 为路由引入Hystrix的断路器保护 HystrixCommand的名称
FallbackHeaders 为FallbackUrl的请求头中添加具体的异常信息 Header的名称
PrefixPath 为原始请求路径添加前缀 前缀路径
PreserveHostHeader 为请求添加一个preserveHostHeader=true的属性,
路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter 用于对请求限流,限流算法为令牌桶 keyResolver、rateLimiter、statusCode、
denyEmptyKey、emptyKeyStatus
RedirectTo 将原始请求重定向到指定的URL http状态码及重定向的url
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 修改原始响应体的内容 修改后的响应体内容
内置局部过滤器的使用
server:
  port: 9009
spring:
  application:
    name: star-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: false
      routes:
        - id: product_route
          uri: lb://star-product
          order: 1
          predicates:
            - Path=/product-serv/**
          filters:
            - StripPrefix=1
            - SetStatus=202				#修改 response的状态码

请求后,相应的状态码变为202

image-20210824170324246

自定义局部过滤器

1、编写一个过滤器类

类名必须以GatewayFilterFactory为结尾

@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
    public LogGatewayFilterFactory(){
        super(LogGatewayFilterFactory.Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                if(config.isConsolelog()){
                    System.out.println("控制台日志功能开启!!");
                }
                if(config.isCachelog()){
                    System.out.println("缓存日志功能开启!!");
                }
                return chain.filter(exchange);
            }
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("consoleLog", "cacheLog");
    }

    public static class Config {
        private boolean consolelog;
        private boolean cachelog;

        public boolean isConsolelog() {
            return consolelog;
        }

        public void setConsolelog(boolean consolelog) {
            this.consolelog = consolelog;
        }

        public boolean isCachelog() {
            return cachelog;
        }

        public void setCachelog(boolean cachelog) {
            this.cachelog = cachelog;
        }
    }
}

2、在配置文件中添加过滤器

image-20210824170820513

全局过滤器

全局过滤器不需要再配置文件中进行配置

内置全局过滤器
全局过滤器 作用
ForwardRoutingFilter 用于本地forward,也就是将请求在Gateway服务内进行转发,而不是转发到下游服务
LoadBalancerClientFilter 整合Ribbon实现负载均衡
NettyRoutinFilter 使用Netty的 HttpClient 转发http、https请求
NettyWriteResponseFilter 将代理响应写回网关的客户端侧
RouteToRequestUrlFilter 将从request里获取的原始url转换成Gateway进行请求转发时所使用的url
WebsocketRoutingFilter 使用Spring Web Socket将转发 Websocket 请求
GatewayMetricsFilter 整合监控相关,提供监控指标
自定义全局过滤器

1、编写过滤器类,实现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(!"test".equals(token)){
            System.out.println("未认证");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

2、进行访问,只有参数token为test时,才可以正常访问

image-20210824174345639

image-20210824174359375