微服务网关概述

1. 微服务网关

不同的微服务一般会有不同的网络地址,客户端在访问这些微服务时必须记住几十甚至几百个地址,这对于客户端方来说太复杂也难以维护。如下图:
在这里插入图片描述
如果让客户端直接与各个微服务通讯,可能会有很多问题:
1.客户端会请求多个不同的服务,需要维护不同的请求地址,增加开发难度
2.在某些场景下存在跨域请求的问题
3.加大身份认证的难度,每个微服务需要独立认证
因此,我们需要一个微服务网关,介于客户端与服务器之间的中间层,所有的外部请求都会先经过微服务网关。客户端只需要与网关交互,只知道一个网关地址即可,这样简化了开发还有以下优点:
1、易于监控
2、易于认证
3、减少了客户端与各个微服务之间的交互次数
在这里插入图片描述

1.1 服务网关的概念

1.1.1 什么是微服务网关

API网关是一个服务器,是系统对外的唯一入口。API网关封装了系统内部架构,为每个客户端提供
一个定制的API。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能.通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。

1.1.2 作用和应用场景

网关具有的职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。当然,最主
要的职责还是与“外界联系”。

1.2 常见的API网关实现方式

  • Kong
    基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。
    问题:只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方
    式。
  • Zuul
    Netflix开源,功能丰富,使用JAVA开发,易于二次开发;需要运行在web容器中,如Tomcat。
    问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如
    Nginx;
  • Traefik
    Go语言开发;轻量易用;提供大多数的功能:服务路由,负载均衡等等;提供WebUI
    问题:二进制文件部署,二次开发难度大;UI更多的是监控,缺乏配置、管理能力;
  • Spring Cloud Gateway
    SpringCloud提供的网关服务
  • Nginx+lua实现
    使用Nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用
    问题:自注册的问题和网关本身的扩展性

1.3 基于Nginx的网关实现

https://blog.csdn.net/qq_39759664/article/details/116567269

2. 微服务网关Zuul

2.1 Zuul简介

ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的
核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 动态路由:动态将请求路由到不同后端集群
  • 压力测试:逐渐增加指向集群的流量,以了解性能
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
  • 静态响应处理:边缘位置进行响应,避免转发到内部集群
  • 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。Spring Cloud对Zuul进行
    了整合和增强。

Spring Cloud对Zuul进行了整合和增强

2.2 搭建Zuul网关服务器

(1)创建工程导入依赖
在IDEA中创建ZUUL网关工程 shop_zuul_server ,并添加响应依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

(2)编写启动类
创建启动类 ZuulServerApplication

@SpringBootApplication
//开启zuul网关功能
@EnableZuulProxy
//eureka的服务发现
@EnableDiscoveryClient
public class ZuulServerApplication {

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

@EnableZuulProxy: 通过 @EnableZuulProxy 注解开启Zuul网管功能
(3)编写配置

server:
  port: 8080 #端口
spring:
  application:
    name: api-zuul-server #服务名称

2.3 Zuul中的路由转发

最直观的理解:“路由”是指根据请求URL,将请求分配到对应的处理程序。在微服务体系中,Zuul负责
接收所有的请求。根据不同的URL匹配规则,将不同的请求转发到不同的微服务处理。


##路由配置
zuul:
  routes:
    #已商品微服务
    product-service: #路由id,随便写
      path: /product-service/** #映射路径  #localhost:8080/product-service/sxxssds
      url: http://127.0.0.1:9001 #映射路径对应的实际微服务url地址

只需要在application.yml文件中配置路由规则即可:
product-service:配置路由id,可以随意取名
url:映射路径对应的实际url地址
path:配置映射路径,这里将所有请求前缀为/product-service/的请求,转发到http://127.0.0.1: 9002处理
配置好Zuul路由之后启动服务,在浏览器中输入 http://localhost:8080/product- service/product/1 ,即可访问到订单微服务。
在这里插入图片描述

2.3.1 面向服务的路由

微服务一般是由几十、上百个服务组成,对于一个URL请求,最终会确认一个服务实例进行处理。如果对每个服务实例手动指定一个唯一访问地址,然后根据URL去手动实现请求匹配,这样做显然就不合
理。

Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的
好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的
路由配置。

(1)添加Eureka客户端依赖

<dependency>
 <groupId>org.springframework.cloud</groupId> 
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(2)开启Eureka客户端发现功能

@SpringBootApplication
//开启zuul网关功能
@EnableZuulProxy
//eureka的服务发现
@EnableDiscoveryClient
public class ZuulServerApplication {

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

(3)添加Eureka配置,获取服务信息

#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册

(4)修改映射配置,通过服务名称获取
因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而
是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。

#配置路由规则 
zuul: 
	routes: 
		product-service: # 这里是路由id,随意写 
			path: /product-service/** # 这里是映射路径 
			serviceId: shop-service-product #配置转发的微服务名称

serviceId: 指定需要转发的微服务实例名称
依次启动Eureka,商品微服务,API网关,在浏览器上通过访问 http://localhost:8080/product- service/product/1 查看最终效果

2.3.2 简化的路由配置

##路由配置
zuul:
  routes:
    #已商品微服务
    #product-service: #路由id,随便写
      #path: /product-service/** #映射路径  #localhost:8080/product-service/sxxssds
      #url: http://127.0.0.1:9001 #映射路径对应的实际微服务url地址
     # serviceId: service-product #配置转发的微服务的服务名称
     #如果路由id 和 对应的微服务的serviceId一致的话
     service-product: /product-service/**
     #zuul中的默认路由配置
     #如果当前的微服务名称 service-product , 默认的请求映射路径 /service-product/**
     #  /service-order/

在刚才的配置中,我们的规则是这样的:
zuul.routes.<route>.path=/xxx/** : 来指定映射路径。 是自定义的路由名
zuul.routes.<route>.serviceId=/product-service :来指定服务名。
而大多数情况下,我们的 <route>路由名称往往和服务名会写成一样的。因此Zuul就提供了一种简化的
配置语法: zuul.routes.<serviceId>=<path>
上面的配置可以简化为一条

zuul: 
	routes: 
		shop-service-product: /product-service/**

2.3.3 默认的路由规则

在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁
琐的。因此Zuul就指定了默认的路由规则:
默认情况下,一切服务的映射路径就是服务名本身。
例如服务名为: shop-service-product ,则默认的映射路径就是: /shop-service- product/**

2.4 过滤器

2.4.1 ZuulFilter简介

Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。

  1. PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请
    求的微服务、记录调试信息等。
  2. ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用
    Apache HttpClient或Netfilx Ribbon请求微服务。
  3. POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP
    Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  4. ERROR:在其他阶段发生错误时执行该过滤器。
    在这里插入图片描述

在这里插入图片描述

  • 正常流程:
    请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的
    服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程:
    整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕
    后,会将请求交给POST过滤器,最后返回给用户。
    如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求
    不会再到达POST过滤器了。
  • 不同过滤器的场景:
    请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
    异常处理:一般会在error类型和post类型过滤器中结合来处理。
    服务调用时长统计:pre和post结合使用。

2.4.2 自定义过滤器

自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * 自定义的zuul过滤器
 *  继承抽象父类
 */
@Component
public class LoginFilter extends ZuulFilter {

	/**
	 * 定义过滤器类型
	 *  pre
	 *  routing
	 *  post
	 *  error
	 */
	public String filterType() {
		return "pre";
	}


	/**
	 *  指定过滤器的执行顺序
	 *      返回值越小,执行顺序越高
	 */
	public int filterOrder() {
		return 1;
	}


	/**
	 * 当前过滤器是否生效
	 *  true : 使用此过滤器
	 *  flase : 不使用此过滤器
	 */
	public boolean shouldFilter() {
		return true;
	}


	/**
	 * 指定过滤器中的业务逻辑
	 *  身份认证:
	 *      1.所有的请求需要携带一个参数 : access-token
	 *      2.获取request请求
	 *      3.通过request获取参数access-token
	 *      4.判断token是否为空
	 *      4.1 token==null : 身份验证失败
	 *      4.2 token!=null : 执行后续操作
	 *  在zuul网关中,通过RequestContext的上下问对象,可以获取对象request对象
	 */
	public Object run() throws ZuulException {
		//System.out.println("执行了过滤器");
		//1.获取zuul提供的上下文对象RequestContext
		RequestContext ctx = RequestContext.getCurrentContext();
		//2.从RequestContext中获取request
		HttpServletRequest request = ctx.getRequest();
		//3.获取请求参数access-token
		String token = request.getParameter("access-token");
		//4.判断
		if (token ==null) {
			//4.1 如果token==null ,拦截请求,返回认证失败
			ctx.setSendZuulResponse(false); // 拦截请求
			ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
		}
		//4.2 如果token!=null ,继续后续操作
		return null;
	}
}

RequestContext:用于在过滤器之间传递消息。它的数据保存在每个请求的ThreadLocal中。它
用于存储请求路由到哪里、错误、HttpServletRequest、HttpServletResponse都存储在
RequestContext中。RequestContext扩展了ConcurrentHashMap,所以,任何数据都可以存储
在上下文中

带token访问
在这里插入图片描述
不带token访问
在这里插入图片描述
zuul的原理:
在这里插入图片描述
每一个请求必须由服务器分配一个线程,直至该请求结束。可能造成线程爆满
另外不支持websocket

3. 微服务网关GateWay

Zuul 1.x 是一个基于阻塞 IO 的 API Gateway 以及 Servlet;直到 2018 年 5 月,Zuul 2.x(基于
Netty,也是非阻塞的,支持长连接)才发布,但 Spring Cloud 暂时还没有整合计划。Spring Cloud
Gateway 比 Zuul 1.x 系列的性能和功能整体要好。

3.1 Gateway简介

3.1.1 简介

Spring Cloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflix ZUUL,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。它是基于Nttey的响应式开发模式。

  1. 路由(route) 路由是网关最基础的部分,路由信息由一个ID、一个目的URL、一组断言工厂和一
    组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。
  2. 断言(predicates) Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是
    Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定
    义匹配来自Http Request中的任何信息,比如请求头和参数等。
  3. 过滤器(filter) 一个标准的Spring webFilter,Spring Cloud Gateway中的Filter分为两种类型,
    分别是Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理。

3.2 入门案例

3.2.1 入门案例

(1) 创建工程导入依赖
在项目中添加新的模块 shop_gateway_server ,并导入依赖

      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

注意SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容。引入的限流组件是
hystrix。redis底层不再使用jedis,而是lettuce。

出现:
在这里插入图片描述

需要在应用springmvc的模块中加入

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

(2) 配置启动类


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

(3) 编写配置文件

server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
    #配置SpringCloudGateway的路由
  cloud:
    gateway:
      routes:
        #配置路由 路由id 路由到微服务的url  断言(判断条件)
      - id: product-service
        uri: http://127.0.0.1:9001
        predicates:
        - Path=/product/**  #product  是controller的名字  因为是在9001 后面

  • id:我们自定义的路由 ID,保持唯一
  • uri:目标服务地址
  • predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
  • filters:过滤规则,暂时没用。

上面这段配置的意思是,配置了一个 id 为 product-service的路由规则,当访问网关请求地址以
product 开头时,会自动转发到地址: http://127.0.0.1:9002/ 。配置完成启动项目即可在浏览器
访问进行测试,当我们访问地址 http://localhost:8080/product/1 时会展示页面展示如下:
在这里插入图片描述

3.2.2 路由规则

Spring Cloud Gateway 的功能很强大,前面我们只是使用了 predicates 进行了简单的条件匹配,其实
Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。在 Spring Cloud Gateway 中 Spring 利用
Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件
匹配到对应的路由

在这里插入图片描述
示例:
在这里插入图片描述

3.2.3 动态路由

和zuul网关类似,在SpringCloud GateWay中也支持动态路由:即自动的从注册中心中获取服务列表并
访问。
(1)添加注册中心依赖
在工程的pom文件中添加注册中心的客户端依赖(这里以Eureka为例)

   <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

(2)配置动态路由
修改 application.yml 配置文件,添加eureka注册中心的相关配置,并修改访问映射的URL为服务名

server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
    #配置SpringCloudGateway的路由
  cloud:
    gateway:
      routes:
        #配置路由 路由id 路由到微服务的url  断言(判断条件)
      - id: product-service
        #uri: http://127.0.0.1:9001
        uri: lb://service-product  #lb://  根据微服务名称从注册中心拉取服务请求路径
        predicates:
        - Path=/product/**  #product  是controller的名字  因为是在9001 后面
#eureka注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册

lb:// 根据微服务名称从注册中心拉取服务请求路径

在这里插入图片描述

3.2.4 重写转发路径

在SpringCloud Gateway中,路由转发是直接将匹配的路由path直接拼接到映射路径(URI)之后,那
么在微服务开发中往往没有那么便利。这里就可以通过RewritePath机制来进行路径重写。
(1) 案例改造
修改 application.yml ,将匹配路径改为 /product-service/**

```spring:
  application:
    name: api-gateway-server #服务名称
    #配置SpringCloudGateway的路由
  cloud:
    gateway:
      routes:
        #配置路由 路由id 路由到微服务的url  断言(判断条件)
      - id: product-service  #保持唯一
        #uri: http://127.0.0.1:9001 #目标微服务请求地址
        uri: lb://service-product  #lb://  根据微服务名称从注册中心拉取服务请求路径

重新启动网关,我们在浏览器访问http://127.0.0.1:8080/product-service/product/1,会抛出404。这
是由于路由转发规则默认转发到商品微服务( http://127.0.0.1:9002/product- service/product/1 )路径上,而商品微服务又没有 product-service 对应的映射配置。
(2) 添加RewritePath重写转发路径
修改 application.yml ,添加重写规则。

server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
    #配置SpringCloudGateway的路由
  cloud:
    gateway:
      routes:
        #配置路由 路由id 路由到微服务的url  断言(判断条件)
      - id: product-service  #保持唯一
        #uri: http://127.0.0.1:9001 #目标微服务请求地址
        uri: lb://service-product  #lb://  根据微服务名称从注册中心拉取服务请求路径
        predicates:
        #- Path=/product/**  #路由条件 path:路由匹配条件
        - Path=/product-service/** #将当前请求转发到 http://127.0.0.1:9001/product/1
        filters: #配置路由过滤器 http://localhost:8080/product-service/priduct/1   --->转发到http://localhost:8080/priduct/1
        - RewritePath=/product-service/(?<segment>.*), /$\{segment} # 路径重写的过滤器
#eureka注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册

通过RewritePath配置重写转发的url,将/product-service/(?.*),重写为{segment},然后转发到订单
微服务。比如在网页上请求http://localhost:8080/product-service/product,此时会将请求转发到htt
p://127.0.0.1:9002/product/1( 值得注意的是在yml文档中 $ 要写成 $\ )

3.2.5开启微服务名称的转发

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启根据服务名称自动转发
          lower-case-service-id: true # 微服务名称以小写形式呈现

3.3 过滤器

Spring Cloud Gateway除了具备请求路由功能之外,也支持对请求的过滤。通过Zuul网关类似,也是通过过滤器的形式来实现的。

3.3.1 过滤器基础

(1) 过滤器的生命周期
Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么丰富,它只有两个:“pre” 和 “post”

  • PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择
    请求的微服务、记录调试信息等。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP
    Header、收集统计信息和指标、将响应从微服务发送给客户端等。

(2) 过滤器类型
Spring Cloud Gateway 的 Filter 从作用范围可分为另外两种GatewayFilter 与 GlobalFilter。

  • GatewayFilter:应用到单个路由或者一个分组的路由上。
  • GlobalFilter:应用到所有的路由上。

3.3.2 局部过滤器

局部过滤器(GatewayFilter),是针对单个路由的过滤器。可以对访问的URL过滤,进行切面处理。在Spring Cloud Gateway中通过GatewayFilter的形式内置了很多不同类型的局部过滤器。这里简单将Spring Cloud Gateway内置的所有过滤器工厂整理成了一张表格,虽然不是很详细,但能作为速览使
用。如下:
在这里插入图片描述
每个过滤器工厂都对应一个实现类,并且这些类的名称必须以 GatewayFilterFactory 结尾,这是Spring Cloud Gateway的一个约定,例如 AddRequestHeader 对应的实现类为AddRequestHeaderGatewayFilterFactory 。对于这些过滤器的使用方式可以参考官方文档

3.3.3 全局过滤器

全局过滤器(GlobalFilter)作用于所有路由,Spring Cloud Gateway 定义了Global Filter接口,用户
可以自定义实现自己的Global Filter。通过全局过滤器可以实现对权限的统一校验,安全性验证等功
能,并且全局过滤器也是程序员使用比较多的过滤器。
Spring Cloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:
在这里插入图片描述

3.4 统一鉴权

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

3.4.1 鉴权逻辑

开发中的鉴权逻辑:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务端对token进行解密,判断是否有效。
    在这里插入图片描述
    如上图,对于验证用户是否已经登录鉴权的过程可以在网关层统一检验。检验的标准就是请求中是否携带token凭证以及token的正确性

3.4.2 代码实现

下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求
参数“token”则不转发路由,否则执行正常的逻辑。

package cn.itcast.gateway.filter;

import org.bouncycastle.asn1.est.AttrOrOID;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;


/**
 * 自定义一个全局过滤器
 *     实现 GlobalFilter, Ordered 接口
 */
@Component
public class LoginFilter implements GlobalFilter, Ordered {
    /**
     * 执行过滤器中的业务逻辑
     *  对请求参数中的access-token进行判断,
     *  如果存在就此参数 代表已经认证成功
     *  如果不存在此参数   代表认证失败
     * @param exchange
     * @param chain
     * @return
     *
     * ServerWebExchange 相当于请求和相应的上下文
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了全局过滤器了。。。。。。。");
        //1. 获取请求参数  access-token
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
        //2. 判断是否存在
        if(token==null){
            //不存在  认证失败
            System.out.println("没有登录");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();//请求结束
        }
        //3. 如果存在,继续进行
        return chain.filter(exchange);
    }

    /**
     * 指定过滤器的执行顺序,数字越小等级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

在这里插入图片描述
在这里插入图片描述

  • 自定义全局过滤器需要实现GlobalFilter和Ordered接口。
  • 在filter方法中完成过滤器的逻辑判断处理
  • 在getOrder方法指定此过滤器的优先级,返回值越大级别越低
  • ServerWebExchange 就相当于当前请求和响应的上下文,存放着重要的请求-响应属性、请求实例和响应实例等等。一个请求中的request,response都可以通过 ServerWebExchange 获取调用 chain.filter 继续向下游执行

3.5 网关限流

3.5.1 常见的限流算法

(1) 计数器
计数器限流算法是最简单的一种限流实现方式。其本质是通过维护一个单位时间内的计数器,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值,则之后的请求都被拒绝,直到单位时间已经过去,再将计数器重置为零
在这里插入图片描述

存在的问题:
如果计数器的规定时间是1分钟,前十秒已经达到计数器的峰值,那么剩余的50秒只要有其他请求,服务器直接拒绝访问,

(2) 漏桶算法
漏桶算法可以很好地限制容量池的大小,从而防止流量暴增。漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。 在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。
在这里插入图片描述
在这里插入图片描述
注意:漏桶网关主要是保护网关下的服务器,对自身不负责

(3) 令牌桶算法
令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。
在这里插入图片描述
在这里插入图片描述
主要是保护自己 然后保护网关下的服务

3.5.2 基于filter的限流

(1)准备工作
redis
启动好redis服务
然后输入monitor监控redis服务的数据变化
在这里插入图片描述

在工程中引入redis的相关依赖

<!--监控插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--redis服务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

(2)修改网关中的application.yml配置

server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
  redis:
    host: localhost
    port: 6379
    database: 0
  cloud: #配置SpringCloudGateway的路由
    gateway:
      routes:
      - id: product-service
        uri: lb://service-product
        predicates:
        - Path=/product-service/**
        filters:
        - name: RequestRateLimiter
          args:
            # 使用SpEL从容器中获取对象
            key-resolver: '#{@pathKeyResolver}'
            # 令牌桶每秒填充平均速率
            redis-rate-limiter.replenishRate: 1
            # 令牌桶的上限
            redis-rate-limiter.burstCapacity: 3
        - RewritePath=/product-service/(?<segment>.*), /$\{segment}
#eureka注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册


  • RequestRateLimiter:使用限流过滤器 是springcloud geteway提供的
  • 参数 replenishRate:令牌桶每秒填充平均速率
  • burstCapacity:令牌桶的上限
    (3)配置redis中key的解析器KeySesolver
  • 编写基于请求路径的限流规则
@Configuration
public class KeyResolverConfiguration {
    /**
     * 编写基于请求路径的限流规则
     *   //abc
     *   //基于请求ip 127.0.0.1
     *   //基于参数
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        //自定义KeyResolver
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getPath().toString());
            }
        };
    }
}

一秒钟访问超过三次会报错
在这里插入图片描述

  • 基于请求ip和参数的限流
@Configuration
public class KeyResolverConfiguration {


    /**
     * 基于请求参数的限流
     * 请求:abc?userId=1
     */
    @Bean
    public KeyResolver userKeyReslover(){
       return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
        // return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("X-Forwarded-For"));//基于请求ip的限流

    }
}

在这里插入图片描述

3.5.3 基于Sentinel的限流

Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流
在这里插入图片描述
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:

  • route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
  • 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组

Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则
和自定义 API 的实体和管理逻辑:

  • GatewayFlowRule :网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
  • ApiDefinition :用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api ,请求 path 模式为 /foo/** 和 /baz/** 的都归到 my_api 这个 API分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。

(1)环境搭建
导入Sentinel 的响应依赖

   <!--sentinel 限流-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
            <version>1.6.3</version>
        </dependency>
server:
  port: 8080 #端口
spring:
  application:
    name: api-gateway-server #服务名称
  redis:
    host: localhost
    port: 6379
    database: 0
  cloud: #配置SpringCloudGateway的路由
    gateway:
      routes:
      - id: product-service
        uri: lb://service-product
        predicates:
        - Path=/product-service/**
        filters:
#        - name: RequestRateLimiter
#          args:
            # 使用SpEL从容器中获取对象
#            key-resolver: '#{@userKeyReslover}'
            # 令牌桶每秒填充平均速率
#            redis-rate-limiter.replenishRate: 1
            # 令牌桶的上限
#            redis-rate-limiter.burstCapacity: 3
        - RewritePath=/product-service/(?<segment>.*), /$\{segment}
        # RequestRateLimiter:使用限流过滤器 是springcloud geteway提供的
        #参数  replenishRate:令牌桶每秒填充平均速率
        # burstCapacity:令牌桶的上限
#eureka注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册

(2) 编写配置类
GatewayConfiguration


/**
 * sentinel限流的配置
 */
@Configuration
public class GatewayConfiguration {

	private final List<ViewResolver> viewResolvers;

	private final ServerCodecConfigurer serverCodecConfigurer;

	public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
	                            ServerCodecConfigurer serverCodecConfigurer) {
		this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
		this.serverCodecConfigurer = serverCodecConfigurer;
	}

	/**
	 * 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
	 */
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
		return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
	}

	/**
	 * 配置限流过滤器
	 */
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public GlobalFilter sentinelGatewayFilter() {
		return new SentinelGatewayFilter();
	}

	/**
	 * 配置初始化的限流参数
	 *  用于指定资源的限流规则
	 *  1. 资源名称 (路由id)
	 *  2. 配置统计时间
	 *  3. 配置限流阈值
	 */
	@PostConstruct
	public void initGatewayRules() {
		Set<GatewayFlowRule> rules = new HashSet<>();
		rules.add(new GatewayFlowRule("product-service")
				.setCount(1)
				.setIntervalSec(1)
		);
		GatewayRuleManager.loadRules(rules);
	}
}

一秒请求两次就会报异常

  • 请求异常返回数据格式
    在上面的配置类中编写方法

	/**
	 * 自定义限流处理器
	 */
	@PostConstruct
	public void initBlockHandlers() {
		BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
			public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
				HashMap<String, String> hashMap = new HashMap<>();
				hashMap.put("code","001");
				hashMap.put("messsage","不好意思限流了");
				return ServerResponse.status(HttpStatus.OK).
						contentType(MediaType.APPLICATION_JSON_UTF8).
						body(BodyInserters.fromObject(hashMap));
			}
		};
		GatewayCallbackManager.setBlockHandler(blockRequestHandler);
		}

在这里插入图片描述

3.5.4 sentinel配置限流规则

  • GateWay的配置类中:
/**
	 * 自定义API限流分组
	 *    1. 定义分组
	 *    2. 对小组配置限流规则
	 */
	@PostConstruct
	private void initCustomizedApis() {
		Set<ApiDefinition> definitions = new HashSet<>();
		ApiDefinition api1 = new ApiDefinition("product_api")
				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
					add(new ApiPathPredicateItem().setPattern("/product-service/product/**").
							setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
				}});
		ApiDefinition api2 = new ApiDefinition("order_api")
				.setPredicateItems(new HashSet<ApiPredicateItem>() {{
					add(new ApiPathPredicateItem().setPattern("/order-service/order"));
				}});
		definitions.add(api1);
		definitions.add(api2);
		GatewayApiDefinitionManager.loadApiDefinitions(definitions);
	}


	/**
	 * 配置初始化的限流参数
	 *  用于指定资源的限流规则
	 *  1. 资源名称 (路由id)
	 *  2. 配置统计时间
	 *  3. 配置限流阈值
	 */
	@PostConstruct
	public void initGatewayRules() {
		Set<GatewayFlowRule> rules = new HashSet<>();
		rules.add(new GatewayFlowRule("product_api")
				.setCount(1)
				.setIntervalSec(1)
		);
		GatewayRuleManager.loadRules(rules);
	}
  • 4
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值