Gateway Limiter

Alternatives To Gateway Limiter
Project NameStarsDownloadsRepos Using ThisPackages Using ThisMost Recent CommitTotal ReleasesLatest ReleaseOpen IssuesLicenseLanguage
Spring Cloud Alibaba25,106717 hours ago31September 15, 2022419apache-2.0Java
Spring Cloud Alibaba provides a one-stop solution for application development for the distributed solutions of Alibaba middleware.
Springall24,478
5 months ago26mitJava
循序渐进,学习Spring Boot、Spring Boot & Shiro、Spring Batch、Spring Cloud、Spring Cloud Alibaba、Spring Security & Spring Security OAuth2,博客Spring系列源码:https://mrbird.cc
Kuboard Press17,575
12 days ago330JavaScript
Kuboard 是基于 Kubernetes 的微服务管理界面。同时提供 Kubernetes 免费中文教程,入门教程,最新版本的 Kubernetes v1.23.4 安装手册,(k8s install) 在线答疑,持续更新。
Springcloudlearning15,253
2 years ago45Java
《史上最简单的Spring Cloud教程源码》
Springboot Learning14,710
6 months ago63Java
《Spring Boot基础教程》,2.x版本持续连载中!点击下方链接直达教程目录!
Mall Learning11,780
24 days ago24apache-2.0Java
mall学习教程,架构、业务、技术要点全方位解析。mall项目(50k+star)是一套电商系统,使用现阶段主流技术实现。涵盖了SpringBoot 2.3.0、MyBatis 3.4.6、Elasticsearch 7.6.2、RabbitMQ 3.7.15、Redis 5.0、MongoDB 4.2.5、Mysql5.7等技术,采用Docker容器化部署。
Mall Swarm9,716
3 months ago35apache-2.0Java
mall-swarm是一套微服务商城系统,采用了 Spring Cloud 2021 & Alibaba、Spring Boot 2.7、Oauth2、MyBatis、Docker、Elasticsearch、Kubernetes等核心技术,同时提供了基于Vue的管理后台方便快速搭建系统。mall-swarm在电商业务的基础集成了注册中心、配置中心、监控中心、网关等系统功能。文档齐全,附带全套Spring Cloud教程。
Activiti9,2911,9696021 hours ago50February 06, 2020531apache-2.0Java
Activiti is a light-weight workflow and Business Process Management (BPM) Platform targeted at business people, developers and system admins. Its core is a super-fast and rock-solid BPMN 2 process engine for Java. It's open-source and distributed under the Apache license. Activiti runs in any Java application, on a server, on a cluster or in the cloud. It integrates perfectly with Spring, it is extremely lightweight and based on simple concepts.
Awesome Architecture8,359
2 years ago7
架构师技术图谱,助你早日成为架构师
Springcloud Learning7,097
a month ago22Java
Spring Cloud基础教程,持续连载更新中
Alternatives To Gateway Limiter
Select To Compare


Alternative Project Comparisons
Readme

  • SpringCloud Gateway redisSpring Cloud Gateway RequestRateLimiterGatewayFilterFactoryRedislua. filterFactoryRateLimiterKeyResolver,KeyResolverrequestkey, RateLimiterkey.

  • sentinel
  1. ,
  • gateway

spring cloud gateway

org.springframework.cloud.gateway.filter.factory#RequestRateLimiterGatewayFilterFactory

  1. RequestRateLimiterGatewayFilterFactory#apply,GatewayFilterfilter

  2. KeyResolverredisKeyResolver denyEmptyfalsetrue,false

  3. limiter.isAllowedlimiterorg.springframework.cloud.gateway.config#redisRateLimitergateway

public GatewayFilter apply(Config config) {
		KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
		RateLimiter<Object> limiter = getOrDefault(config.rateLimiter,
				defaultRateLimiter);
		boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
		HttpStatusHolder emptyKeyStatus = HttpStatusHolder
				.parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));

		return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY)
				.flatMap(key -> {
					if (EMPTY_KEY.equals(key)) {
						if (denyEmpty) {
							setResponseStatus(exchange, emptyKeyStatus);
							return exchange.getResponse().setComplete();
						}
						return chain.filter(exchange);
					}
					return limiter.isAllowed(config.getRouteId(), key)
							.flatMap(response -> {

								for (Map.Entry<String, String> header : response
										.getHeaders().entrySet()) {
									exchange.getResponse().getHeaders()
											.add(header.getKey(), header.getValue());
								}

								if (response.isAllowed()) {
									return chain.filter(exchange);
								}

								setResponseStatus(exchange, config.getStatusCode());
								return exchange.getResponse().setComplete();
							});
				});
	}

org.springframework.cloud.gateway.filter.factory#GatewayRedisAutoConfiguration

  1. redisRequestRateLimiterScript,request_rate_limiter.luaredis gatewaylua
  2. stringReactiveRedisTemplatereactiveredis
  3. [email protected] starter
class GatewayRedisAutoConfiguration {

	@Bean
	@SuppressWarnings("unchecked")
	public RedisScript redisRequestRateLimiterScript() {
		DefaultRedisScript redisScript = new DefaultRedisScript<>();
		redisScript.setScriptSource(new ResourceScriptSource(
				new ClassPathResource("META-INF/scripts/request_rate_limiter.lua")));
		redisScript.setResultType(List.class);
		return redisScript;
	}

	@Bean
	// TODO: replace with ReactiveStringRedisTemplate in future
	public ReactiveRedisTemplate<String, String> stringReactiveRedisTemplate(
			ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
		RedisSerializer<String> serializer = new StringRedisSerializer();
		RedisSerializationContext<String, String> serializationContext = RedisSerializationContext
				.<String, String>newSerializationContext().key(serializer)
				.value(serializer).hashKey(serializer).hashValue(serializer).build();
		return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory,
				serializationContext);
	}

	@Bean
	@ConditionalOnMissingBean
	public RedisRateLimiter redisRateLimiter(
			ReactiveRedisTemplate<String, String> redisTemplate,
			@Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
			Validator validator) {
		return new RedisRateLimiter(redisTemplate, redisScript, validator);
	}

}

org.springframework.cloud.gateway.filter.factory#RedisRateLimiter

  1. RedisRateLimiter#isAllowed,routeIdgatewayrouteIdidKeyResolver getKeystokenKeykeyList
public Mono<Response> isAllowed(String routeId, String id) {
		if (!this.initialized.get()) {
			throw new IllegalStateException("RedisRateLimiter is not initialized");
		}

		Config routeConfig = loadConfiguration(routeId);

		// How many requests per second do you want a user to be allowed to do?
		// qps
		int replenishRate = routeConfig.getReplenishRate();

		// How much bursting do you want to allow?
		// 
		int burstCapacity = routeConfig.getBurstCapacity();

		try {
			List<String> keys = getKeys(id);

			// The arguments to the LUA script. time() returns unixtime in seconds.
			//LUA 1lua
			List<String> scriptArgs = Arrays.asList(replenishRate + "",
					burstCapacity + "", Instant.now().getEpochSecond() + "", "1");
			// allowed, tokens_left = redis.eval(SCRIPT, keys, args)
			// evalevalsharedis2.6luaRedisLuaLua
			Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys,
					scriptArgs);
			// .log("redisratelimiter", Level.FINER);
			return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
					.reduce(new ArrayList<Long>(), (longs, l) -> {
						longs.addAll(l);
						return longs;
					}).map(results -> {
					    //lua
						boolean allowed = results.get(0) == 1L;
						Long tokensLeft = results.get(1);

						Response response = new Response(allowed,
								getHeaders(routeConfig, tokensLeft));

						if (log.isDebugEnabled()) {
							log.debug("response: " + response);
						}
						return response;
					});
		}
		catch (Exception e) {
			/*
			 * We don't want a hard dependency on Redis to allow traffic. Make sure to set
			 * an alert so you know if this is happening too much. Stripe's observed
			 * failure rate is 0.01%.
			 */
			log.error("Error determining if user allowed from redis", e);
		}
		return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
	}
	
	static List<String> getKeys(String id) {
    		// use `{}` around keys to use Redis Key hash tags
    		// this allows for using redis cluster
    
    		// Make a unique key per user.
    		String prefix = "request_rate_limiter.{" + id;
    
    		// You need two Redis keys for Token Bucket.
    		String tokenKey = prefix + "}.tokens";
    		String timestampKey = prefix + "}.timestamp";
    		return Arrays.asList(tokenKey, timestampKey);
    }

lua

-- tokenkey
local tokens_key = KEYS[1]
-- key
local timestamp_key = KEYS[2]
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)

-- 
local rate = tonumber(ARGV[1])
-- 
local capacity = tonumber(ARGV[2])
-- 
local now = tonumber(ARGV[3])
-- 
local requested = tonumber(ARGV[4])

-- 
local fill_time = capacity/rate
-- redis  
-- 10s9s
local ttl = math.floor(fill_time*2)

-- 
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
    last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)
-- 0
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
    last_refreshed = 0
end
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)
-- 
local delta = math.max(0, now-last_refreshed)
-- 
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
-- 
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
-- false
local allowed_num = 0
-- 
if allowed then
    new_tokens = filled_tokens - requested
    allowed_num = 1
end

-- redis
redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)
--  luajavaList
return { allowed_num, new_tokens }

RateLimiterConf

@Component
@ConfigurationProperties(prefix = "ratelimiter-conf")
public class RateLimiterConf {
    //
    private static final String DEFAULT_REPLENISHRATE = "default.replenishRate";
    //
    private static final String DEFAULT_BURSTCAPACITY = "default.burstCapacity";

    private Map<String, Integer> rateLimitMap = new ConcurrentHashMap<String, Integer>() {
        {
            put(DEFAULT_REPLENISHRATE, 100);
            put(DEFAULT_BURSTCAPACITY, 1000);
        }
    };

    public Map<String, Integer> getRateLimitMap() {
        return rateLimitMap;
    }

    public void setRateLimitMap(Map<String, Integer> rateLimitMap) {
        this.rateLimitMap = rateLimitMap;
    }
}

SystemRedisRateLimiter,isAllowed

 @Override
    public Mono<Response> isAllowed(String routeId, String id) {
        if (!this.initialized.get()) {
            throw new IllegalStateException("RedisRateLimiter is not initialized");
        }
        if (ObjectUtils.isEmpty(rateLimiterConf)) {
            throw new IllegalArgumentException("No Configuration found for route " + routeId);
        }
        Map<String, Integer> rateLimitMap = rateLimiterConf.getRateLimitMap();
        //key
        String replenishRateKey = routeId + "." + id + "." + REPLENISH_RATE_KEY;

        int replenishRate = ObjectUtils.isEmpty(rateLimitMap.get(replenishRateKey)) ? rateLimitMap.get(DEFAULT_REPLENISHRATE) : rateLimitMap.get(replenishRateKey);
        //key
        String burstCapacityKey = routeId + "." + id + "." + BURST_CAPACITY_KEY;

        int burstCapacity = ObjectUtils.isEmpty(rateLimitMap.get(burstCapacityKey)) ? rateLimitMap.get(DEFAULT_BURSTCAPACITY) : rateLimitMap.get(burstCapacityKey);

        try {
            List<String> keys = getKeys(id);

            List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
                    Instant.now().getEpochSecond() + "", "1");
            Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);

            return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
                    .reduce(new ArrayList<Long>(), (longs, l) -> {
                        longs.addAll(l);
                        return longs;
                    }).map(results -> {
                        boolean allowed = results.get(0) == 1L;
                        Long tokensLeft = results.get(1);

                        RateLimiter.Response response = new RateLimiter.Response(allowed, getHeaders(replenishRate, burstCapacity, tokensLeft));

                        return response;
                    });
        } catch (Exception e) {
            e.printStackTrace();
        }

        return Mono.just(new RateLimiter.Response(true, getHeaders(replenishRate, burstCapacity, -1L)));
    }                             

  • KeyResolverSystemRedisRateLimiter
@Bean
    KeyResolver sysKeyResolver() {
        return exchange -> {
            List<String> openAPiToken = exchange.getRequest().getHeaders().get("X-Open-Api-Token");
            if (CollectionUtils.isEmpty(openAPiToken)) {
                return Mono.just("____EMPTY_KEY__");
            }
            Optional<String> pathOptional = Optional.of(exchange.getRequest().getPath().toString());
            String path="";
            if (pathOptional.isPresent()) {
                path = pathOptional.get().substring(1).replace("/","-");

            }
            return Mono.just(openAPiToken.get(0) + "." + path);
        };
    }

    @Bean
    @Primary
    SystemRedisRateLimiter systemRedisRateLimiter(
            ReactiveRedisTemplate<String, String> redisTemplate,
            @Qualifier(SystemRedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> script,
            Validator validator) {
        return new SystemRedisRateLimiter(redisTemplate, script, validator);
    }

server.port: 8081

spring:
  application:
    name: gateway
  redis:
    host: localhost
    port: 6379
  cloud:
    gateway:
      routes:
      - id: rateLimit_route
        uri: http://localhost:8088/hello
        order: 0
        predicates:
        - Path=/rateLimit/**
        filters:
        #filterRequestRateLimiter
        - name: RequestRateLimiter
          args:
            rate-limiter: "#{@systemRedisRateLimiter}"
            key-resolver: "#{@sysKeyResolver}"
ratelimiter-conf:
  #RateLimiterConf
  rateLimitMap:
    #routeid(gateway).token+path.replenishRate()/burstCapacity
    rateLimit_route.t1.rateLimit-hello.replenishRate: 1
    rateLimit_route.t1.rateLimit-hello.burstCapacity: 5

logging:
  level:
    #org.springframework.cloud.gateway: debug
    com.batman.gateway: debug
  • headert1
  curl http://localhost:8081/rateLimit/hello

  1. api https://www.cnblogs.com/qianwei/p/10127700.html
Popular Cloud Computing Projects
Popular Spring Projects
Popular Cloud Computing Categories

Get A Weekly Email With Trending Projects For These Categories
No Spam. Unsubscribe easily at any time.
Java
Lua
Cloud Computing
Spring
Rating
Gateway
Spring Cloud