0. 引言

前两期我们针对微服务的概念和基本情况做了介绍,那么本期我们就针对其中最重要的网关组件来进行详细讲解。如果还不清楚这些基础概念的,可以查看前两期文章:
什么是分布式微服务,如何学习微服务(一)

微服务涉及哪些技术、有哪些核心组件(二)

1. Spring Cloud Gateway简介

Spring Cloud Gateway是Spring Cloud推出的用来替代Zuul的网关产品,如同zuul综合了ribbon、hystrix的负载均衡、熔断、降级、限流能力,Spring Cloud Gateway也整合了hystrix

网关作为服务的统一入口,我们也额外在网关上提供了身份校验、监控、应用检测等功能。

1.2 gateway与zuul的区别

zuul1.x采用Servlet进行通信,底层是同步IO,新来一个请求就会新增一个线程,并且不会进行回收。所以资源占用较高,也就意味着支持的并发量不高。虽然在zuul2.x将通信调整为了Netty+Servlet来实现,并且支持异步,但是性能上差别不是很大

gateway底层是Netty,支持的请求数在1W~1.5W左右,性能要比Zuul高很多。因此我们更推荐使用gateway。

2. gateway的使用

maven依赖

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

2.1 断言 Predicates

断言在编程中的含义是表示与验证软件开发者预期的结果。这个抽象的概念具象到网关中表示的意思就是通过配置规则来判断请求会分发到哪个服务上。

gateway提供了十多种断言,我们列举几种常用的:

  • path断言:根据请求url路径判断
predicates:
- Path=/xxx/* 
  • query断言:根据请求路径中的参数名与参数值判断,比如localhost:8080/user?type=apple。
    Query有两个参数:参数名,参数值(支持正则,非必填)
predicates:
  # 匹配请求参数名为type,且值为a开头的请求
- Query=type,a.*
  # 匹配请求参数名为type的请求
# - Query=type
  • method断言:根据请求方式判断,比如get,post
predicates:
- Method=get
  • host断言:根据域名判断
predicates:
- Host=baidu.com
  • cookie断言:根据cookie判断
predicates:
- Cookie=name,55555
  • Header断言:根据请求头判断
predicates:
- Header=Authorization,password

断言需要与route搭配使用,后续我们具体讲解

2.2 负载均衡 balancer

gateway可结合ribbon来实现切换负载均衡策略,ribbon的负载均衡策略有:

策略类策略名称作用
RandomRule随机策略随机选择服务
RoundRobinRule轮询策略轮询选择服务,默认策略
RetryRule重试策略先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败则在指定的时间会进行重试,进行获取可用的服务。如多次获取某个服务失败,就不会再次获取该服务。主要是在一个时间段内,如果选择一个服务不成功,就继续找可用的服务,直到超时
BestAvailableRule最低并发策略会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略
AvailabilityFilteringRule可用过滤策略会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表安装轮询策略进行访问
WeightedResponseTimeRule响应时间加权重策略据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule。响应时间长,权重低,被选择的概率低。反之,同样道理。此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间
ZoneAvoidanceRule区域权重策略综合判断Server所在区域的性能和Server的可用性,轮询选择服务器

ribbon依赖

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

配置示例,这里的xxx是注册到注册中心上的服务名

xxx:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

自定义负载均衡策略

若要自定义负载均衡,只需要继承AbstractLoadBalancerRule类。实现choose和initWithNiwsConfig方法即可

public class MyRule extends AbstractLoadBalancerRule{
    
    @Override
    public Server choose(Object v){
    	// list是服务列表,可返回的是调用的服务
         List<Server> list = this.getLoadBalancer().getReachableServers();
         // TODO 自定义逻辑                     
         return list.get(0);   
    } 
    
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig){
        //      
    }
}

配置文件中声明

xxx:
    ribbon:
        NFLoadBalancerRuleClassname: com.wu.gateway.MyRule

2.3 动态路由 route

什么是动态路由?

动态路由是指路由器能够自动地建立自己的路由表,并且能够根据实际情况的变化适时地进行调整。结合到网关来说,网关通过服务名为路径来进行请求转发,并且网关也实时维护了自己的路由表(服务名与真实ip路径的对应表),从而实现动态路由实现

首先路由指的就是用户发来的请求,可以对应转发到具体的服务上,那么动态体现在哪里?如下图所示,当两个商品服务都存在时,请求打进来会根据负载均衡策略转发到具体的服务上。当有一个服务宕机时,就会从服务列表中剔除,从而只转发到存活的服务上,可以看到这个过程是动态调整的。
在这里插入图片描述
开启从注册中心动态创建路由的功能,利用微服务名进行路由

spring: 
  cloud:
    gateway:
      discovery:
        locator:
          # 开启从注册中心动态创建路由的功能,利用微服务名进行路由,默认false
          enabled: true

另外上面说到断言要和路由结合使用,其配置示例如下

其中uri可以使用lb://服务名的形式,这种就是结合了注册中心来进行转发的,如果没有注册中心,也可以直接维护ip地址,如uri:http://localhost:8081,http://localhost8082,多个地址用英文逗号隔开

spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
          enabled: true
      routes:
        # 路由ID,唯一即可,一般建议服务名
        - id: product-server
        # uri可以用具体的ip地址,比如uri:http://localhost:8081
          uri: lb://product-server
        # 路径断言:当请求url以/product-server/product开头的转发到这个路由的服务地址上
          predicates:
            - Path=/product-server/product/**
        - id: order-server
          uri: lb://order-server
          predicates:
            - Method=get

自定义路由

要实现自定路由的话,需要自定义一个RouteLocator的bean,如下提供了一个代码示例,这个方法需要放到一个配置类中(普通类上添加上@Configuration注解就是配置类)

其实不难发现,其配置和上述在配置文件中的配置类似,我们也可以直接在配置文件中自定义路由

@Bean
public RouteLocator routeLocator (RouteLocatorBuilder locatorBuilder){
    return locatorBuilder.routes()
        .route(p -> 
            p.path("/xxx").filters(f -> f.stripPrefix(1)).uri("http://xxx.com")
        )
        .route(p -> 
            p.path("/go").filters(f -> f.stripPrefix(1)).uri("lb://product-server")  
        ).build();
}

2.4 过滤器 filter

什么是过滤器?
所谓过滤器是在请求过程中对请求或响应进行一些处理。gateway中的过滤器从触发点上分为post和pre,即请求到微服务之后触发,和请求转发到微服务之前触发。

通过pre过滤器,因为是在转发之前触发,就可以实现参数校验、权限校验、流量监控、日志记录等等

通过post过滤器,因为是在转发之后触发,就可以对响应内容做二次处理。

要实现自定义过滤器,要声明 Ordered,GlobalFilter接口,并且实现filter、getOrder方法,下面我们给一个校验Token的过滤器示例:

@Component
public class MyFilter implements Ordered,GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
       //校验 Token 合法性
		ServerHttpResponse resp = exchange.getResponse();
		String headerToken = exchange.getRequest().getHeaders().getFirst(AuthProvider.AUTH_KEY);
		String paramToken = exchange.getRequest().getQueryParams().getFirst(AuthProvider.AUTH_KEY);
		if (StringUtils.isBlank(headerToken) && StringUtils.isBlank(paramToken)) {
			return unAuth(resp, "缺失令牌,鉴权失败");
		}
        return chain.filter(exchange);                                                                
    }
    
    // filter执行的顺序
    @Override
    public int getOrder(){
        return 0;    
    }
}

2.5 限流 ratelimiter

所谓限流就是限制请求量,比如说当因为恶意攻击或者热点事件导致请求量在某个时间点的时候激增,如果不做限制,会导致服务因为承载不住而崩溃。

因此我们需要给网关加上一层限制措施,当流量激增时,会正常服务一部分请求,另一部分请求会隔离出去,起码保证部分用户的可用性。

常见的限流算法包括:

  • 计数器算法
  • 漏桶算法
  • 令牌桶算法
    这一期我们就不详细扩展这些算法了,后续再做讲解

限流配置
创建限流类,用于说明根据什么规则进行限流,比如说根据IP进行限流,如下设置一个Ip每秒只能访问3次

@Configuration
public class RateLimitConfig{ 
	
	@Bean
    KeyResolver ipKeyResolver() {
        // 根据访问者的Ip地址进行限流
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());   
    }
}

修改配置文件

routes:
- id: w1
  predicates:
  - Path=/w/**
  uri: lb://product-server
  
  filters:
  - StripPrefix=1
  - name: RequestRateLimiter
    args: 
     # 通过SPEL表达式来指定使用哪一个KeyResolver
      key-resolver: '#{@ipKeyResolver}'
      # 一共3个牌子,每秒发1个,领到牌子请求才能正常转发到服务
      redis-rate-limiter.replenishRate: 1
      redis-rate-limiter.burstCapacity: 3

2.6 鉴权 auth

因为网关是统一入口,所以实际上我们可以在网关中针对权限做一个初步校验。比如说判断用户是否有登陆、是否有token、token是否合法

什么是token?
初学者看到这里可能会疑惑,什么是token呢?简单来说就是用户初次登陆的时候系统会根据其用户、密码生成一个加密的字符串,这个字符串就是token,之后这个token会返回给用户,后续这个用户的每次请求,都会在请求头中加入这个token。然后网关中就会根据同样的加密算法来对这个字符串进行解密,解密后并不会再对用户密码进行校验,而是如果解密出来的字符串符合算法的格式要求就认为token是合法的,请求通过。这样的好处在于,适用于分布式系统,并且无需再去数据库进行校验。这里先对token有一个初步了解即可,后续我们再详谈。

实际上上述对于过滤器的讲解中,已经展示了部分token校验,目前主流的进行token检验的组件是利用JWT来实现,这个我们后续在项目实操中具体演示。
在这里插入图片描述

2.7 灰度发布

灰度发布:指某些服务分为版本1和版本2,部分用户请求的请求打到版本1,部分用户的请求打到版本2。这个适用于有部分服务有新功能上线时,要求部分用户进行测试体验,而其他大部分用户仍然使用原来稳定的版本。

在gateway中我们可以利用断言加权重配置来实现
比如我们在请求路径中添加上版本号(或者也可以在header中添加version),然后我们针对不同的版本配置不同的权重配比

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
      - id: w1
        predicates:
        - Path=/w/v2/**
        # weight的两个参数:分组,权重
        - Weight=service,5
        uri: lb://product-server
        filters:
        - StripPrefix=1
      - id: w2 
        predicates:
        - Path=/w/**
        - Weight=service,95
        uri: lb://product-server
        filters:
        - StripPrefix=1 

相信到了这里,大家对gateway支持的功能也有了大致了解,后续我们通过项目实战来加深大家对与网关的理解。

下期预告

1、springcloud:通过项目实操来深入理解网关组件gateway
2、spring cloud alibaba nacos详解
3、基于gateway,nacos搭建微服务框架

关注公众号,了解更多新鲜内容

在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐