forked from wangzheng0822/ratelimiter4j
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4c18cd9
commit 0dceb4e
Showing
111 changed files
with
6,718 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
apply plugin: 'java' | ||
apply plugin: "jacoco" | ||
|
||
sourceCompatibility = '1.8' | ||
targetCompatibility = '1.8' | ||
|
||
tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } | ||
|
||
group 'com.eudemon' | ||
version '0.1.7-SNAPSHOT' | ||
|
||
jacoco { | ||
toolVersion = "0.8.1" | ||
reportsDir = file("$buildDir/customJacocoReportDir") | ||
} | ||
|
||
jacocoTestReport { | ||
reports { | ||
xml.enabled false | ||
csv.enabled false | ||
html.destination file("${buildDir}/jacocoHtml") | ||
} | ||
} | ||
|
||
configurations { | ||
all*.exclude group: 'ch.qos.logback' | ||
all*.exclude module: 'slf4j-log4j12' | ||
all*.exclude module: 'slf4j-simple' | ||
all*.exclude group: 'log4j', module: 'log4j' | ||
} | ||
|
||
test { | ||
useTestNG() | ||
finalizedBy jacocoTestReport | ||
} | ||
|
||
repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' | ||
} } | ||
|
||
dependencies { | ||
compile 'org.apache.curator:curator-recipes:4.0.0' | ||
compile 'org.slf4j:slf4j-api:1.7.25' | ||
compile 'redis.clients:jedis:2.9.0' | ||
compile 'commons-codec:commons-codec:1.10' | ||
compile 'org.apache.commons:commons-lang3:3.7' | ||
compile 'org.yaml:snakeyaml:1.17' | ||
compile 'com.google.guava:guava:23.0' | ||
compile 'com.fasterxml.jackson.core:jackson-core:2.9.6' | ||
compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.6' | ||
compile 'com.fasterxml.jackson.core:jackson-databind:2.9.6' | ||
|
||
testCompile 'org.testng:testng:6.14.3' | ||
testCompile 'org.hamcrest:hamcrest-library:1.3' | ||
testCompile 'org.hamcrest:hamcrest-core:1.3' | ||
testCompile 'org.mockito:mockito-core:2.18.3' | ||
} | ||
|
207 changes: 207 additions & 0 deletions
207
src/main/java/com/eudemon/ratelimiter/AbstractUrlRateLimiter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
package com.eudemon.ratelimiter; | ||
|
||
import static com.eudemon.ratelimiter.context.RateLimiterBeansFactory.BEANS_CONTEXT; | ||
|
||
import java.util.List; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import com.eudemon.ratelimiter.Interceptor.RateLimiterInterceptor; | ||
import com.eudemon.ratelimiter.Interceptor.RateLimiterInterceptorChain; | ||
import com.eudemon.ratelimiter.algorithm.RateLimiter; | ||
import com.eudemon.ratelimiter.exception.InternalErrorException; | ||
import com.eudemon.ratelimiter.exception.InvalidUrlException; | ||
import com.eudemon.ratelimiter.exception.OverloadException; | ||
import com.eudemon.ratelimiter.rule.ApiLimit; | ||
import com.eudemon.ratelimiter.rule.RateLimitRule; | ||
import com.eudemon.ratelimiter.rule.source.RuleConfigSource; | ||
import com.eudemon.ratelimiter.utils.UrlUtils; | ||
import com.google.common.annotations.VisibleForTesting; | ||
|
||
/** | ||
* Abstract class for url rate limit. | ||
*/ | ||
public abstract class AbstractUrlRateLimiter implements UrlRateLimiter { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(AbstractUrlRateLimiter.class); | ||
|
||
private final ConcurrentHashMap<String, RateLimiter> counters = new ConcurrentHashMap<>(256); | ||
|
||
private final RateLimitRule rateLimitRule; | ||
|
||
private RateLimiterInterceptorChain interceptorChain; | ||
|
||
/** | ||
* Default construct. | ||
*/ | ||
public AbstractUrlRateLimiter() { | ||
this((RateLimitRule) null); | ||
} | ||
|
||
/** | ||
* Construct. | ||
* | ||
* @param source the limit config source, if null, will use default rate limit config source. | ||
*/ | ||
public AbstractUrlRateLimiter(RuleConfigSource source) { | ||
this.rateLimitRule = BEANS_CONTEXT.obtainUrlRateLimitRule(null); | ||
/* load config from source and build rule. */ | ||
source = BEANS_CONTEXT.obtainRuleConfigSource(source); | ||
this.rateLimitRule.addRule(source.load()); | ||
|
||
this.interceptorChain = BEANS_CONTEXT.obtainInterceptorChain(null); | ||
} | ||
|
||
/** | ||
* Construct. | ||
* | ||
* @param rule the limit rule, if null, rate limiter will load limit rule from file or zookeeper. | ||
*/ | ||
public AbstractUrlRateLimiter(RateLimitRule rule) { | ||
this(rule, null); | ||
} | ||
|
||
/** | ||
* Construct. | ||
* | ||
* @param rule the limit rule, if null, rate limiter will load limit rule from file or zookeeper. | ||
* @param chain the interceptor chain, if null, will use default interceptor chain. | ||
*/ | ||
public AbstractUrlRateLimiter(RateLimitRule rule, RateLimiterInterceptorChain chain) { | ||
this.rateLimitRule = BEANS_CONTEXT.obtainUrlRateLimitRule(rule); | ||
if (rule == null) { | ||
/* load config from source and build rule. */ | ||
RuleConfigSource source = BEANS_CONTEXT.obtainRuleConfigSource(null); | ||
this.rateLimitRule.addRule(source.load()); | ||
} | ||
|
||
this.interceptorChain = BEANS_CONTEXT.obtainInterceptorChain(chain); | ||
} | ||
|
||
/** | ||
* Add interceptors into the default interceptor chain. The interceptor will do some work | ||
* before/after the {@code UrlRateLimiter.limit} method. | ||
* | ||
* @param interceptors the interceptor list to be added into the interceptor chain. | ||
*/ | ||
@Override | ||
public void addInteceptors(List<RateLimiterInterceptor> interceptors) { | ||
if (interceptors != null && !interceptors.isEmpty()) { | ||
this.interceptorChain.addInterceptors(interceptors); | ||
} | ||
} | ||
|
||
/** | ||
* Add interceptor into the default interceptor chain. The interceptor will do some work | ||
* before/after the {@code UrlRateLimiter.limit} method. | ||
* | ||
* @param interceptor the interceptor to be added into the interceptor chain. | ||
*/ | ||
@Override | ||
public void addInterceptor(RateLimiterInterceptor interceptor) { | ||
if (interceptor != null) { | ||
this.interceptorChain.addInterceptor(interceptor); | ||
} | ||
} | ||
|
||
/** | ||
* check if the url request of the specified app exceeds the max hit limit. | ||
* | ||
* @param appId the app ID | ||
* @param url the request url. | ||
* @throws OverloadException if the app exceeds the max hit limit for the api. | ||
* @throws InvalidUrlException if the url is invalid. | ||
* @throws InternalErrorException if some internal error occurs. | ||
*/ | ||
@Override | ||
public void limit(String appId, String url) | ||
throws OverloadException, InvalidUrlException, InternalErrorException { | ||
interceptorChain.doBeforeLimit(appId, url); | ||
|
||
ApiLimit apiLimit = null; | ||
boolean passed = false; | ||
Exception exception = null; | ||
try { | ||
// TODO(zheng): validate url | ||
String urlPath = UrlUtils.getUrlPath(url); | ||
// TODO(zheng): do not need get apilimit every time. | ||
apiLimit = rateLimitRule.getLimit(appId, urlPath); | ||
if (apiLimit == null) { | ||
logger.warn("no rate limit rule for api: {}", urlPath); | ||
return; // passed | ||
} | ||
|
||
RateLimiter rateLimiter = | ||
getRateLimiterAlgorithm(appId, apiLimit.getApi(), apiLimit.getLimit()); | ||
passed = rateLimiter.tryAcquire(); | ||
if (!passed) { | ||
StringBuilder builder = new StringBuilder(); | ||
builder.append(appId).append(":").append(url); | ||
builder.append(" has exceeded max tps limit:"); | ||
builder.append(apiLimit.toString()); | ||
throw new OverloadException(builder.toString()); | ||
} | ||
} catch (OverloadException e) { | ||
passed = false; | ||
throw e; | ||
} catch ( InvalidUrlException | InternalErrorException e) { | ||
exception = e; | ||
throw e; | ||
} catch (Exception e) { | ||
InternalErrorException re = new InternalErrorException("Rate limiter internal error.", e); | ||
exception = re; | ||
throw re; | ||
} finally { | ||
interceptorChain.doAfterLimit(appId, url, apiLimit, passed, exception); | ||
} | ||
} | ||
|
||
/** | ||
* Create or get rate limit algorithm for every API. | ||
* | ||
* @param appId the app ID | ||
* @param api the API | ||
* @param limit the max hit count limit per second. | ||
* @return the RateLimiter algorithm. | ||
* @throws InvalidUrlException if API is invalid. | ||
*/ | ||
@VisibleForTesting | ||
public RateLimiter getRateLimiterAlgorithm(String appId, String api, int limit) | ||
throws InvalidUrlException { | ||
String limitKey = generateUrlKey(appId, api); | ||
RateLimiter rateLimiter = counters.get(limitKey); | ||
if (rateLimiter == null) { | ||
RateLimiter newRateLimiter = createRateLimitAlgorithm(limitKey, limit); | ||
rateLimiter = counters.putIfAbsent(limitKey, newRateLimiter); | ||
if (rateLimiter == null) { | ||
rateLimiter = newRateLimiter; | ||
} | ||
} | ||
return rateLimiter; | ||
} | ||
|
||
/** | ||
* Create rate limiter algorithm. | ||
* | ||
* @param limitKey the API key, such as "appid:api" | ||
* @param limit the max hit count limit per second. | ||
* @return the rate limit algorithm. | ||
*/ | ||
protected abstract RateLimiter createRateLimitAlgorithm(String limitKey, int limit); | ||
|
||
/** | ||
* Generate unique key for every appID and api pattern. | ||
* | ||
* @param appId the app ID. | ||
* @param api the api pattern. | ||
* @return the unique key. | ||
*/ | ||
private String generateUrlKey(String appId, String api) { | ||
StringBuilder builder = new StringBuilder(); | ||
builder.append(appId).append(":").append(api); | ||
return builder.toString(); | ||
} | ||
|
||
} |
Oops, something went wrong.