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

原创
2022/11/19 22:50
阅读数 4.6K

关于使用 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 即可,想要设置内存中的对象数量就在这个文件中配置即可。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部