使用springcloud gateway搭建网关(分流,限流,熔断)

Spring Cloud Gateway

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

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

相关概念:

  • Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
  • Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
  • Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。

工作流程:

客户端向 Spring Cloud Gateway 发出请求。如果 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

Spring Cloud Gateway 的特征:

  • 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
  • 动态路由
  • Predicates 和 Filters 作用于特定路由
  • 集成 Hystrix 断路器
  • 集成 Spring Cloud DiscoveryClient
  • 易于编写的 Predicates 和 Filters
  • 限流
  • 路径重写

快速上手

引入spring-boot  2.1.1.RELEASE ,springcloud的版本为 Greenwich.M3

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.M3</spring-cloud.version>
    </properties>

    <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>

添加的依赖包如下

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </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-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

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

路由断言

接下来就是配置了,可以使用java代码硬编码配置路由过滤器,也可以使用yml配置文件配置。下面我们首先介绍配置文件配置方式

application.yml

server.port: 8082

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://localhost:8000
          order: 0
          predicates:
            - Path=/foo/**
          filters:
            - StripPrefix=1

上面给出了一个根据请求路径来匹配目标uri的例子,如果请求的路径为/foo/bar,则目标uri为 http://localhost:8000/bar。如果上面例子中没有加一个StripPrefix=1过滤器,则目标uri 为http://localhost:8000/foo/bar,StripPrefix过滤器是去掉一个路径。

其他的路由断言和过滤器使用方法请查看官网

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.RC2/single/spring-cloud-gateway.html#gateway-how-it-works

接下来我们来看一下设计一个网关应该需要的一些功能

修改接口返回报文

因为网关路由的接口返回报文格式各异,并且网关也有有一些限流、认证、熔断降级的返回报文,为了统一这些报文的返回格式,网关必须要对接口的返回报文进行修改,过滤器代码如下:

package org.gateway.filter.global;

import java.nio.charset.Charset;

import org.gateway.response.Response;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class WrapperResponseFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        // -1 is response write filter, must be called before that
        return -2;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        // probably should reuse buffers
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        // 释放掉内存
                        DataBufferUtils.release(dataBuffer);
                        String rs = new String(content, Charset.forName("UTF-8"));
                        Response response = new Response();
                        response.setCode("1");
                        response.setMessage("请求成功");
                        response.setData(rs);

                        byte[] newRs = JSON.toJSONString(response).getBytes(Charset.forName("UTF-8"));
                        originalResponse.getHeaders().setContentLength(newRs.length);//如果不重新设置长度则收不到消息。
                        return bufferFactory.wrap(newRs);
                    }));
                }
                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };
        // replace response with decorator
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }
}

需要注意的是order需要小于-1,需要先于NettyWriteResponseFilter过滤器执行。

有了一个这样的过滤器,我们就可以统一返回报文格式了。

认证

以下提供一个简单的认证过滤器

package org.gateway.filter.global;

import java.nio.charset.StandardCharsets;

import org.gateway.response.Response;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;

import reactor.core.publisher.Mono;

@Component
public class AuthFilter implements GlobalFilter{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if ("token".equals(token)) {
            return chain.filter(exchange);
        }
        ServerHttpResponse response = exchange.getResponse();
        Response data = new Response();
        data.setCode("401");
        data.setMessage("非法请求");
        byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(datas);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

限流

springcloud gateway 为我们提供了限流过滤器RequestRateLimiterGatewayFilterFactory,和限流的实现类RedisRateLimiter使用令牌桶限流。但是官方的不一定满足我们的需求,所以我们重新写一个过滤器(基本和官方一致),只是将官方的返回报文改了。

package org.gateway.limiter;

import java.nio.charset.StandardCharsets;
import java.util.Map;

import org.gateway.response.Response;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;

import com.alibaba.fastjson.JSON;

import reactor.core.publisher.Mono;

/**
 * User Request Rate Limiter filter. See https://stripe.com/blog/rate-limiters and
 */
public class RateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<RateLimiterGatewayFilterFactory.Config> {

    public static final String KEY_RESOLVER_KEY = "keyResolver";

    private final RateLimiter defaultRateLimiter;
    private final KeyResolver defaultKeyResolver;

    public RateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter,
                                                  KeyResolver defaultKeyResolver) {
        super(Config.class);
        this.defaultRateLimiter = defaultRateLimiter;
        this.defaultKeyResolver = defaultKeyResolver;
    }

    public KeyResolver getDefaultKeyResolver() {
        return defaultKeyResolver;
    }

    public RateLimiter getDefaultRateLimiter() {
        return defaultRateLimiter;
    }

    @SuppressWarnings("unchecked")
    @Override
    public GatewayFilter apply(Config config) {
        KeyResolver resolver = (config.keyResolver == null) ? defaultKeyResolver : config.keyResolver;
        RateLimiter<Object> limiter = (config.rateLimiter == null) ? defaultRateLimiter : config.rateLimiter;

        return (exchange, chain) -> {
            Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

            return resolver.resolve(exchange).flatMap(key ->
                    // TODO: if key is empty?
                    limiter.isAllowed(route.getId(), key).flatMap(response -> {

                        for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
                            exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
                        }

                        if (response.isAllowed()) {
                            return chain.filter(exchange);
                        }
                        ServerHttpResponse rs = exchange.getResponse();
                        Response data = new Response();
                        data.setCode("101");
                        data.setMessage("访问过快");
                        byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
                        DataBuffer buffer = rs.bufferFactory().wrap(datas);
                        rs.setStatusCode(HttpStatus.UNAUTHORIZED);
                        rs.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
                        return rs.writeWith(Mono.just(buffer));
                    }));
        };
    }

    public static class Config {
        private KeyResolver keyResolver;
        private RateLimiter rateLimiter;
        private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;

        public KeyResolver getKeyResolver() {
            return keyResolver;
        }

        public Config setKeyResolver(KeyResolver keyResolver) {
            this.keyResolver = keyResolver;
            return this;
        }
        public RateLimiter getRateLimiter() {
            return rateLimiter;
        }

        public Config setRateLimiter(RateLimiter rateLimiter) {
            this.rateLimiter = rateLimiter;
            return this;
        }

        public HttpStatus getStatusCode() {
            return statusCode;
        }

        public Config setStatusCode(HttpStatus statusCode) {
            this.statusCode = statusCode;
            return this;
        }
    }

}

然后限流必须要有一个key,根据什么来进行限流,ip,接口,或者用户来进行限流,所以我们自定义一个KeyResolver

package org.gateway.limiter;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;

import reactor.core.publisher.Mono;

public class CustomKeyResolver implements KeyResolver {

    public static final String BEAN_NAME = "customKeyResolver";

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(getKey(exchange));
    }

    /**
     *
     * @param exchange
     * @return
     */
    private String getKey(ServerWebExchange exchange) {

        LimitKey limitKey = new LimitKey();

        limitKey.setApi(exchange.getRequest().getPath().toString());
        limitKey.setBiz(exchange.getRequest().getQueryParams().getFirst("biz"));

        return JSON.toJSONString(limitKey);
    }
}

最后RedisRateLimiter我们也需要重写,因为不支持多级限流,原生的只会判断一个key。代码如下:

    /**
     * This uses a basic token bucket algorithm and relies on the fact that Redis scripts
     * execute atomically. No other operations can run between fetching the count and
     * writing the new count.
     */
    @Override
    public Mono<Response> isAllowed(String routeId, String id) {
        if (!this.initialized.get()) {
            throw new IllegalStateException("RedisRateLimiter is not initialized");
        }

        LimitConfig limitConfig = getLimitConfig(routeId);

        if (limitConfig == null || limitConfig.getTokenConfig().size()==0) {
            return Mono.just(new Response(true,null));
        }

        Map<String, Config> conf = limitConfig.getTokenConfig();

        LimitKey limitKey = JSON.parseObject(id, LimitKey.class);
        //api限流
        String api = limitKey.getApi();
        Config apiConf = conf.get(api);
        //业务方限流
        String biz = limitKey.getBiz();
        Config bizConf = conf.get(biz);

        if (apiConf!=null) {
            return isSingleAllow(api,routeId,apiConf).flatMap(res -> {
                if (res.isAllowed()) {
                    if(bizConf!=null) {
                        return isSingleAllow(biz, routeId, bizConf);
                    }else {
                        return Mono.just(new Response(true,new HashMap<>()));
                    }
                }else {
                    return Mono.just(res);
                }
            } );
        }else {
            if (bizConf!=null) {
                return isSingleAllow(biz, routeId, bizConf);
            }else {
                return Mono.just(new Response(true,new HashMap<>()));
            }
        }
    }

    /**
     * 单级限流
     * @param api
     * @param routeId
     * @param apiConf
     * @return
     */
    private Mono<Response> isSingleAllow(String key, String routeId, Config config) {
        // How many requests per second do you want a user to be allowed to do?
        int replenishRate = config.getReplenishRate();

        // How much bursting do you want to allow?
        int burstCapacity = config.getBurstCapacity();

        try {
            List<String> keys = getKeys(routeId+"$"+key);

            // The arguments to the LUA script. time() returns unixtime in seconds.
            List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
                    Instant.now().getEpochSecond() + "", "1");
            // allowed, tokens_left = redis.eval(SCRIPT, keys, args)
            Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
                    // .log("redisratelimiter", Level.FINER);
            return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
                    .reduce(new ArrayList<Long>(), (longs, l) -> {
                        longs.addAll(l);
                        return longs;
                    }) .map(results -> {
                        boolean allowed = results.get(0) == 1L;
                        Long tokensLeft = results.get(1);

                        Response response = new Response(allowed, getHeaders(config, tokensLeft));

                        if (log.isDebugEnabled()) {
                            log.debug("response: " + response);
                        }
                        return response;
                    });
        }
        catch (Exception e) {
            /*
             * We don‘t want a hard dependency on Redis to allow traffic. Make sure to set
             * an alert so you know if this is happening too much. Stripe‘s observed
             * failure rate is 0.01%.
             */
            log.error("Error determining if user allowed from redis", e);
        }
        return Mono.just(new Response(true, getHeaders(config, -1L)));
    }

    private LimitConfig getLimitConfig(String routeId) {
        Map<String, LimitConfig> map = new HashMap<>();
        LimitConfig limitConfig = new LimitConfig();
        limitConfig.setRouteId("rateLimit_route");
        Map<String, Config> tokenMap = new HashMap<>();
        Config apiConfig = new Config();
        apiConfig.setBurstCapacity(5);
        apiConfig.setReplenishRate(5);

        Config bizConfig = new Config();
        bizConfig.setBurstCapacity(1);
        bizConfig.setReplenishRate(1);

        tokenMap.put("/hello/rateLimit", apiConfig);
        tokenMap.put("jieyin", bizConfig);
        limitConfig.setTokenConfig(tokenMap);
        map.put("rateLimit_route", limitConfig);
        return limitConfig;
    }

如上的代码是写死的,但是我们可以根据我们的业务需求设计一个自定义key,自定义令牌桶容量和速率的限流规则。

bean配置和yml配置如下

    @Bean
    @Primary
    public CustomRedisRateLimiter customRedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
                                             @Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
                                             Validator validator) {
        return new CustomRedisRateLimiter(redisTemplate, redisScript, validator);
    }

    @Bean
    public RateLimiterGatewayFilterFactory rateLimiterGatewayFilterFactory(CustomRedisRateLimiter customRedisRateLimiter, CustomKeyResolver customKeyResolver) {
        return new RateLimiterGatewayFilterFactory(customRedisRateLimiter, customKeyResolver);
    }
server.port: 8082

spring:
  application:
    name: gateway
  redis:
      host: localhost
      port: 6379
      password: 123456
  cloud:
    gateway:
      routes:
        - id: rateLimit_route
          uri: http://localhost:8000
          order: 0
          predicates:
            - Path=/foo/**
          filters:
            - StripPrefix=1
            - name: RateLimiter

熔断

当下游接口负载很大,或者接口不通等其他原因导致超时,如果接口不熔断的话将会影响到下游接口得不到喘息,网关也会因为超时连接一直挂起,很可能因为一个子系统的问题导致整个系统的雪崩。所以我们的网关需要设计熔断,当因为熔断器打开时,网关将返回一个降级的应答。

熔断配置如下:

server.port: 8082

spring:
  application:
    name: gateway
  redis:
      host: localhost
      port: 6379
      password: 123456
  cloud:
    gateway:
      routes:
        - id: rateLimit_route
          uri: http://localhost:8000
          order: 0
          predicates:
            - Path=/foo/**
          filters:
            - StripPrefix=1
            - name: RateLimiter
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback

hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds: 5000

package org.gateway.controller;

import org.gateway.response.Response;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FallbackController {

    @GetMapping("/fallback")
    public Response fallback() {
        Response response = new Response();
        response.setCode("100");
        response.setMessage("服务暂时不可用");
        return response;
    }
}

注意需要设置commandKey的超时时间。其他的hystrix配置请访问Hystrix wiki.

动态配置路由和过滤器

最后我们来看一下如何动态配置路由和过滤器。

定义路由实体

/**
 * Gateway的路由定义模型
 */
public class GatewayRouteDefinition {

    /**
     * 路由的Id
     */
    private String id;

    /**
     * 路由断言集合配置
     */
    private List<GatewayPredicateDefinition> predicates = new ArrayList<>();

    /**
     * 路由过滤器集合配置
     */
    private List<GatewayFilterDefinition> filters = new ArrayList<>();

    /**
     * 路由规则转发的目标uri
     */
    private String uri;

    /**
     * 路由执行的顺序
     */
    private int order = 0;
}

路由断言实体

/**
 * 路由断言定义模型
 */
public class GatewayPredicateDefinition {

    /**
     * 断言对应的Name
     */
    private String name;

    /**
     * 配置的断言规则
     */
    private Map<String, String> args = new LinkedHashMap<>();}

过滤器实体

/**
 * 过滤器定义模型
 */
public class GatewayFilterDefinition {

    /**
     * Filter Name
     */
    private String name;

    /**
     * 对应的路由规则
     */
    private Map<String, String> args = new LinkedHashMap<>();
}

路由增删改controller

package org.gateway.controller;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.gateway.model.GatewayFilterDefinition;
import org.gateway.model.GatewayPredicateDefinition;
import org.gateway.model.GatewayRouteDefinition;
import org.gateway.route.DynamicRouteServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;

@RestController
@RequestMapping("/route")
public class RouteController {

    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;

    /**
     * 增加路由
     * @param gwdefinition
     * @return
     */
    @PostMapping("/add")
    public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
        try {
            RouteDefinition definition = assembleRouteDefinition(gwdefinition);
            return this.dynamicRouteService.add(definition);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "succss";
    }

    @GetMapping("/delete/{id}")
    public String delete(@PathVariable String id) {
        return this.dynamicRouteService.delete(id);
    }

    @PostMapping("/update")
    public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = assembleRouteDefinition(gwdefinition);
        return this.dynamicRouteService.update(definition);
    }

    private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = new RouteDefinition();
        List<PredicateDefinition> pdList=new ArrayList<>();
        definition.setId(gwdefinition.getId());
        List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
        for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setArgs(gpDefinition.getArgs());
            predicate.setName(gpDefinition.getName());
            pdList.add(predicate);
        }

        List<GatewayFilterDefinition> gatewayFilterDefinitions = gwdefinition.getFilters();
        List<FilterDefinition> filterList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(gatewayFilterDefinitions)) {
            for (GatewayFilterDefinition gatewayFilterDefinition : gatewayFilterDefinitions) {
                FilterDefinition filterDefinition = new FilterDefinition();
                filterDefinition.setName(gatewayFilterDefinition.getName());
                filterDefinition.setArgs(gatewayFilterDefinition.getArgs());
                filterList.add(filterDefinition);
            }
        }
        definition.setPredicates(pdList);
        definition.setFilters(filterList);
        URI uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
        definition.setUri(uri);
        return definition;
    }
}

动态路由service

package org.gateway.route;

import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.gateway.model.GatewayPredicateDefinition;
import org.gateway.model.GatewayRouteDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

import com.alibaba.fastjson.JSON;

import reactor.core.publisher.Mono;

@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;

    /**
     * 增加路由
     * @param definition
     * @return
     */
    public String add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }

    /**
     * 更新路由
     * @param definition
     * @return
     */
    public String update(RouteDefinition definition) {
        try {
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception e) {
            return "update fail,not find route  routeId: "+definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            return "update route  fail";
        }

    }
    /**
     * 删除路由
     * @param id
     * @return
     */
    public String delete(String id) {
        try {
            this.routeDefinitionWriter.delete(Mono.just(id));
            return "delete success";
        } catch (Exception e) {
            e.printStackTrace();
            return "delete fail";
        }

    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}
 上面 routeDefinitionWriter的实现默认是InMemoryRouteDefinitionRepository,将路由存在内存中,我们可以自己实现一个将路由存在redis中的repository。
this.publisher.publishEvent(new RefreshRoutesEvent(this));则会将CachingRouteLocator中的路由缓存清空。
以上只是springcloud gateway支持的一小部分功能。

虽然springcloud gateway 才发布不久,相关的文档还不是很完善,代码中充满了TODO的地方,react代码友好性低。但是由于它的高性能而且是spring自己的框架,未来取代zuul不是没有可能。

原文地址:https://www.cnblogs.com/qianwei/p/10127700.html

时间: 2024-08-30 14:21:30

使用springcloud gateway搭建网关(分流,限流,熔断)的相关文章

Spring Cloud Gateway 结合配置中心限流

前言 假设你领导给你安排了一个任务,具体需求如下: 针对具体的接口做限流 不同接口限流的力度可以不同 可以动态调整限流配置,实时生效 如果你接到上面的任务,你会怎么去设计+实现呢? 每个人看待问题的角度不同,自然思考出来的方案也不同,正所谓条条大路通罗马,能到达目的地的路那就是一条好路. 如何分析需求 下面我给出我的实现方式,仅供各位参考,大牛请忽略. 具体问题具体分析,针对需求点,分别去做分析. 需求一 "如何针对具体的接口做限流" 这个在上篇文章中也有讲过,只需要让KeyResol

.Net微服务实践(四)[网关]:Ocelot限流熔断、缓存以及负载均衡

目录 限流 熔断 缓存 Header转化 HTTP方法转换 负载均衡 注入/重写中间件 后台管理 最后 在上篇.Net微服务实践(三)[网关]:Ocelot配置路由和请求聚合中我们介绍了Ocelot的配置,主要特性路由以及服务聚合.接下来,我们会介绍Ocelot的限流.熔断.缓存以及负载均衡. 限流 我们先来看限流的配置 Reroute节点中的配置如下: { "DownstreamPathTemplate": "/api/orders", "Downstr

.Net Core的API网关Ocelot的使用(二)[负载,限流,熔断,Header转换]

网关的负载均衡 当下游拥有多个节点的时候,我们可以用DownstreamHostAndPorts来配置 { "UpstreamPathTemplate": "/Api_A/{controller}/{action}", "DownstreamPathTemplate": "/api/{controller}/{action}", "DownstreamScheme": "https",

微服务网关常用限流算法

常用算法有三种:计数器算法.漏斗桶算法.令牌桶算法,市面上最常用的是最后一个 第一个:计数器算法  他维护的是单位时间内的最大请求量,因此极端情况可能造成服务抖动  第二个:漏斗桶算法,这种算法保护了后端的微服务,但是会可能造成微服务网关压力激增 第三种:令牌桶算法 令牌桶算法相对于漏斗桶算法,其实就是少了一个输出速率的设置,他与漏斗桶算法相比,主要是为了保护网关自己,由于网关在实际的应用场景中会显得非常关键,因此大部分的限流算法都会选择令牌桶算法 原文地址:https://www.cnblog

spring cloud gateway整合sentinel作网关限流

说明: sentinel可以作为各微服务的限流,也可以作为gateway网关的限流组件. spring cloud gateway有限流功能,但此处用sentinel来作为替待. 说明:sentinel流控可以放在gateway网关端,也可以放在各微服务端. 1,以父工程为基础,创建子工程 2,添加pom依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>sprin

springboot + aop + Lua分布式限流原理解析

一.什么是限流?为什么要限流?不知道大家有没有做过帝都的地铁,就是进地铁站都要排队的那种,为什么要这样摆长龙转圈圈?答案就是为了 限流 !因为一趟地铁的运力是有限的,一下挤进去太多人会造成站台的拥挤.列车的超载,存在一定的安全隐患.同理,我们的程序也是一样,它处理请求的能力也是有限的,一旦请求多到超出它的处理极限就会崩溃.为了不出现最坏的崩溃情况,只能耽误一下大家进站的时间. 限流是保证系统高可用的重要手段!!! 由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动

Spring Cloud微服务安全实战_4-10_用spring-cloud-zuul-ratelimit做限流

本篇讲网关上的限流 用开源项目spring-cloud-zuul-ratelimit 做网关上的限流 (项目github:https://github.com/marcosbarbero/) 1,在网关项目里,引入限流组件的maven依赖: 2,在网关项目yml配置里,配限流相关配置 github也有相关配置说明:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit 限流框架限流需要存一些信息,可以存在数据库里,也可以存在red

分布式系统中的限流与熔断

在应对秒杀.大促.双 11.618 等高性能压力的场景时,限流已经成为了标配技术解决方案,为保证系统的平稳运行起到了关键性的作用.不管应用场景是哪种,限流无非就是针对超过预期的流量,通过预先设定的限流规则选择性的对某些请求进行限流“熔断”. 1. 限流 1.1 单机限流 a>>限制并发量 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.con

Spring Cloud微服务Sentinel+Apollo限流、熔断实战

在Spring Cloud微服务体系中,由于限流熔断组件Hystrix开源版本不在维护,因此国内不少有类似需求的公司已经将眼光转向阿里开源的Sentinel框架.而以下要介绍的正是作者最近两个月的真实项目实践过程,这中间被不少网络Demo示例级别水文误导过,为了以正视听特将实践过程加以总结,希望能够帮到有类似需要的朋友! 一.Sentinel概述 在基于Spring Cloud构建的微服务体系中,服务之间的调用链路会随着系统的演进变得越来越长,这无疑会增加了整个系统的不可靠因素.在并发流量比较高