使用 resilience4j 实现海量访客 IP 的限流

关于使用 resilience4j 实现基于访客 IP 的限流,网上已经有很多文章了,我这里就不再赘述。

本文主要是要解决一个问题:如果访客量很大,会占用太多的内存来存放限流对象。

假设一个系统每天有上百万的访客,而基于 IP 的限流策略会导致 resilience4j 中用来存放每个 IP 对应的限流对象的哈希表巨大无比。

下面是限流的策略:

//每分钟最多访问100次
RateLimiterConfig config = RateLimiterConfig.custom()
        .limitRefreshPeriod(Duration.ofMinutes(1))
        .limitForPeriod(100)
        .timeoutDuration(Duration.ofMillis(0))
        .build();
var registry = RateLimiterRegistry.of(config);

然后每来一个请求,就调用 registry.rateLimiter (<ip>) 方法做进一步限流,当这个 IP 量非常大时,所有这些信息都存在内存中,会导致内存占用膨胀太厉害。

所以我引入了 J2Cache 来做 rateLimiter 的缓存,内存中设置固定的数量,确保不会把所有的对象都塞到内存中。

写了一个新的类 CacheRateLimiterRegistry,直接从 InMemoryRateLimiterRegistry 继承而来:

package com.xxx.security;

import com.xxx.cache.CacheMgr;
import io.github.resilience4j.core.ConfigurationNotFoundException;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.ratelimiter.internal.AtomicRateLimiter;
import io.github.resilience4j.ratelimiter.internal.InMemoryRateLimiterRegistry;
import io.vavr.collection.Map;

import java.util.Objects;
import java.util.function.Supplier;

/**
 * 使用缓存来管理 RateLimiter 实例,解决的问题:
 * 1. 分布式环境下的使用(暂时还无法共享状态)
 * 2. 避免内存过载
 */
public class CacheRateLimiterRegistry extends InMemoryRateLimiterRegistry {

    private final static String REGION = "rateLimiter";
    private String catalog;

    public CacheRateLimiterRegistry(String catalog, RateLimiterConfig defaultConfig) {
        super(defaultConfig);
        this.catalog = catalog;
    }

    @Override
    public RateLimiter rateLimiter(String name, RateLimiterConfig rateLimiterConfig, Map<String, String> tags) {
        String key = catalog + name;
        return CacheMgr.get(REGION, key, () -> new AtomicRateLimiter(name,
                Objects.requireNonNull(rateLimiterConfig, CONFIG_MUST_NOT_BE_NULL), getAllTags(tags)));
    }

    @Override
    public RateLimiter rateLimiter(String name, Supplier<RateLimiterConfig> rateLimiterConfigSupplier, Map<String, String> tags) {
        String key = catalog + name;
        return CacheMgr.get(REGION, key, () -> new AtomicRateLimiter(name, Objects.requireNonNull(
                Objects.requireNonNull(rateLimiterConfigSupplier, SUPPLIER_MUST_NOT_BE_NULL).get(),
                CONFIG_MUST_NOT_BE_NULL), getAllTags(tags)));
    }

    @Override
    public RateLimiter rateLimiter(String name, String configName, Map<String, String> tags) {
        String key = catalog + name;
        return CacheMgr.get(REGION, key, () -> RateLimiter.of(name, getConfiguration(configName)
                .orElseThrow(() -> new ConfigurationNotFoundException(configName)), getAllTags(tags)));
    }
}

使用方法如下:

var registry = new CacheRateLimiterRegistry("prefix", config);

上述的 prefix 主要是为不同的限流场景提供一个前缀标识而已。

CacheMgr 类很简单,只是对 J2Cache 做一个简单封装:

package com.xxx.cache;

import net.oschina.j2cache.CacheChannel;
import net.oschina.j2cache.CacheException;
import net.oschina.j2cache.J2CacheBuilder;
import net.oschina.j2cache.J2CacheConfig;

import java.io.IOException;
import java.util.function.Supplier;

/**
 * Cache manager
 */
public class CacheMgr {

    private static final String CONFIG_FILE = "/cache.properties";

    private final static J2CacheBuilder builder;
    private final static CacheChannel cache;

    static {
        try {
            J2CacheConfig config = J2CacheConfig.initFromConfig(CONFIG_FILE);
            builder = J2CacheBuilder.init(config);
            cache = builder.getChannel();
        } catch (IOException e) {
            throw new CacheException("Failed to load j2chache config file" + CONFIG_FILE, e);
        }
    }


    public static <T> T get(String region, String key, Supplier<T> loader) {
        return (T) cache.get(region, key, k -> loader.get(), true).getValue();
    }

    public static boolean exists(String region, String key) {
        return cache.exists(region, key);
    }

    public static void set(String region, String key, Object value) {
        cache.set(region, key, value);
    }

    public static void evict(String region, String... keys) {
        cache.evict(region, keys);
    }

    public static void close() {
        cache.close();
    }

}

完事在 j2cache 的一级缓存定义中增加一个 rateLimiter 的 region 即可,想要设置内存中的对象数量就在这个文件中配置即可。

你可以通过在Feign客户端上添加Resilience4j的拦截器来实现熔断逻辑。下面是一些步骤来实现这个过程: 1. 首先,确保你已经添加了Resilience4j的依赖到你的项目中。你可以在Maven或者Gradle配置文件中添加以下依赖: ```xml <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-cloud2</artifactId> <version>1.6.1</version> </dependency> ``` 2. 创建一个实现Feign的RequestInterceptor接口的类,用于拦截Feign的请求。以下是一个示例: ```java import feign.RequestInterceptor; import feign.RequestTemplate; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class Resilience4jFeignInterceptor implements RequestInterceptor { private final CircuitBreakerRegistry circuitBreakerRegistry; @Autowired public Resilience4jFeignInterceptor(CircuitBreakerRegistry circuitBreakerRegistry) { this.circuitBreakerRegistry = circuitBreakerRegistry; } @Override public void apply(RequestTemplate requestTemplate) { String serviceName = requestTemplate.feignTarget().name(); circuitBreakerRegistry.circuitBreaker(serviceName).executeRunnable(() -> { // 在这里执行你的Feign请求 requestTemplate.header("Authorization", "Bearer your-token"); }); } } ``` 3. 在你的Feign客户端接口上添加`configuration`属性,使用上述的拦截器类。例如: ```java import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(name = "your-service-name", configuration = Resilience4jFeignInterceptor.class) public interface YourFeignClient { @GetMapping("/your-endpoint") String yourFeignMethod(); } ``` 这样,当你使用`YourFeignClient`接口的方法发送请求时,请求将会通过Resilience4j的断路器进行拦截和熔断处理。 请注意,上面的示例中使用了`CircuitBreakerRegistry`来获取相应的断路器实例。你需要根据你的实际需求进行配置和使用Resilience4j的断路器。另外,你还可以根据需要添加其他的Resilience4j功能,如限流、重试等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自橙一派

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值