微服务网关哪家强

随着微服务时代的到来,关于服务的访问也变得尤其重要,而微服务网关作为流量和服务之间的桥梁,也占据着举足轻重的地位。近年来伴随着微服务架构的盛行,微服务网关的技术也是日新月异,今天小编就选取几个最常用的几个网关选型,从部署、使用到特性讲解,一步步剖析。

1、网关的概念


传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关,客户端只能在本地记录每个微服务的调用地址,当需要调用的微服务数量很多时,它需要了解每个服务的接口,这个工作量很大。那有了网关之后,网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务,所以,使用网关的好处在于:

  • 简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;

  • 降低函数间的耦合度。一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性

  • 解放开发人员把精力专注于业务逻辑的实现。由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能,而不需要每个服务 API 实现时都去考虑 

2、流量网关和业务网关


有的小伙伴们可能在工作过程中听到过网关的这种区分,流量网关和业务网关这两个概念其实也并没有严格意义上的区分。从概念上来讲,流量网关是承接系统的一手流量,位于最前端,主要工作通常是转发请求,还可以做一些流量控制、鉴权、黑白名单等操作,流量网关作为访问流程的第一道屏障需要非常高的性能,比较常用的就是Nginx。业务网关更多的是跟后端微服务领域相关的,部署在流量网关的下一层,在这里会做协议转换、服务发现、负载均衡、服务治理(限流、容错、降级)等操作。现在很多企业的架构也经常把两者合二为一,并不一定要严格的区分部署分开部署。 

外部流量转发流程图:

3、常用网关选型 


目前市面上开源的网关实现有很多种,功能上几乎都差不多,实现语言上有Java、GO、C等,今天我们主要选取三种比较常用的网关技术,具体分析下实现步骤。

Spring Cloud Gateway

Spring Cloud Gateway 是 Spring Cloud的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,可以集成Eureka等注册中心使用。

Spring Cloud Gateway有三大组件:

Route(路由):这是网关的基本模块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。

Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。

Filter(过滤器):这是org.springframework.cloud.gateway.fifilter.GatewayFilter的实例,我们可以使用它修改请求和响应。
 

下面我们从一个真实案例出发,看看Spring Cloud Gateway如何在项目中使用:

  1. 创建一个Spring Cloud Gateway项目:
    引入依赖:Spring Cloud的一员,直接项目中添加依赖即可
     

    //引入gateway
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
    //引入eureka注册中心
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    

    启动项目:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient // 开启Eureka客户端发现服务
    public class DemoGatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoGatewayApplication.class, args);
        }
    }

    开启配置:application.yml

    
    # 端口号
    server.port: 10010
    # 应用名
    spring.application.name: api-gateway
    # 注册中心地址
    eureka.client.service-url.defaultZone: http://10.106.19.13:10086/eureka/
    

    实现上面几步,我们就已经搭建好了一个Spring Cloud Gateway项目,接下面就看下如何进行一些网关的配置。

  2. Preicate(断言)
    Predicate(断言)断言就是转发条件的配置,满足这个条件才会进入后续的转发流程。

    Path规则:路径匹配,比如/user/**,请求URL前缀为user的请求
    
    predicates:  
      - Path=/user/**   //前缀为user开头的路径会被转发

    Query:参数校验,可以校验URL中带指定的参数

    
    predicates:  
      - Query=abc   //请求中包含参数abc

    Header:请求的Header中必须包含某个参数以及对应的值符合要求

    
    predicates:  
      - Header=Connection,keep-alive  - Header=Cache-Control,max-age=0
    

    Method:指定的请求方式才会被转发

    
    predicates:
      - Method=GET

    RemoteAddr:允许访问的客户端IP地址

    
    predicates:
      - RemoteAddr=127.0.0.1
  3. URI配置
    URI(转发路径):URI和Predicate是对应的,指定的Predicate校验规则满足条件之后跳转到对应的URI,URI的配置方式也有多种,具体如下:

    直接 URI:指定一个完整的目标 URI,请求将直接转发到该 URI。

    
    spring:
      cloud:
        gateway:
          routes:
            - id: direct-route
              uri: http://example.com   //转发到example.com这个地址

    Forward Prefix:使用 forward: 前缀来将请求转发到内部资源。

    spring:
      cloud:
        gateway:
          routes:
            - id: forward-route
              uri: forward:/internal-resource   //通过forward转发到内部地址

    Websocket URI:用于将请求转发到支持 Websocket 的目标 URI。

    spring:
      cloud:
        gateway:
          routes:
            - id: websocket-route
              uri: ws://websocket-service  //转发到一个websocket服务

    不同的 URI 配置方式适用于不同的使用场景。选择合适的配置方式取决于你的需求和后端服务的架构。

  4. Filter
    Filter(过滤器):过滤器就是在请求的前后做一些操作,比如常用的鉴权、限流等,都是通过过滤器实现,Spring Cloud Gateway的过滤器有"pre"和"post"两个顺序,分别会在请求执行前和执行后调用。

    Gateway自带有几十种过滤器,可以直接在配置文件中声明使用:

    针对自带的过滤器我们都可以采用声明式的方式在yml配置文件中直接使用,gateway已经帮我们做好了具体的实现,下面介绍几个常用的自带过滤器:

    AddRequestHeader在请求的Header中添加数据
    AddRequestParameters添加请求参数
    AddResponseHeader对从网关返回的响应添加Header
    StripPrefix对匹配上的请求路径去除前缀
    PrefixPath对匹配上的请求路径添加前缀
     
    
    spring:
      cloud:
        gateway:
          # 全局过滤器配置
          default-filters:
         # 往响应过滤器中加入信息
            - AddResponseHeader: i-love,itheima
            - AddRequestParameter:name,bjsxt: 
            - AddRequestHeader: MyHeader,jqk
            - StripPrefix: 1
            - PrefixPath: /user

    除了使用Gateway自带的Filter,我们也可以自己自定义Fiilter,下面是一个自定义Filter示例判断请求参数Token:
     

    
    @Configuration
    public class MyGlobalFileter implements GlobalFilter, Ordered {
        /**
         * 自定义过滤器规则
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String token = exchange.getRequest().getQueryParams().getFirst("token");
            if (StringUtils.isBlank(token)) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }
    
        /**
         * 定义过滤器执行顺序
         * 返回值越小,越靠前执行
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }

    Gateway限流,这里单独提一下Gateway限流,其实也是用Filter进行的实现,不过Gateway为我们自带了限流的功能,是基于redis的RateLimter实现,只需要引入即可使用。

    引入依赖:

    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>

    定义KeyResolver:KeyResolver⽤于计算某⼀个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。

    
    @Bean
    public KeyResolver ipKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
            }
        };
    }

    RateLimit也是通过过滤器的方式来实现的,所以定义好过滤的key,还需要在配置文件中配置对应的限流策略:

    
    filters:
      - PrefixPath=/test
      - name: RequestRateLimiter
        args:
          //ipKeyResolver是通过spel表达式获取对应的Bean
          key-resolver: "#{@ipKeyResolver}"
          //令牌桶每秒填充速率
          redis-rate-limiter.replenishRate: 1
          //令牌桶容量
          redis-rate-limiter.burstCapacity: 1


    到这里关于Spring Cloud Gateway大部分的功能都介绍完毕了,下面给一个完整的配置示例:
     

    spring:
      application:
        name: sysgateway
      cloud:
        gateway:
          globalcors:
            cors-configurations:
              '[/**]': # 匹配所有请求
                allowedOrigins: "*" #跨域处理 允许所有的域
                allowedMethods: # ⽀持的⽅法
                  - GET
                  - POST
                  - PUT
                  - DELETE
        routes:
          - id: goods
            uri: lb://goods
            predicates:
              - Path=/goods/**
            filters:
              - StripPrefix= 1
              - name: RequestRateLimiter #请求数限流 名字不能随便写
                args:
                  key-resolver: "#{@ipKeyResolver}"
                  redis-rate-limiter.replenishRate: 1
                  redis-rate-limiter.burstCapacity: 1
          - id: system
            uri: lb://system
            predicates:
              - Path=/system/**
            filters:
              - StripPrefix= 1
        # 配置Redis 127.0.0.1可以省略配置
        redis:
          host: 192.168.200.128
          port: 6379
    server:
      port: 9101
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:6868/eureka
      instance:
        prefer-ip-address: true

Zuul 

Zuul是Netflix开源的微服务网关,可以和Eureka、Ribbon、Hystrix等组件配合使用,Spring Cloud对Zuul进行了整合与增强,Zuul的主要功能是路由转发和过滤器。路由转发功能是将外部请求转发到具体的我们的微服务上,是实现外部访问统一入口的基础,因为之后我们所有访问都要从网关走,我们会通过配置忽略掉其他的访问。而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。目前Spring Cloud对Zuul做了整合,所以使用起来也是相当的方便,跟Gateway差别不大。

  1. 创建一个Zuul应用项目:
    引入依赖: 
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    启动项目:@EnableZuulProxy

    
    @SpringBootApplication
    @EnableZuulProxy   //@EnableZuulProxy - 开启Zuul网关
    public class ZuulApplication {    
        public static void main(String[] args) {
            SpringApplication.run(ZuulApplication.class, args);
        }
    }

    基础配置application.yml

    server:
     # 本服务的端口号
      port: 9100
    
    spring:
      application:
        # 服务名或服务ID
        name: online-taxi-zuul
    
    #注册中心
    #当前应用是一个Zuul微服务网关。会在Eureka注册中心中注册当前服务。并发现其他的服务。
    eureka: 
      client:
        #设置服务注册中心的URL
        service-url:                      
          defaultZone: http://root:root@eureka-7900:7900/eureka/
      instance: 
        # 对应调用本服务的主机名或主机地址
        hostname: localhost
        # 向eureka注册的服务名或服务ID
        instance-id: online-taxi-zuul

    zuul核心配置
    转发配置:zuul转发路径配置相对来说比较简单,在application.yml文件中以zuul.routes开头。

    采用URL的转发方式:

    zuul:
      routes:
        custom-zuul-name: 
          path: /zuul-custom-name/**
          url: http://localhost:8080/custom   //转发到指定路径

    指定service-name的转发方式:

    zuul:
      routes:
        custom-zuul-name: 
          path: /zuul-custom-name/**
          service-id: service-custom   //转发到指定的服务

    对于转发到指定的服务,zuul会从配置中心拉取对应的服务,然后内部是采用Ribbon做的负载均衡。
     

    Filter
    Filter(过滤器)是Zuul的最核心的部分,核心逻辑就是通过过滤器链来完成的,类似于权限校验、特殊路径处理、限流以及错误处理等都是通过Filter实现。

    定义Zuul里面的Filter需要实现ZuulFilter接口,接口有以下几部分:

    filterType设定过滤器的类型(4种选择)
    filterOrder该过滤器在这种类型过滤器中的执行排序
    shouldFilter是否执行该过滤器的业务逻辑
    run该过滤器的业务逻辑


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

    过滤器内部状态流转:

    下面自定义一个限流的过滤器:

    @Component
    public class LimitFilter extends ZuulFilter {
    
        //声明类型
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            // 限流应该是所有过滤器的最前面,最为第一道工具,所以该值越小越好
            return -10;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        // gova的东西  相当于限流中的令牌生成器
        // create(50) 表示每秒生成50个令牌
        // create(0.1) 表示10秒生成1个(1秒生成0.1个)
        // 2 qps(1秒  2个 请求 Query Per Second 每秒查询量)
        private static final RateLimiter RATE_LIMITER = RateLimiter.create(50);
    
        @Override
        public Object run() throws ZuulException {
            RequestContext currentContext = RequestContext.getCurrentContext();
    
    
            if (RATE_LIMITER.tryAcquire()) {
                // RATE_LIMITER.tryAcquire(3); // 1下拿几个令牌
                // RATE_LIMITER.acquire();// acquire()属于阻塞,就是一直在这里等待着,tryAcquire()非阻塞,但可以设置等待时间,请自行尝试。
                System.out.println("通过");
                return null;
            } else {
                // 被流控的逻辑
                System.out.println("被限流了");
    
                // 可以设置limit值,后面所有的过滤器的shouldFilter中都监控该值,如过为false,就都不执行
                currentContext.set("limit", false);
                /**
                 * 所有其他过滤器中shouldFilter中添加的Limit的判断
                 *  Object limit = RequestContext.getCurrentContext().get("limit");
                 *  if (null != limit && false == (Boolean) limit) {
                 *      return false;
                 *  }
                 */
    
    
                currentContext.setSendZuulResponse(false);
                currentContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
            }
            return null;
        }
    }

Traefik 
 

Traefix是一个开源的云原生网关,主要用于云原生场景,可自动识别Kubernetes、Docker、Docker Swarm等等技术下部署的服务,通过label标签自动生成反向代理路由规则。近年来云原生发展迅速,kubernetes几乎是企业项目必备的基础设置,所以Traefix也得到了很好的应用,目前小编所在的公司就是使用的Traefik做的网关,给我的感觉就是为kubernetes而生。

了解Traefix之前我们必须要先了解一下Kubernetes的Ingress, Ingress是Kubernetes的一个插件,用于管理集群中的HTTP和HTTPS流量路由。它允许我们通过一个统一的入口点来管理多个服务的流量,同时提供负载均衡、SSL终止等功能。Ingress只是k8s定义的一种标准并没有具体的实现,而Traefix就是这种实现,同时还有Nginx Ingress Controller和HAProxy。在学习Traefik之前需要对Kubernetes有一定的了解,熟悉里面的资源对象和控制对象,可以参考小编之前发布的关于Kubernetes相关文章: kubernetes基础概念篇  kubernetes核心组件篇

下面是官网上的一张图,描述了一个外部请求是经过哪些流程到我们的k8s服务:

从中可以看到,Traefix所包含的几个核心组件:

  • Providers
    Providers是基础组件,Traefik的配置发现是通过它来实现的,它可以是协调器,容器引擎,云提供商或者键值存储。Traefik通过查询Providers的API来查询路由的相关信息,一旦检测到变化,就会动态的更新路由。

  • Entrypoints
    Entrypoints是Traefik的网络入口,它定义接收请求的接口,以及是否监听TCP或者UDP,外部的请求首先就是到Entrypoint

  • Routers
    Routers是路由处理,里面包含了请求的路由规则Rule以及对请求的特殊处理Middware

  • Services
    Services负责配置如何到达最终将处理传入请求的实际服务

  • Middlewares
    Middlewares用来修改请求或者根据请求来做出一些判断(authentication, rate limiting, headers, …),可以理解为上面两种网关的Filter,中间件被附加到路由上,是一种在请求发送到你的服务之前(或者在服务的响应发送到客户端之前)调整请求的一种方法。

下面通过配置一个完整的示例来一步步剖析每个流程,整个过程都是基于k8s的声明方式来进行部署,和之前介绍的两个网关有很大的差别。

  1. 部署Traefik(通过Helm部署)
    下载Traefik镜像:
    
    $ helm  search repo traefik
    NAME            CHART VERSION APP VERSION DESCRIPTION                                  
    traefik/traefik 10.6.0        2.5.3       A Traefik based Kubernetes ingress controller
    $ helm pull traefik/traefik

    配置文件:
     

    service:
      type: NodePort 
    
    ingressRoute:
      dashboard:
        enabled: false
    ports:
      traefik:
        port: 9000
        expose: true
      web:
        port: 8000
        expose: true
      websecure:
        port: 8443
        expose: true
    persistence:
      enabled: true
      name: data
      accessMode: ReadWriteOnce
      size: 5G
      storageClass: "openebs-hostpath"
      path: /data
    additionalArguments:
      - "--serversTransport.insecureSkipVerify=true"
      - "--api.insecure=true"
      - "--api.dashboard=true"

    部署命令:

    
    kubectl create ns traefik-ingress
    helm install traefik -n traefik-ingress -f my-value.yaml .

    部署成功后可以登录Traefik的Dashboard:

  2. 路由配置
    上面已经说到了kubernetes路面服务路由是根据Ingress进行配置,Traefik就是Ingress的一种具体实现,这里我使用CRD IngressRoute方式来配置,可以更好的配置对k8s服务的访问。

    配置Route:
    Match:路径匹配规则
    Services:对应kubernetes上面的service
    spec:
      entryPoints:
        - web
        - websecure
      routes:
        - kind: Rule
          match: PathPrefix(`/api/com-qt-app-ids/ids-detect`) //匹配规则
          services:
            - kind: Service
              name: svc-ids-detect   //匹配上转发到k8s的这个service上
              namespace: qt-system   //服务对应的命名空间
              passHostHeader: true   //将请求头传递给后端服务
              port: 80    //服务监听的端口

    部署路由规则:

    kubectl apply -f detect-ingressRoute.yaml -n qt-system
    

    当我们访问的路径为/api/com-qt-app-ids/ids-detect开头的,就会被转发到svc-ids-detect这个服务的80端口进行处理。

  3. Middleware配置
    Middleware中文解释是中间件,这里我们可以理解为就是网关中的Filter,可以在请求转发前后做一些操作,一个请求匹配Rules后,会经过一系列的Middleware,再到具体的Services上。

    先创建一个Middleware:将Http请求转发到Https:

    apiVersion: traefik.containo.us/v1alpha1
    kind: Middleware
    metadata:
      name: redirect-https-middleware
    spec:
      redirectScheme:
        scheme: http

    在路由规则中配置Middleware:

    apiVersion: traefik.containo.us/v1alpha1
    kind: IngressRoute
    metadata:
      name: ir-service-ids-detect
      namespace: qt-system
    spec:
      entryPoints:
        - web
        - websecure
      routes:
        - kind: Rule
          match: PathPrefix(`/api/com-qt-app-ids/ids-detect`)
          services:
            - kind: Service
              name: svc-ids-detect
              namespace: qt-system
              passHostHeader: true
              port: 80
          middlewares:
            - name: redirect-https-middleware   //配置之前创建的Middleware
              namespace: qt-syste

    除了Traefik自带的Middleware,我们也可以自己基于GO语音实现不同功能的Middleware,主要是需要编写下述几点:

    中间件配置:代码中表现为一个结构体,可以映射到配置文件中。
    中间件Handler:作为实现中间件逻辑功能的结构体,实现 http.Handler 接口。
    中间件的初始化代码:需要定义中间件的“构造函数”,用于读取配置并实例化中间件的Handler。

    这里是一个模板示例,插件如果需要被Traefik使用,还需要在Traefik对应的地方加载插件,即调用New方法:
     

    
    package main
    
    import (
      "net/http"
      "strings"
    
      "github.com/traefik/traefik/v2/pkg/middlewares"
    )
    
    type CustomHeader struct {
      next http.Handler
    }
    
    func NewCustomHeader(next http.Handler) (*CustomHeader, error) {
      return &CustomHeader{next: next}, nil
    }
    
    func (ch *CustomHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
      // 在这里添加自定义的头信息
      rw.Header().Add("X-Custom-Header", "Hello from Custom Header Middleware")
    
      // 继续处理下一个中间件或者后端服务
      ch.next.ServeHTTP(rw, req)
    }
    
    func New(ctx middlewares.RequestCtx, next http.Handler, config *middlewares.HandlerInfo) (http.Handler, error) {
      return NewCustomHeader(next)
    }

    关于网关的介绍到这里也要结束了,上面介绍了几种常用网关的使用方法,配置方面的应该有更丰富的配置,由于篇幅的原因这里只列举了一些常用的,在小编看来选取那种网关并没有好坏之分,需要看各自系统的环境以及技术栈。

学习更多微服务架构相关技术关注作者个人技术公众号:八阿哥技术圈,干货满满!!!

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值