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

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.1.1.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7.  
  8. <properties>
  9. <java.version>1.8</java.version>
  10. <spring-cloud.version>Greenwich.M3</spring-cloud.version>
  11. </properties>
  12.  
  13. <dependencyManagement>
  14. <dependencies>
  15. <dependency>
  16. <groupId>org.springframework.cloud</groupId>
  17. <artifactId>spring-cloud-dependencies</artifactId>
  18. <version>${spring-cloud.version}</version>
  19. <type>pom</type>
  20. <scope>import</scope>
  21. </dependency>
  22. </dependencies>
  23. </dependencyManagement>

添加的依赖包如下

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-gateway</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.cloud</groupId>
  7. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-webflux</artifactId>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
  16. </dependency>

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

路由断言

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

application.yml

  1. server.port: 8082
  2.  
  3. spring:
  4. application:
  5. name: gateway
  6. cloud:
  7. gateway:
  8. routes:
  9. - id: path_route
  10. uri: http://localhost:8000
  11. order: 0
  12. predicates:
  13. - Path=/foo/**
  14. filters:
  15. - 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

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

修改接口返回报文

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

  1. package org.gateway.filter.global;
  2.  
  3. import java.nio.charset.Charset;
  4.  
  5. import org.gateway.response.Response;
  6. import org.reactivestreams.Publisher;
  7. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  8. import org.springframework.cloud.gateway.filter.GlobalFilter;
  9. import org.springframework.core.Ordered;
  10. import org.springframework.core.io.buffer.DataBuffer;
  11. import org.springframework.core.io.buffer.DataBufferFactory;
  12. import org.springframework.core.io.buffer.DataBufferUtils;
  13. import org.springframework.http.server.reactive.ServerHttpResponse;
  14. import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
  15. import org.springframework.stereotype.Component;
  16. import org.springframework.web.server.ServerWebExchange;
  17.  
  18. import com.alibaba.fastjson.JSON;
  19.  
  20. import reactor.core.publisher.Flux;
  21. import reactor.core.publisher.Mono;
  22.  
  23. @Component
  24. public class WrapperResponseFilter implements GlobalFilter, Ordered {
  25. @Override
  26. public int getOrder() {
  27. // -1 is response write filter, must be called before that
  28. return -2;
  29. }
  30.  
  31. @Override
  32. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  33. ServerHttpResponse originalResponse = exchange.getResponse();
  34. DataBufferFactory bufferFactory = originalResponse.bufferFactory();
  35. ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
  36. @Override
  37. public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
  38. if (body instanceof Flux) {
  39. Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
  40. return super.writeWith(fluxBody.map(dataBuffer -> {
  41. // probably should reuse buffers
  42. byte[] content = new byte[dataBuffer.readableByteCount()];
  43. dataBuffer.read(content);
  44. // 释放掉内存
  45. DataBufferUtils.release(dataBuffer);
  46. String rs = new String(content, Charset.forName("UTF-8"));
  47. Response response = new Response();
  48. response.setCode("1");
  49. response.setMessage("请求成功");
  50. response.setData(rs);
  51.  
  52. byte[] newRs = JSON.toJSONString(response).getBytes(Charset.forName("UTF-8"));
  53. originalResponse.getHeaders().setContentLength(newRs.length);//如果不重新设置长度则收不到消息。
  54. return bufferFactory.wrap(newRs);
  55. }));
  56. }
  57. // if body is not a flux. never got there.
  58. return super.writeWith(body);
  59. }
  60. };
  61. // replace response with decorator
  62. return chain.filter(exchange.mutate().response(decoratedResponse).build());
  63. }
  64. }

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

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

认证

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

  1. package org.gateway.filter.global;
  2.  
  3. import java.nio.charset.StandardCharsets;
  4.  
  5. import org.gateway.response.Response;
  6. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  7. import org.springframework.cloud.gateway.filter.GlobalFilter;
  8. import org.springframework.core.io.buffer.DataBuffer;
  9. import org.springframework.http.HttpStatus;
  10. import org.springframework.http.server.reactive.ServerHttpResponse;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.web.server.ServerWebExchange;
  13.  
  14. import com.alibaba.fastjson.JSON;
  15.  
  16. import reactor.core.publisher.Mono;
  17.  
  18. @Component
  19. public class AuthFilter implements GlobalFilter{
  20. @Override
  21. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  22. String token = exchange.getRequest().getHeaders().getFirst("token");
  23. if ("token".equals(token)) {
  24. return chain.filter(exchange);
  25. }
  26. ServerHttpResponse response = exchange.getResponse();
  27. Response data = new Response();
  28. data.setCode("401");
  29. data.setMessage("非法请求");
  30. byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
  31. DataBuffer buffer = response.bufferFactory().wrap(datas);
  32. response.setStatusCode(HttpStatus.UNAUTHORIZED);
  33. response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
  34. return response.writeWith(Mono.just(buffer));
  35. }
  36. }

限流

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

  1. package org.gateway.limiter;
  2.  
  3. import java.nio.charset.StandardCharsets;
  4. import java.util.Map;
  5.  
  6. import org.gateway.response.Response;
  7. import org.springframework.cloud.gateway.filter.GatewayFilter;
  8. import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
  9. import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
  10. import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
  11. import org.springframework.cloud.gateway.route.Route;
  12. import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
  13. import org.springframework.core.io.buffer.DataBuffer;
  14. import org.springframework.http.HttpStatus;
  15. import org.springframework.http.server.reactive.ServerHttpResponse;
  16.  
  17. import com.alibaba.fastjson.JSON;
  18.  
  19. import reactor.core.publisher.Mono;
  20.  
  21. /**
  22. * User Request Rate Limiter filter. See https://stripe.com/blog/rate-limiters and
  23. */
  24. public class RateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<RateLimiterGatewayFilterFactory.Config> {
  25.  
  26. public static final String KEY_RESOLVER_KEY = "keyResolver";
  27.  
  28. private final RateLimiter defaultRateLimiter;
  29. private final KeyResolver defaultKeyResolver;
  30.  
  31. public RateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter,
  32. KeyResolver defaultKeyResolver) {
  33. super(Config.class);
  34. this.defaultRateLimiter = defaultRateLimiter;
  35. this.defaultKeyResolver = defaultKeyResolver;
  36. }
  37.  
  38. public KeyResolver getDefaultKeyResolver() {
  39. return defaultKeyResolver;
  40. }
  41.  
  42. public RateLimiter getDefaultRateLimiter() {
  43. return defaultRateLimiter;
  44. }
  45.  
  46. @SuppressWarnings("unchecked")
  47. @Override
  48. public GatewayFilter apply(Config config) {
  49. KeyResolver resolver = (config.keyResolver == null) ? defaultKeyResolver : config.keyResolver;
  50. RateLimiter<Object> limiter = (config.rateLimiter == null) ? defaultRateLimiter : config.rateLimiter;
  51.  
  52. return (exchange, chain) -> {
  53. Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
  54.  
  55. return resolver.resolve(exchange).flatMap(key ->
  56. // TODO: if key is empty?
  57. limiter.isAllowed(route.getId(), key).flatMap(response -> {
  58.  
  59. for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
  60. exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
  61. }
  62.  
  63. if (response.isAllowed()) {
  64. return chain.filter(exchange);
  65. }
  66. ServerHttpResponse rs = exchange.getResponse();
  67. Response data = new Response();
  68. data.setCode("101");
  69. data.setMessage("访问过快");
  70. byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
  71. DataBuffer buffer = rs.bufferFactory().wrap(datas);
  72. rs.setStatusCode(HttpStatus.UNAUTHORIZED);
  73. rs.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
  74. return rs.writeWith(Mono.just(buffer));
  75. }));
  76. };
  77. }
  78.  
  79. public static class Config {
  80. private KeyResolver keyResolver;
  81. private RateLimiter rateLimiter;
  82. private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;
  83.  
  84. public KeyResolver getKeyResolver() {
  85. return keyResolver;
  86. }
  87.  
  88. public Config setKeyResolver(KeyResolver keyResolver) {
  89. this.keyResolver = keyResolver;
  90. return this;
  91. }
  92. public RateLimiter getRateLimiter() {
  93. return rateLimiter;
  94. }
  95.  
  96. public Config setRateLimiter(RateLimiter rateLimiter) {
  97. this.rateLimiter = rateLimiter;
  98. return this;
  99. }
  100.  
  101. public HttpStatus getStatusCode() {
  102. return statusCode;
  103. }
  104.  
  105. public Config setStatusCode(HttpStatus statusCode) {
  106. this.statusCode = statusCode;
  107. return this;
  108. }
  109. }
  110.  
  111. }

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

  1. package org.gateway.limiter;
  2.  
  3. import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
  4. import org.springframework.web.server.ServerWebExchange;
  5.  
  6. import com.alibaba.fastjson.JSON;
  7.  
  8. import reactor.core.publisher.Mono;
  9.  
  10. public class CustomKeyResolver implements KeyResolver {
  11.  
  12. public static final String BEAN_NAME = "customKeyResolver";
  13.  
  14. @Override
  15. public Mono<String> resolve(ServerWebExchange exchange) {
  16. return Mono.just(getKey(exchange));
  17. }
  18.  
  19. /**
  20. *
  21. * @param exchange
  22. * @return
  23. */
  24. private String getKey(ServerWebExchange exchange) {
  25.  
  26. LimitKey limitKey = new LimitKey();
  27.  
  28. limitKey.setApi(exchange.getRequest().getPath().toString());
  29. limitKey.setBiz(exchange.getRequest().getQueryParams().getFirst("biz"));
  30.  
  31. return JSON.toJSONString(limitKey);
  32. }
  33. }

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

  1. /**
  2. * This uses a basic token bucket algorithm and relies on the fact that Redis scripts
  3. * execute atomically. No other operations can run between fetching the count and
  4. * writing the new count.
  5. */
  6. @Override
  7. public Mono<Response> isAllowed(String routeId, String id) {
  8. if (!this.initialized.get()) {
  9. throw new IllegalStateException("RedisRateLimiter is not initialized");
  10. }
  11.  
  12. LimitConfig limitConfig = getLimitConfig(routeId);
  13.  
  14. if (limitConfig == null || limitConfig.getTokenConfig().size()==0) {
  15. return Mono.just(new Response(true,null));
  16. }
  17.  
  18. Map<String, Config> conf = limitConfig.getTokenConfig();
  19.  
  20. LimitKey limitKey = JSON.parseObject(id, LimitKey.class);
  21. //api限流
  22. String api = limitKey.getApi();
  23. Config apiConf = conf.get(api);
  24. //业务方限流
  25. String biz = limitKey.getBiz();
  26. Config bizConf = conf.get(biz);
  27.  
  28. if (apiConf!=null) {
  29. return isSingleAllow(api,routeId,apiConf).flatMap(res -> {
  30. if (res.isAllowed()) {
  31. if(bizConf!=null) {
  32. return isSingleAllow(biz, routeId, bizConf);
  33. }else {
  34. return Mono.just(new Response(true,new HashMap<>()));
  35. }
  36. }else {
  37. return Mono.just(res);
  38. }
  39. } );
  40. }else {
  41. if (bizConf!=null) {
  42. return isSingleAllow(biz, routeId, bizConf);
  43. }else {
  44. return Mono.just(new Response(true,new HashMap<>()));
  45. }
  46. }
  47. }
  48.  
  49. /**
  50. * 单级限流
  51. * @param api
  52. * @param routeId
  53. * @param apiConf
  54. * @return
  55. */
  56. private Mono<Response> isSingleAllow(String key, String routeId, Config config) {
  57. // How many requests per second do you want a user to be allowed to do?
  58. int replenishRate = config.getReplenishRate();
  59.  
  60. // How much bursting do you want to allow?
  61. int burstCapacity = config.getBurstCapacity();
  62.  
  63. try {
  64. List<String> keys = getKeys(routeId+"$"+key);
  65.  
  66. // The arguments to the LUA script. time() returns unixtime in seconds.
  67. List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
  68. Instant.now().getEpochSecond() + "", "1");
  69. // allowed, tokens_left = redis.eval(SCRIPT, keys, args)
  70. Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
  71. // .log("redisratelimiter", Level.FINER);
  72. return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
  73. .reduce(new ArrayList<Long>(), (longs, l) -> {
  74. longs.addAll(l);
  75. return longs;
  76. }) .map(results -> {
  77. boolean allowed = results.get(0) == 1L;
  78. Long tokensLeft = results.get(1);
  79.  
  80. Response response = new Response(allowed, getHeaders(config, tokensLeft));
  81.  
  82. if (log.isDebugEnabled()) {
  83. log.debug("response: " + response);
  84. }
  85. return response;
  86. });
  87. }
  88. catch (Exception e) {
  89. /*
  90. * We don't want a hard dependency on Redis to allow traffic. Make sure to set
  91. * an alert so you know if this is happening too much. Stripe's observed
  92. * failure rate is 0.01%.
  93. */
  94. log.error("Error determining if user allowed from redis", e);
  95. }
  96. return Mono.just(new Response(true, getHeaders(config, -1L)));
  97. }
  98.  
  99. private LimitConfig getLimitConfig(String routeId) {
  100. Map<String, LimitConfig> map = new HashMap<>();
  101. LimitConfig limitConfig = new LimitConfig();
  102. limitConfig.setRouteId("rateLimit_route");
  103. Map<String, Config> tokenMap = new HashMap<>();
  104. Config apiConfig = new Config();
  105. apiConfig.setBurstCapacity(5);
  106. apiConfig.setReplenishRate(5);
  107.  
  108. Config bizConfig = new Config();
  109. bizConfig.setBurstCapacity(1);
  110. bizConfig.setReplenishRate(1);
  111.  
  112. tokenMap.put("/hello/rateLimit", apiConfig);
  113. tokenMap.put("jieyin", bizConfig);
  114. limitConfig.setTokenConfig(tokenMap);
  115. map.put("rateLimit_route", limitConfig);
  116. return limitConfig;
  117. }

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

bean配置和yml配置如下

  1. @Bean
  2. @Primary
  3. public CustomRedisRateLimiter customRedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
  4. @Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
  5. Validator validator) {
  6. return new CustomRedisRateLimiter(redisTemplate, redisScript, validator);
  7. }
  8.  
  9. @Bean
  10. public RateLimiterGatewayFilterFactory rateLimiterGatewayFilterFactory(CustomRedisRateLimiter customRedisRateLimiter, CustomKeyResolver customKeyResolver) {
  11. return new RateLimiterGatewayFilterFactory(customRedisRateLimiter, customKeyResolver);
  12. }
  1. server.port: 8082
  2.  
  3. spring:
  4. application:
  5. name: gateway
  6. redis:
  7. host: localhost
  8. port: 6379
  9. password: 123456
  10. cloud:
  11. gateway:
  12. routes:
  13. - id: rateLimit_route
  14. uri: http://localhost:8000
  15. order: 0
  16. predicates:
  17. - Path=/foo/**
  18. filters:
  19. - StripPrefix=1
  20. - name: RateLimiter

熔断

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

熔断配置如下:

  1. server.port: 8082
  2.  
  3. spring:
  4. application:
  5. name: gateway
  6. redis:
  7. host: localhost
  8. port: 6379
  9. password: 123456
  10. cloud:
  11. gateway:
  12. routes:
  13. - id: rateLimit_route
  14. uri: http://localhost:8000
  15. order: 0
  16. predicates:
  17. - Path=/foo/**
  18. filters:
  19. - StripPrefix=1
  20. - name: RateLimiter
  21. - name: Hystrix
  22. args:
  23. name: fallbackcmd
  24. fallbackUri: forward:/fallback

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

  1. package org.gateway.controller;
  2.  
  3. import org.gateway.response.Response;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6.  
  7. @RestController
  8. public class FallbackController {
  9.  
  10. @GetMapping("/fallback")
  11. public Response fallback() {
  12. Response response = new Response();
  13. response.setCode("100");
  14. response.setMessage("服务暂时不可用");
  15. return response;
  16. }
  17. }

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

动态配置路由和过滤器

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

定义路由实体

  1. /**
  2. * Gateway的路由定义模型
  3. */
  4. public class GatewayRouteDefinition {
  5.  
  6. /**
  7. * 路由的Id
  8. */
  9. private String id;
  10.  
  11. /**
  12. * 路由断言集合配置
  13. */
  14. private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
  15.  
  16. /**
  17. * 路由过滤器集合配置
  18. */
  19. private List<GatewayFilterDefinition> filters = new ArrayList<>();
  20.  
  21. /**
  22. * 路由规则转发的目标uri
  23. */
  24. private String uri;
  25.  
  26. /**
  27. * 路由执行的顺序
  28. */
  29. private int order = 0;
  30. }

路由断言实体

  1. /**
  2. * 路由断言定义模型
  3. */
  4. public class GatewayPredicateDefinition {
  5.  
  6. /**
  7. * 断言对应的Name
  8. */
  9. private String name;
  10.  
  11. /**
  12. * 配置的断言规则
  13. */
  14. private Map<String, String> args = new LinkedHashMap<>();
    }

过滤器实体

  1. /**
  2. * 过滤器定义模型
  3. */
  4. public class GatewayFilterDefinition {
  5.  
  6. /**
  7. * Filter Name
  8. */
  9. private String name;
  10.  
  11. /**
  12. * 对应的路由规则
  13. */
  14. private Map<String, String> args = new LinkedHashMap<>();
  15. }

路由增删改controller

  1. package org.gateway.controller;
  2.  
  3. import java.net.URI;
  4. import java.util.ArrayList;
  5. import java.util.Arrays;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9.  
  10. import org.gateway.model.GatewayFilterDefinition;
  11. import org.gateway.model.GatewayPredicateDefinition;
  12. import org.gateway.model.GatewayRouteDefinition;
  13. import org.gateway.route.DynamicRouteServiceImpl;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.cloud.gateway.filter.FilterDefinition;
  16. import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
  17. import org.springframework.cloud.gateway.route.RouteDefinition;
  18. import org.springframework.util.CollectionUtils;
  19. import org.springframework.web.bind.annotation.GetMapping;
  20. import org.springframework.web.bind.annotation.PathVariable;
  21. import org.springframework.web.bind.annotation.PostMapping;
  22. import org.springframework.web.bind.annotation.RequestBody;
  23. import org.springframework.web.bind.annotation.RequestMapping;
  24. import org.springframework.web.bind.annotation.RestController;
  25. import org.springframework.web.util.UriComponentsBuilder;
  26.  
  27. @RestController
  28. @RequestMapping("/route")
  29. public class RouteController {
  30.  
  31. @Autowired
  32. private DynamicRouteServiceImpl dynamicRouteService;
  33.  
  34. /**
  35. * 增加路由
  36. * @param gwdefinition
  37. * @return
  38. */
  39. @PostMapping("/add")
  40. public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
  41. try {
  42. RouteDefinition definition = assembleRouteDefinition(gwdefinition);
  43. return this.dynamicRouteService.add(definition);
  44. } catch (Exception e) {
  45. e.printStackTrace();
  46. }
  47. return "succss";
  48. }
  49.  
  50. @GetMapping("/delete/{id}")
  51. public String delete(@PathVariable String id) {
  52. return this.dynamicRouteService.delete(id);
  53. }
  54.  
  55. @PostMapping("/update")
  56. public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
  57. RouteDefinition definition = assembleRouteDefinition(gwdefinition);
  58. return this.dynamicRouteService.update(definition);
  59. }
  60.  
  61. private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
  62. RouteDefinition definition = new RouteDefinition();
  63. List<PredicateDefinition> pdList=new ArrayList<>();
  64. definition.setId(gwdefinition.getId());
  65. List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
  66. for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
  67. PredicateDefinition predicate = new PredicateDefinition();
  68. predicate.setArgs(gpDefinition.getArgs());
  69. predicate.setName(gpDefinition.getName());
  70. pdList.add(predicate);
  71. }
  72.  
  73. List<GatewayFilterDefinition> gatewayFilterDefinitions = gwdefinition.getFilters();
  74. List<FilterDefinition> filterList = new ArrayList<>();
  75. if (!CollectionUtils.isEmpty(gatewayFilterDefinitions)) {
  76. for (GatewayFilterDefinition gatewayFilterDefinition : gatewayFilterDefinitions) {
  77. FilterDefinition filterDefinition = new FilterDefinition();
  78. filterDefinition.setName(gatewayFilterDefinition.getName());
  79. filterDefinition.setArgs(gatewayFilterDefinition.getArgs());
  80. filterList.add(filterDefinition);
  81. }
  82. }
  83. definition.setPredicates(pdList);
  84. definition.setFilters(filterList);
  85. URI uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
  86. definition.setUri(uri);
  87. return definition;
  88. }
  89. }

动态路由service

  1. package org.gateway.route;
  2.  
  3. import java.net.URI;
  4. import java.util.Arrays;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7.  
  8. import org.gateway.model.GatewayPredicateDefinition;
  9. import org.gateway.model.GatewayRouteDefinition;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
  12. import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
  13. import org.springframework.cloud.gateway.route.RouteDefinition;
  14. import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
  15. import org.springframework.context.ApplicationEventPublisher;
  16. import org.springframework.context.ApplicationEventPublisherAware;
  17. import org.springframework.stereotype.Service;
  18. import org.springframework.web.util.UriComponentsBuilder;
  19.  
  20. import com.alibaba.fastjson.JSON;
  21.  
  22. import reactor.core.publisher.Mono;
  23.  
  24. @Service
  25. public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
  26.  
  27. @Autowired
  28. private RouteDefinitionWriter routeDefinitionWriter;
  29.  
  30. private ApplicationEventPublisher publisher;
  31.  
  32. /**
  33. * 增加路由
  34. * @param definition
  35. * @return
  36. */
  37. public String add(RouteDefinition definition) {
  38. routeDefinitionWriter.save(Mono.just(definition)).subscribe();
  39. this.publisher.publishEvent(new RefreshRoutesEvent(this));
  40. return "success";
  41. }
  42.  
  43. /**
  44. * 更新路由
  45. * @param definition
  46. * @return
  47. */
  48. public String update(RouteDefinition definition) {
  49. try {
  50. this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
  51. } catch (Exception e) {
  52. return "update fail,not find route routeId: "+definition.getId();
  53. }
  54. try {
  55. routeDefinitionWriter.save(Mono.just(definition)).subscribe();
  56. this.publisher.publishEvent(new RefreshRoutesEvent(this));
  57. return "success";
  58. } catch (Exception e) {
  59. return "update route fail";
  60. }
  61.  
  62. }
  63. /**
  64. * 删除路由
  65. * @param id
  66. * @return
  67. */
  68. public String delete(String id) {
  69. try {
  70. this.routeDefinitionWriter.delete(Mono.just(id));
  71. return "delete success";
  72. } catch (Exception e) {
  73. e.printStackTrace();
  74. return "delete fail";
  75. }
  76.  
  77. }
  78.  
  79. @Override
  80. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
  81. this.publisher = applicationEventPublisher;
  82. }
  83. }
  1.  上面 routeDefinitionWriter的实现默认是InMemoryRouteDefinitionRepository,将路由存在内存中,我们可以自己实现一个将路由存在redis中的repository
  1. this.publisher.publishEvent(new RefreshRoutesEvent(this));则会将CachingRouteLocator中的路由缓存清空。
  1. 以上只是springcloud gateway支持的一小部分功能。
  2.  
  3. 虽然springcloud gateway 才发布不久,相关的文档还不是很完善,代码中充满了TODO的地方,react代码友好性低。但是由于它的高性能而且是spring自己的框架,未来取代zuul不是没有可能。

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

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

    目录 限流 熔断 缓存 Header转化 HTTP方法转换 负载均衡 注入/重写中间件 后台管理 最后 在上篇.Net微服务实践(三)[网关]:Ocelot配置路由和请求聚合中我们介绍了Ocelot的 ...

  2. Spring Cloud Gateway 结合配置中心限流

    前言 上篇文章我讲过复杂的限流场景可以通过扩展RedisRateLimiter来实现自己的限流策略. 假设你领导给你安排了一个任务,具体需求如下: 针对具体的接口做限流 不同接口限流的力度可以不同 可 ...

  3. Spring Cloud Gateway 扩展支持动态限流

    之前分享过 一篇 <Spring Cloud Gateway 原生的接口限流该怎么玩>, 核心是依赖Spring Cloud Gateway 默认提供的限流过滤器来实现 原生Request ...

  4. .net core使用ocelot---第四篇 限流熔断

    简介 .net core使用ocelot---第一篇 简单使用 .net core使用ocelot---第二篇 身份验证 .net core使用ocelot---第三篇 日志记录 前几篇文章我们陆续介 ...

  5. springBoot整合Sentinel实现降级限流熔断

    由于hystrix的停止更新,以及阿里Sentinel在历年双十一的贡献.项目中使用了Sentinel,今天我们来讲讲Sentinel的入门教程,本文使用1.6.3版本进行讲解 本文通过Sentine ...

  6. Hystrix介绍以及服务的降级限流熔断

    (dubbo熔断,Hystrix问的少) 无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源.作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang (挂起)在这个资源上, ...

  7. DBPack 限流熔断功能发布说明

    上周我们发布了 v0.4.0 版本,增加了限流熔断功能,现对这两个功能做如下说明. 限流 DBPack 限流熔断功能通过 filter 实现.要设置限流规则,首先要定义 RateLimitFilter ...

  8. Spring Cloud alibaba网关 sentinel zuul 四 限流熔断

    spring cloud alibaba 集成了 他内部开源的 Sentinel 熔断限流框架 Sentinel 介绍 官方网址 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentine ...

  9. 深入学习spring cloud gateway 限流熔断

    前言 Spring Cloud Gateway 目前,Spring Cloud Gateway是仅次于Spring Cloud Netflix的第二个最受欢迎的Spring Cloud项目(就GitH ...

随机推荐

  1. C++ const 和static的总结以及使用

    一  static的使用 (作用域和存储方式) 1.作用域---------->隐藏 静态函数跟静态全局变量的作用类似 (静态函数不能被其它文件所用: 其它文件中可以定义相同名字的函数,不会发生 ...

  2. httprunner 使用总结

    HttpRunner 概念 HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试.性能测试.线上监控.持续集成等多种测试 ...

  3. Android之AppWidget

    1.Widget设计步骤 需要修改三个XML,一个class: 1)第一个xml是布局XML文件(如:main.xml),是这个widget的.一般来说如果用这个部件显示时间,那就只在这个布局XML中 ...

  4. webpack的安装与使用(单文件)

    在安装 Webpack 前,你本地环境必须已安装nodejs. 可以使用npm安装,当然由于 npm 安装速度慢,也可以使用淘宝的镜像及其命令cnpm(npm install -g cnpm --re ...

  5. 高仿IOS下拉刷新的粘虫效果

    最近看需要做一款下拉刷新的效果,由于需要和Ios界面保持一致,所以这用安卓的方式实现了ios下的下拉刷新的粘虫效果. 最新的安卓手机版本的QQ也有这种类似的效果,就是拖动未读信息的那个红色圆圈,拖动近 ...

  6. eclispe中使用python库 pyswip 进行prolog编程

    from pyswip import Prolog prolog = Prolog() prolog.assertz("father(michael,john)") prolog. ...

  7. 下载apache-tomcat-9.0.17-windows-x64及安装以及用途

    首先我们先去这个网站下载http://www.apache.org/,进入Tomcat,点击Tomcat9 下载64-bit Windows_zip 当我们下载好了之后解压,把多余的文件删除掉,也可以 ...

  8. django框架--路由系统

    目录 一.路由系统理解 二.路由系统功能划分 三.路由表创建 创建工具 二级路由 路由别名 动态路由及重定向 四.自定义错误页面 五.图示路由系统在框架中的定位 六.路由系统的进阶想法 一.路由系统理 ...

  9. Spring Security构建Rest服务-1400-授权

    安全分为 认证和授权,前边讲的都是认证,现在说授权. 前端业务系统的权限简单些,一般只区分是否登录,复杂点的还会区分 VIP用户等简单的角色,权限规则基本不变. 后台系统比较复杂,角色众多,权限随着业 ...

  10. Android 开发工具类 31_WebService 获取手机号码归属地

    AndroidInteractWithWebService.xml <?xml version="1.0" encoding="utf-8"?> & ...