点赞再看,养成习惯,微信搜寻【牧小农】关注我获取更多资讯,风里雨里,小农等你,很快乐可能成为你的敌人。
我的项目源码地址:公众号回复 sentinel,即可收费获取源码

背景

在微服务架构中,通常一个零碎会被拆分为多个微服务,面对这么多微服务客户端应该如何去调用呢?如果没有其余更优办法,咱们只能记录每个微服务对应的地址,别离去调用,然而这样会有很多的问题和潜在因素。

  1. 客户端屡次申请不同的微服务,会减少客户端代码和配置的复杂性,保护老本比价高。
  2. 认证简单,每个微服务可能存在不同的认证形式,客户端去调用,要去适配不同的认证,
  3. 存在跨域的申请,调用链有肯定的绝对复杂性(防火墙 / 浏览器不敌对的协定)。
  4. 难以重构,随着我的项目的迭代,可能须要从新划分微服务

为了解决下面的问题,微服务引入了 网关 的概念,网关为微服务架构的零碎提供简略、无效且对立的API路由治理,作为零碎的对立入口,提供外部服务的路由直达,给客户端提供对立的服务,能够实现一些和业务没有耦合的专用逻辑,次要性能蕴含认证、鉴权、路由转发、安全策略、防刷、流量管制、监控日志等。

网关在微服务中的地位:

网关比照

  • Zuul 1.0 : Netflix开源的网关,应用Java开发,基于Servlet架构构建,便于二次开发。因为基于Servlet外部提早重大,并发场景不敌对,一个线程只能解决一次连贯申请。
  • Zuul 2.0 : 采纳Netty实现异步非阻塞编程模型,一个CPU一个线程,可能解决所有的申请和响应,申请响应的生命周期通过事件和回调进行解决,缩小线程数量,开销较小

  • GateWay : 是Spring Cloud的一个全新的API网关我的项目,替换Zuul开发的网关服务,基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能高于Zuul
  • Nginx+lua : 性能要比下面的强很多,应用Nginx的反向代码和负载平衡实现对API服务器的负载平衡以及高可用,lua作为一款脚本语言,能够编写一些简略的逻辑,然而无奈嵌入到微服务架构中
  • Kong : 基于OpenResty(Nginx + Lua模块)编写的高可用、易扩大的,性能高效且稳固,反对多个可用插件(限流、鉴权)等,开箱即可用,只反对HTTP协定,且二次开发扩大难,不足更易用的治理和配置形式

GateWay

官网文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter

Spring Cloud Gateway 是Spring Cloud的一个全新的API网关我的项目,目标是为了替换掉Zuul1,它基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能⾼于Zuul,官⽅测试,Spring Cloud GateWay是Zuul的1.6倍 ,旨在为微服务架构提供⼀种简略无效的统⼀的API路由治理⽅式。

  1. 能够与Spring Cloud Discovery Client(如Eureka)、Ribbon、Hystrix等组件配合应用,实现路由转发、负载平衡、熔断、鉴权、门路重写、⽇志监控等
  2. Gateway还内置了限流过滤器,实现了限流的性能。
  3. 设计优雅,容易拓展

基本概念

路由(Route)是GateWay中最根本的组件之一,示意一个具体的路由信息载体,次要由上面几个局部组成:

  1. id:路由惟一标识,区别于其余的route
  2. url: 路由指向的目的地URL,客户端申请最终被转发到的微服务
  3. order: 用于多个Route之间的排序,数值越小越靠前,匹配优先级越高
  4. predicate:断言的作用是进行条件判断,只有断言为true,才执行路由
  5. filter: 过滤器用于批改申请和响应信息

外围流程

外围概念:

  1. Gateway ClientSpring Cloud Gateway 发送申请
  2. 申请首先会被 HttpWebHandlerAdapter 进行提取组装成网关上下文
  3. 而后网关的上下文会传递到 DispatcherHandler ,它负责将申请分发给 RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping 负责路由查找,并依据路由断言判断路由是否可用
  5. 如果过断言胜利,由 FilteringWebHandler 创立过滤器链并调用
  6. 通过特定于申请的 Fliter 链运行申请,Filter 被虚线分隔的起因是Filter能够在发送代理申请之前(pre)和之后(post)运行逻辑
  7. 执行所有pre过滤器逻辑。而后进行代理申请。收回代理申请后,将运行“post”过滤器逻辑。
  8. 处理完毕之后将 Response 返回到 Gateway 客户端

Filter过滤器:

  • Filter在pre类型的过滤器能够做参数效验、权限效验、流量监控、日志输入、协定转换等。
  • Filter在post类型的过滤器能够做响应内容、响应头的批改、日志输入、流量监控等

核心思想

当用户发出请求达到 GateWay 之后,会通过一些匹配条件,定位到真正的服务节点,并且在这个转发过程前后,进行一些细粒度的管制,其中 Predicate(断言) 是咱们的匹配条件,Filter 是一个拦截器,有了这两点,再加上URL,就能够实现一个具体的路由,核心思想:路由转发+执行过滤器链

这个过程就好比考试,咱们考试首先要找到对应的考场,咱们须要晓得考场的地址和名称(id和url),而后咱们进入考场之前会有考官查看咱们的准考证是否匹配(断言),如果匹配才会进入考场,咱们进入考场之后,(路由之前)会进行身份的注销和考试的科目,填写考试信息,当咱们考试实现之后(路由之后)会进行签字交卷,走出考场,这个就相似咱们的过滤器

Route(路由) :构建网关的根底模块,由ID、指标URL、过滤器等组成

Predicate(断言) :开发人员能够匹配HTTP申请中的内容(申请头和申请参数),如果申请断言匹配贼进行路由

Filter(过滤) :GateWayFilter的实例,应用过滤器,能够在申请被路由之前或者之后对申请进行批改

框架搭建

通过上述解说曾经理解了根底概念,咱们来入手搭建一个GateWay我的项目,来看看它到底是如何运行的
新建我的项目:cloud-alibaba-gateway-9006

版本对应:

GateWay属于SprinigCloud且有web依赖,在咱们导入对应依赖时,要留神版本关系,咱们这里应用的版本是 2.2.x的版本,所以配合应用的Hoxton.SR5版本

在这里咱们要留神的是引入GateWay肯定要删除spring-boot-starter-web依赖,否则会有抵触无奈启动

父类pom援用:

<spring-cloud-gateway-varsion>Hoxton.SR5</spring-cloud-gateway-varsion> <dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-dependencies</artifactId>    <version>${spring-cloud-gateway-varsion}</version>    <type>pom</type>    <scope>import</scope></dependency>

子类POM援用:

<dependencies>    <dependency>        <groupId>com.alibaba.cloud</groupId>        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-gateway</artifactId>        <version>2.2.5.RELEASE</version>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>    </dependency></dependencies>

yml配置

server:  port: 9006spring:  application:    name: cloud-gateway-service  cloud:    nacos:      discovery:        server-addr: localhost:8848    gateway:      discovery:        locator:          enabled: false #开启注册核心路由性能      routes:  # 路由        - id: nacos-provider #路由ID,没有固定要求,然而要保障惟一,倡议配合服务名          uri: http://localhost:9001/nacos-provider # 匹配提供服务的路由地址 lb://示意开启负载平衡          predicates: # 断言            - Path=/mxn/** # 断言,门路相匹配进行路由

咱们在之前的cloud-alibaba-nacos-9001我的项目中增加上面测试代码

@RestController@RequestMapping("/mxn")public class DemoController {    @Value("${server.port}")    private String serverPort;    @GetMapping(value = "/hello")    public String hello(){        return "hello world ,my port is :"+serverPort;    } }   

启动Nacos、cloud-alibaba-nacos-9001cloud-alibaba-gateway-9006通过gateway网关去拜访9001的mxn/order看看。

首先咱们在Nacos中看到咱们服务是注册到Nacos中了

而后咱们拜访http://localhost:9001/mxn/hello,确保是胜利的,在通过http://localhost:9006/mxn/hello去拜访,也是OK,阐明咱们GateWay搭建胜利,咱们进入下一步

在上述办法中咱们是通过YML去实现的配置,GateWay还提供了另外一种配置形式,就是通过代码的形式进行配置,@Bean 注入一个 RouteLocator

import org.springframework.cloud.gateway.route.RouteLocator;import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class GateWayConfig {      /*    配置了一个id为path_mxn的路由规定    当拜访地址http://localhost:9999/mxn/**    就会转发到http://localhost:9001/nacos-provider/mxn/任何地址     */    @Bean    public RouteLocator gateWayConfigInfo(RouteLocatorBuilder routeLocatorBuilder){        // 构建多个路由routes        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();        // 具体路由地址        routes.route("path_mxn",r -> r.path("/mxn/**").uri("http://localhost:9001/nacos-provider")).build();        // 返回所有路由规定        return routes.build();    }}

咱们能够将路由正文掉之后看一下,重启9006服务,拜访地址http://localhost:9006/mxn/hello 就能够转发到9001中具体的接口中

这里并不举荐,应用代码的形式来进行配置gateWay,大家有个理解就能够,因为代码的配置保护的老本比拟高,而且对于一些须要批改的项,须要改代码才能够实现,这样不利于保护和拓展,所以还是举荐大家应用yml进行配置。

GateWay负载平衡

在上述的解说中,咱们曾经把握了 GateWay 的一些根本配置和两种应用形式,上面咱们就来解说一下 GateWay 如何实现负载平衡

咱们只须要在9006中增加lb://nacos-provider就能够显示负载平衡。

当咱们去拜访http://localhost:9006/mxn/hello的时候,就能够看到9001和9002不停的切换

Predicate 断言

在这一篇中咱们来钻研一下 断言 ,咱们能够了解为:当满足条件后才会进行转发路由,如果是多个,那么多个条件须要同时满足

在官网提供的断言品种有11种(最新的有12种类型):

具体地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

  1. After:匹配在指定日期工夫之后产生的申请。
  2. Before:匹配在指定日期之前产生的申请。
  3. Between:须要指定两个日期参数,设定一个工夫区间,匹配此工夫区间内的申请。
  4. Cookie:须要指定两个参数,别离为name和regexp(正则表达式),也能够了解Key和Value,匹配具备给定名称且其值与正则表达式匹配的Cookie。
  5. Header:须要两个参数header和regexp(正则表达式),也能够了解为Key和Value,匹配申请携带信息。
  6. Host:匹配以后申请是否来自于设置的主机。
  7. Method:能够设置一个或多个参数,匹配HTTP申请,比方GET、POST
  8. Path:匹配指定门路下的申请,能够是多个用逗号分隔
  9. Query:须要指定一个或者多个参数,一个必须参数和一个可选的正则表达式,匹配申请中是否蕴含第一个参数,如果有两个参数,则匹配申请中第一个参数的值是否合乎正则表达式。
  10. RemoteAddr:匹配指定IP或IP段,符合条件转发。
  11. Weight:须要两个参数group和weight(int),实现了路由权重性能,依照路由权重抉择同一个分组中的路由

1. After : 示意配置工夫之后才进行转发

工夫戳获取代码,用于工夫代码的获取:

    public static void main(String[] args) {        ZonedDateTime zbj = ZonedDateTime.now();//默认时区        System.out.println(zbj);    }
spring:  application:    name: cloud-gateway-service  cloud:    nacos:      discovery:        server-addr: localhost:8848    gateway:      discovery:        locator:          enabled: true #开启注册核心路由性能      routes:  # 路由        - id: nacos-provider #路由ID,没有固定要求,然而要保障惟一,倡议配合服务名          uri: lb://nacos-provider # 匹配提供服务的路由地址 lb://示意开启负载平衡          predicates: # 断言            - Path=/mxn/** # 断言,门路相匹配进行路由            - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访

如果在时间段之前拜访则404

Before

匹配ZonedDateTime类型的工夫,示意匹配在指定日期工夫之前的申请,之后的申请则回绝404谬误

predicates: # 断言  - Path=/mxn/** # 断言,门路相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]

Between

Between 能够匹配ZonedDateTime类型的工夫,由两个ZonedDateTime参数组成,第一个参数为开始工夫,第二参数为完结工夫,逗号进行分隔,匹配在指定的开始工夫与完结工夫之内的申请,配置如下:

predicates: # 断言  - Path=/mxn/** # 断言,门路相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]

Cookie

由两个参数组成,别离为name(Key)regexp(正则表达式)(Value),匹配具备给定名称且其值与正则表达式匹配的Cookie。

路由规定会通过获取Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果匹配不上则不执行。

predicates: # 断言  - Path=/mxn/** # 断言,门路相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]   - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)示意任意字母

小写字母匹配胜利:

数字匹配不胜利:

Header

由两个参数组成,第一个参数为Header名称,第二参数为Header的Value值,指定名称的其值和正则表达式相匹配的Header的申请

predicates: # 断言  - Path=/mxn/** # 断言,门路相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)示意任意字母  - Header=headerName, \d+ # \d示意数字

申请头携带数字断言申请胜利,

断言字母匹配失败:

Host

匹配以后申请是否来自于设置的主机。

predicates: # 断言  - Path=/mxn/** # 断言,门路相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)示意任意字母#  - Header=headerName, \d+ # \d示意数字  - Host=**.muxiaonong.com #匹配以后的主机地址收回的申请

满足Host断言,申请胜利

不满足Host断言失败

Method

能够设置一个或多个参数,匹配HTTP申请,比方POST,PUT,GET,DELETE

predicates: # 断言  - Path=/mxn/** # 断言,门路相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)示意任意字母#  - Header=headerName, \d+ # \d示意数字#  - Host=**.muxiaonong.com #匹配以后的主机地址收回的申请  - Method=POST,GET

GET断言胜利

PUT断言申请失败

Query

由两个参数组成,第一个为参数名称(必须),第二个为参数值(可选-正则表达式),匹配申请中是否蕴含第一个参数,如果有两个参数,则匹配申请中第一个参数的值是否合乎第二个正则表达式。

predicates: # 断言  - Path=/mxn/** # 断言,门路相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)示意任意字母#  - Header=headerName, \d+ # \d示意数字#  - Host=**.muxiaonong.com #匹配以后的主机地址收回的申请#  - Method=POST,GET  - Query=id,.+ # 匹配任意申请参数,这里如果须要匹配多个参数,能够写多个- Query=

断言匹配 申请胜利

RemoteAddr

参数由CIDR 表示法(IPv4 或 IPv6)字符串组成,也就是匹配的ID地址,配置如下:

predicates: # 断言  - Path=/mxn/** # 断言,门路相匹配进行路由# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在这个工夫之后的申请够能够进行通过,之前的则不能进行拜访#  - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]#  - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]#  - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正则表达式)示意任意字母#  - Header=headerName, \d+ # \d示意数字#  - Host=**.muxiaonong.com #匹配以后的主机地址收回的申请#  - Method=POST,GET#  - Query=id,.+ # 匹配任意申请参数,这里如果须要匹配多个参数,能够写多个Query  - RemoteAddr=192.168.1.1/24

RemoteAddr

须要两个参数group和weight(int)权重数值,实现了路由权重性能,示意将雷同的申请依据权重跳转到不同的uri地址,要求group的名称必须统一

routes:  # 路由  - id: weight_high #路由ID,没有固定要求,然而要保障惟一,倡议配合服务名    uri: https://blog.csdn.net/qq_14996421    predicates: # 断言      - Weight=groupName,8  - id: weight_low #路由ID,没有固定要求,然而要保障惟一,倡议配合服务名    uri: https://juejin.cn/user/2700056290405815    predicates: # 断言      - Weight=groupName,2

间接拜访http://localhost:9006/能够看到咱们申请的地址成8/2比例交替显示, 80% 的流量转发到https://blog.csdn.net/qq_14996421,将约 20% 的流量转发到https://juejin.cn/user/2700056290405815

Predicate就是为了实现一组匹配规定,让申请过去找到对应的Route进行解决。如果有多个断言则全副命中后进行解决

GateWay Filter

路由过滤器容许批改传入的HTTP申请或者返回的HTTP响应,路由过滤器的范畴是特定的路由.

Spring Cloud GateWay 内置的Filter生命周期有两种:pre(业务逻辑之前)、post(业务逻辑之后)

GateWay自身自带的Filter分为两种: GateWayFilter(繁多)、GlobalFilter(全局)

GateWay Filter提供了丰盛的过滤器的应用,繁多的有32种,全局的有9种,有趣味的小伙伴能够理解一下。

官网参考网址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters

StripPrefix

StripPrefix 在咱们以后申请中,通过规定值去掉某一部分地址,比方咱们有一台服务中退出了一个前端nacos-provider想要通过这个去拜访,咱们在我的项目cloud-alibaba-nacos-9001中退出 context-path

server:  port: 9001  servlet:    context-path: /nacos-provider

当初9001的拜访门路变为http://localhost:9001/nacos-provider/mxn/hello,然而如果咱们通过网关去拜访门路就会变成http://localhost:9006/mxn/nacos-provider/mxn/hello 这个时候咱们通过这个门路去拜访是拜访不胜利的,想要解决这个办法,这个就用到了咱们FIlter 中的 StripPrefix

routes:  # 路由  - id: nacos-provider #路由ID,没有固定要求,然而要保障惟一,倡议配合服务名    uri: lb://nacos-provider    predicates: # 断言      - Path=/mxn/** # 匹配对应地址    filters:      - StripPrefix=1 # 去掉地址中的第一局部

咱们重新启动9006我的项目,再去拜访

自定义Filter

尽管Gateway给咱们提供了丰盛的内置Filter,然而理论我的项目中,自定义Filter的场景十分常见,因而独自介绍下自定义FIlter的应用。

想要实现GateWay自定义过滤器,那么咱们须要实现GatewayFilter接口和Ordered接口

@Slf4j@Componentpublic class MyFilter implements Ordered, GlobalFilter {    /**     * @param exchange 能够拿到对应的request和response     * @param chain 过滤器链     * @return 是否放行     */    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        //获取第一个参数        String id = exchange.getRequest().getQueryParams().getFirst("id");        //打印以后工夫        log.info("MyFilter 以后申请工夫为:"+new Date());        //判断用户是否存在        if(StringUtils.isEmpty(id)){            log.info("用户名不存在,非法申请!");            //如果username为空,返回状态码为407,须要代理身份验证            exchange.getResponse().setStatusCode(HttpStatus.PROXY_AUTHENTICATION_REQUIRED);            // 后置过滤器            return exchange.getResponse().setComplete();        }        return chain.filter(exchange);    }    /**     * 设定过滤器的优先级,值越小则优先级越高     * @return     */    @Override    public int getOrder() {        return 0;    }}

当咱们拜访http://localhost:9006/mxn/nacos-provider/mxn/hello申请,没有携带ID参数,申请失败

当咱们拜访http://localhost:9006/mxn/nacos-provider/mxn/hello?id=1申请,申请胜利

总结

到这里咱们的GateWay就解说完了,对于GateWay的外围点次要有三个Route\Predicate\Filter,咱们搞懂了这三点,基本上对于GateWay的常识就把握的差不多了,GateWay外围的流程就是:路由转发+执行过滤器链,如果对文中有疑难的小伙伴,欢送留言探讨。

创作不易,如果文中对你有帮忙,记得点赞关注,您的反对是我创作的最大能源。

我是牧小农,怕什么真谛无穷,进一步有进一步的欢喜~