目录

(一)Nacos动态配置
(二)Nacos注册中心
(三)Sentinel之限流
(四)Sentinel之熔断
(五)Gateway之路由、限流
(六)Gateway之鉴权、日志
(七)Gateway搭配Nacos实现动态路由
(八)Dubbo + Nacos

正文

在微服务架构中,网关的职责包括路由、鉴权、限流、日志、监控、灰度发布等,目前主流的方案有Neflix Zuul和Spring Cloud Gateway。

需要注意的是,从性能上看,无论是Zuul、Zuul2还是Spring的亲儿子Gateway,都和直接用Nignx的方式有较大的差距。先看看Spring Cloud Gateway提供了什么样的功能,值得为它牺牲性能。

一、路由

1 准备工作

参考 Spring Cloud Alibaba实战(二) - Nacos注册中心 启动Nacos和两个payment-service服务。

2 创建项目

创建一个SpringBoot项目,添加Cloud Gateway和Nacos相关依赖,不要添加Web依赖。完成后的pom如下:

<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
        <alibaba.version>0.9.0.RELEASE</alibaba.version>
    </properties>

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

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>${alibaba.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${alibaba.version}</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

application.yml

server:
  port: 8084

bootstrap.yml

spring:
  application:
    name: gateway
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: payment-router
        uri: lb://payment-service
        predicates:
        - Path=/pay/**

在上面的配置中:

id: payment-router 值随意,方便记忆并且在所有路由定义中唯一即可
uri: lb://payment-service lb://为固定写法,表示开启负载均衡;payment-service即服务在Nacos中注册的名字
predicates:- Path=/pay/** 使用"Path Route Predicate Factory",规则为/pay开头的任意URI

最后一步,在启动类上增加@EnableDiscoveryClient注解

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

启动应用并测试

返回结果表明请求被转发到payment-service上,同时查看两个payment服务的输出窗口:

可以看出多次请求被平均分配到两个实例上。

除了Path Route Predicate Factory,Gateway还支持多种设置方式:

类型示例
AfterAfter=2017-01-20T17:42:47.789-07:00[America/Denver]
BeforeBefore=2017-01-20T17:42:47.789-07:00[America/Denver]
Between2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
CookieCookie=chocolate, ch.p
HeaderHeader=X-Request-Id, \d+
HostHost=**.somehost.org
MethodMethod=GET
PathPath=/foo/{segment}
QueryQuery=baz
RemoteAddrRemoteAddr=192.168.1.1/24

二、限流

Gateway通过内置的RequestRateLimiter过滤器实现限流,使用令牌桶算法,借助Redis保存中间数据。用户可通过自定义KeyResolver设置限流维度,例如:

  • 对请求的目标URL进行限流
  • 对来源IP进行限流
  • 特定用户进行限流

本例针对来源IP限流。

添加Redis依赖:

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

在application.yml中添加Redis配置:

server:
  port: 8084
spring:
  redis:
    host: 127.0.0.1
    port: 6379

SpringBoot自动配置的RedisTemplate生成的key中会包含特殊字符,所以创建一个RedisTemplate替换

@Configuration
public class RedisConfiguration {

    @Bean("redisTemplate")
    public RedisTemplate redisTemplate(@Value("${spring.redis.host}") String host,
                                       @Value("${spring.redis.port}") int port) {
        RedisTemplate redisTemplate = new RedisTemplate();
        RedisSerializer stringRedisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setConnectionFactory(standaloneConnectionFactory(host, port));
        return redisTemplate;
    }

    protected JedisConnectionFactory standaloneConnectionFactory(String host, int port) {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
    }
}

自定义KeyResolver

@Configuration
public class RateLimiterConfiguration {

    @Bean(value = "ipKeyResolver")
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

最后一步,在bootstrap.yml的payment-router路由中加入限流过滤器

...
      routes:
      - id: payment-router
        uri: lb://payment-service
        predicates:
        - Path=/pay/**
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 5
            key-resolver: '#{@ipKeyResolver}'

其中令牌桶容量redis-rate-limiter.burstCapacity设置为5,即1秒内最大请求通行数为5个,令牌桶填充速率redis-rate-limiter.replenishRate设置为1。使用jmeter测试:

第一次,5个线程

第二次,10个线程

三、熔断

网关是所有请求的入口,如果部分后端服务延时严重,则可能导致大量请求堆积在网关上,拖垮网关进而瘫痪整个系统。这就需要对响应慢的服务做超时快速失败处理,即熔断。

添加hystrix依赖

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

在bootstrap.yml中添加默认过滤器

spring:
  ...
  cloud:
    ...
    gateway:
      discovery:
        locator:
          enabled: true
      default-filters:
      - name: Hystrix
        args:
          name : default
          fallbackUri: 'forward:/defaultFallback'
      ...
hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: SEMAPHORE
          thread:
            timeoutInMilliseconds: 2000

 创建降级处理FallbackController.java

@RestController
public class FallbackController {
    @RequestMapping("/defaultFallback")
    public Map defaultFallback() {
        Map map = new HashMap<>();
        map.put("code", 1);
        map.put("message", "服务异常");
        return map;
    }
}

在Nacos后台中把payment-service-dev.properties的sleep值修改为2000模拟服务延时效果,然后测试

本期源码

链接:https://pan.baidu.com/s/1ZHn59CIMTuDO3YC34AM-KQ 
提取码:3ob9 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐