11

为Spring Cloud Gateway加上全局过滤器,并使用其内部自带的令牌桶算法实现(Redis)来...

 4 years ago
source link: https://www.skypyb.com/2019/10/jishu/1130/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

为Spring Cloud Gateway加上全局过滤器,并使用其内部自带的令牌桶算法实现(Redis)来进行限流

既然是一个网关。那么全局过滤器肯定是少不了的一个存在。像是鉴权、认证啥的不可能每个服务都做一次,一般都是在网关处就搞定了。

Zuul他就有很强大的过滤器体系来给人使用。

Gateway当然也不会差这么点东西。

对于SpringCloud体系来说,一切的实现都是那么的简单。那么废话不多说,直接开始写起来。

Gateway内部有一个接口 名为GlobalFilter,这个就是Gateway的全局过滤器接口,只要在应用中实现此接口后注册为Spring的Bean,背后就会帮你将这个实现注册到全局过滤器链条里边去。

我这里就简单的写了个模拟鉴权的过滤器实现:

@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        //不为空则通过
        if (!StringUtils.isEmpty(token)) return chain.filter(exchange);
        ServerHttpResponse response = exchange.getResponse();
        // 封装错误信息
        Map<String, Object> responseData = Maps.newHashMapWithExpectedSize(3);
        responseData.put("code", HttpStatus.UNAUTHORIZED.value());
        responseData.put("message", "Token is empty");
        responseData.put("cause", "Token is empty");
        // 将信息转换为 JSON
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] data = new byte[0];
            data = objectMapper.writeValueAsBytes(responseData);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        // 返回错误信息json
        DataBuffer buffer = response.bufferFactory().wrap(data);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    //最后执行
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;

虽说是鉴权,但实际上我这就是个简单的demo而已。想知道真正的Spring Security鉴权/认证怎么写?

我以前写的这个: http://skypyb.com/2019/09/jishu/kj/1050/ 应该可以帮助你。

看我写的这个过滤器内部实现哈,其实就是拿出Request Header中的 Authorization字段(token) 然后判断是否存在。不存在就返回错误,存在就交给链条中的下一个过滤器。

过滤器其实也没啥好说的,那么说说限流。

关于限流这个东西,常见的算法就是漏桶和令牌桶了,对于一个应用单机限流来说也复杂不到哪儿去。

靠着google guava包里的RateLimiter工具都能搞定大多数场景了。

不过既然人家Gateway好心好意给你搞了个限流的实现。那么还是尊重他用一下。

由于Gateway是用的Redis和lua脚本实现了令牌桶的算法,那么先导入几个需要的依赖:

        <!--Redis begin-->
        <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>
        <!--Redis end-->

既然是Redis,那还是先配一下Redis序列化先:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;

万事俱备,开始进行限流的具体实现了。

既然是限流,那么也得有个限流策略

是根据用户来限流呢?还是说根据请求路径限流?或者是IP限流?

不过这个都是由需求来决定了,我这就简单的写个根据IP来限流的。

人家也给你封装完毕了,只需要你自己实现KeyResolver这个接口就可以。

由于实现这个一般来说也就一行代码,所以我就不写个单独的类去实现了,而是直接写在配置类里边。

@Configuration
public class GatewayRateLimiterConfig {
     * Gateway通过内置的RequestRateLimiter过滤器实现限流,用的是令牌桶算法,借助Redis保存中间数据
     * 这里自定义一个KeyResolver
     * 作用是对来源ip进行限流
    @Bean(value = "ipKeyResolver")
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());

看,我其实只需要返回我需要限制的东西就可以了。我这里提取到用户的IP将其返回,即可实现通过ip来进行限流的策略。

不过限流相关的配置写了,那也得用起来。

这个怎么用起来? 其实直接在配置文件里配置就OK了

spring:
  application:
    # 应用名称
    name: sc-demo-alibaba-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.3.105:8848 #注册进nacos
    # 使用 Sentinel 作为熔断器
    sentinel:
      transport:
        port: 18102
        dashboard: 192.168.3.105:8858
    # 路由网关配置
    gateway:
      # 这里是设置与服务注册发现组件结合,这样可以采用服务名的路由策略
      discovery:
        locator:
          enabled: true
      # 配置路由规则
      routes:
        - id: ROUTER#sc-demo-alibaba-consumer #这个是路由ID,需要保证在所有路由定义中唯一,值随便写就是了
          # 采用 LoadBalanceClient 方式请求,以 lb:// 开头,后面的是注册在 Nacos 上的服务名
          uri: lb://sc-demo-alibaba-consumer
          predicates:
            # Method ,这里是匹配 GET 和 POST 请求
            - Method=GET,POST
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.burstCapacity: 20
                redis-rate-limiter.replenishRate: 5
                key-resolver: '#{@ipKeyResolver}'
        - id: ROUTER#sc-demo-alibaba-provider
          uri: lb://sc-demo-alibaba-provider
          predicates:
            - Method=GET,POST
  #Redis配置
  redis:
    host: 192.168.3.105
    port: 6379
    #Redis连接池配置
    jedis:
      pool:
        min-idle: 0
        max-idle: 8
        max-active: 8
        max-wait: -1ms
server:
  port: 8888
feign:
  sentinel:
    enabled: true
management:
  endpoints:
      exposure:
        include: "*"
# 配置日志级别,方别调试
logging:
  level:
    org.springframework.cloud.gateway: debug

这里可以看到,除了Redis配置 ( spring.redis.** )以外。

主要就是对于Getway的配置。

在Gateway路由配置中,设置了一个filters参数。

这个是为了指定路由的各种过滤器的。这个参数也有很多种,可以参考官方讲解: https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html#gateway-route-filters

我这就是指定了一个RequestRateLimiter,请求限流。

redis-rate-limiter.burstCapacity: 20     这个参数表示突发容量,即每秒可以最大通过多少次请求

redis-rate-limiter.replenishRate: 5       这个是令牌桶的补充速度,每秒往桶里边放几个令牌

key-resolver: ‘#{@ipKeyResolver}’             这个就是用上KeyResolver的具体实现了,这里用spel表达式指定我写的那个ip限制类

准备好之后将应用启动就完事了,想测的话可以用jmeter测测看,或者将请求限制写的更小一点,在网页上狂按f5也行。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK