优秀的编程知识分享平台

网站首页 > 技术文章 正文

SpringCloud Gateway 应用Hystrix 限流功能 自定义Filter详解

nanyue 2024-08-21 19:41:56 技术文章 4 ℃

环境:Springboot2.3.10.RELEASE + SpringCloud Hoxton.SR11 + SpringCloud Alibaba2.2.5.RELEASE + Redis


依赖

<dependencies>
  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
  </dependency>
</dependencies>
  • 工程机构
  • service-consumer子模块接口
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
	
  @Resource
  private RestTemplate restTemplate ;
  @Resource
  private HttpServletRequest request ;
	
  @GetMapping("/get")
  public Object invoke(String serviceName) {
    try {
      TimeUnit.SECONDS.sleep(3) ;
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return restTemplate.getForObject("http://service-producer/discovery/get?serviceName=" + serviceName, Object.class);
  }
	
}
  • service-producer子模块接口
@RestController
@RequestMapping("discovery")
public class DiscoveryController {

  @NacosInjected
  private NamingService namingService;
  @Resource
  private DiscoveryClient discoverClient ;

  @GetMapping(value = "/get")
  public Object get(@RequestParam String serviceName) throws Exception {
    Map<String, Object> res = new HashMap<>() ;
    res.put("services", discoverClient.getServices()) ;
    res.put("instances", discoverClient.getInstances(serviceName)) ;
    res.put("port", 9000) ;
    return res  ;
  }
}

服务发现配置(访问中直接服务名访问)

方式一(不推荐):

spring:
  cloud:
    gateway:
      discovery:
        locator:
          # 如:http://localhost:9999/service-consumer/consumer/get?serviceName=service-producer
          enabled: true 
          lower-case-service-id: true

通过上面的配置,我们可以直接通过服务名(spring.application.name)来访问服务。实际在生产环境应该不会这样用吧,所以这样的功能应该是关闭的。

访问:

方式二:

spring:
  cloud:
    gateway:
      routes:
      - id: gw001
        #当前如果配置的路由被匹配,这里的uri就是被转发的地址
        uri: lb://service-consumer
        # 这里的name(Path)是GatewayPredicate接口实现类对应的前缀。如:PathRoutePredicateFactory => Path
        predicates:
        #下面两种写法都OK。XXX=xxx  这种写法与谓词工厂中的shortcutFieldOrder方法是对应的Pattern是List集合,那么多个配置就用逗号分割。
        - Path=/api/**, /api-a/**, /api-b/**
        filters:
        #截取请求路径的第一段(http://xxx:port/api/demo) 这里会自动的截取掉/api  
        #最后的转发地址为:  http://service-consumer/demo          
        - StripPrefix=1

访问:

这里可以分别通过/api, /api-a, /api-b三个前缀访问。

整合Hystrix

这里需要引入Hystrix依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
spring:
  cloud:
    gateway:
      routes:
      - id: gw001
        #当前如果配置的路由被匹配,这里的uri就是被转发的地址
        uri: lb://service-consumer
        # 这里的name(Path)是GatewayPredicate接口实现类对应的前缀。如:PathRoutePredicateFactory => Path
        predicates:
        #下面两种写法都OK。XXX=xxx  这种写法与谓词工厂中的shortcutFieldOrder方法是对应的Pattern是List集合,那么多个配置就用逗号分割。
        - Path=/api/**, /api-a/**, /api-b/**
        filters:
        - StripPrefix=1
        - name: Hystrix
          args:
            name: gatewayfallback
            fallbackUri: forward:/gateway/error

注意:Hystrix Filter的name必须填写,名称随意。fallbackUri属性是在降级的时候调用的接口。

@RestController
@RequestMapping("/gateway")
public class DefaultController {
	
  @RequestMapping("/error")
  public Object error() {
    Map<String, Object> result = new HashMap<>() ;
    result.put("code", -1) ;
    result.put("message", "服务不可用,请稍候再试") ;
    result.put("data", null) ;
    return result ;
  }
	
}

hysrix默认的熔断时间是1s,所以在service-consumer接口中通过睡眠3s来模拟

访问:

hysrix相关的配置还是按照常规的配置即可。

自定义过滤器

  • 全局过滤器
@Component
public class GlobalExecutionTimeFilter implements GlobalFilter {

  private static Logger logger = LoggerFactory.getLogger(GlobalExecutionTimeFilter.class) ;
	
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		
    long start = System.currentTimeMillis() ;
		
    Mono<Void> result = chain.filter(exchange) ;
		
    logger.info("本次任务执行时间:{}", (System.currentTimeMillis() - start) + "毫秒") ;
    return result ;
  }

}

只需要实现GlobalFilter并且注册成Bean即可。全局过滤器会自动的应用到所有的路由上并且还不同配置任何东西。

访问:

  • 自定义路由过滤器

该过滤器必须配置到具体的路由才能生效。

下面自定义一个验证token的过滤器。

@Component
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.TokenConfig> {
  public static final String ENABLE_KEY = "enable";
  public static final String EXCLUDE_KEY = "exclude" ;

  public TokenGatewayFilterFactory() {
    super(TokenConfig.class);
  }
	
  @Override
  public List<String> shortcutFieldOrder() {
    return Arrays.asList(ENABLE_KEY, EXCLUDE_KEY);
  }

	@Override
	public GatewayFilter apply(TokenConfig config) {
    return (exchange, chain) -> {
      if (!config.isEnable()) {
        return chain.filter(exchange) ;
      }
      ServerHttpRequest request = exchange.getRequest() ;
      ServerHttpResponse response = exchange.getResponse() ;
      String token = request.getHeaders().getFirst("access-token") ;
      if (token == null || "".equals(token)) {
        token = request.getQueryParams().getFirst("access-token") ;
      }
      if (token == null || "".equals(token)) {
        DataBuffer data = setErrorInfo(response, "请检查Token是否填写");
        return response.writeWith(Mono.just(data)) ;
      }
      return chain.filter(exchange) ;
    };
	}

	private DataBuffer setErrorInfo(ServerHttpResponse response, String msg) {
    //设置headers
    HttpHeaders httpHeaders = response.getHeaders();
    httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
    httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
    //设置body
    String body = "{\"code\":-1, \"message\": \"" + msg + "\"}";
    DataBuffer data = response.bufferFactory().wrap(body.getBytes());
    return data;
  }
	
	public static class TokenConfig {
    private boolean enable ;
    private String exclude ;

    public String getExclude() {
      return exclude;
    }

    public void setExclude(String exclude) {
      this.exclude = exclude;
    }

    public boolean isEnable() {
      return enable;
    }

    public void setEnable(boolean enable) {
      this.enable = enable;
    }
  }
	
}

配置:

spring:
  cloud:
    gateway:
      routes:
      - id: gw001
        #当前如果配置的路由被匹配,这里的uri就是被转发的地址
        uri: lb://service-consumer
        # 这里的name(Path)是GatewayPredicate接口实现类对应的前缀。如:PathRoutePredicateFactory => Path
        predicates:
        #下面两种写法都OK。XXX=xxx  这种写法与谓词工厂中的shortcutFieldOrder方法是对应的Pattern是List集合,那么多个配置就用逗号分割。
        - Path=/api/**, /api-a/**, /api-b/**
        filters:
        - StripPrefix=1
        - name: Hystrix
          args:
            name: gatewayfallback
            fallbackUri: forward:/gateway/error
        - name: Token
          args:
            enable: true

访问:

如果请求header中不包含access-token

限流

引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
</dependency>
spring:
  cloud:
    gateway:
      routes:
      - id: gw001
        #当前如果配置的路由被匹配,这里的uri就是被转发的地址
        uri: lb://service-consumer
        # 这里的name(Path)是GatewayPredicate接口实现类对应的前缀。如:PathRoutePredicateFactory => Path
        predicates:
        #下面两种写法都OK。XXX=xxx  这种写法与谓词工厂中的shortcutFieldOrder方法是对应的Pattern是List集合,那么多个配置就用逗号分割。
        - Path=/api/**, /api-a/**, /api-b/**
        filters:
        - StripPrefix=1
        - name: RequestRateLimiter
          args:
            keyResolver: '#{@redisKeyResolver}' #SpringEL表达式
            redis-rate-limiter.replenishRate: 1 #补充率,每秒产生多少令牌
            redis-rate-limiter.burstCapacity: 2 #令牌桶容量
@Component
public class RedisKeyResolver implements KeyResolver {

  // 限流策略,根据IP等信息进行限流,同一IP每秒访问次数
  @Override
  public Mono<String> resolve(ServerWebExchange exchange) {
    String address = exchange.getRequest().getRemoteAddress().getHostString() ;
    return Mono.just(address) ;
  }

}

访问:

当访问过快就会返回429状态码,太多的请求。

完毕!!!

给个关注+转发呗谢谢

关注公众:Springboot实战案例锦集

Spring Cloud Sentinel 熔断降级

Spring Cloud Sentinel 流控限流

Spring Cloud Sentinel 基础配置

Spring Cloud Nacos 开启权限验证

Spring Cloud Gateway应用详解1之谓词

SpringCloud Nacos 整合feign

SpringCloud Alibaba 之 Nacos 服务

Spring Cloud Sentinel整合Feign

SpringCloud Nacos 服务提供者

SpringCloud Nacos 服务动态配置

SpringCloud zuul 动态网关配置

Spring Cloud Gateway应用详解2内置过滤器

SpringCloud Hystrix实现资源隔离应用

最近发表
标签列表