package com.yichao.woo.ratelimiter;
import lombok.Getter;
import lombok.Setter;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Yichao-Woo.
*/
public class RateLimiter {
private JedisPool jedisPool;
private long intervalInMills;
private long limit;
private double intervalPerPermit;
public RateLimiter() {
jedisPool = new JedisPool("192.168.38.3", 6379);
intervalInMills = 10000;
limit = 3;
intervalPerPermit = intervalInMills * 1.0 / limit;
}
// 单线程操作下才能保证正确性
// 需要这些操作原子性的话,最好使用 redis 的 lua script
public boolean access(String userId) {
String key = genKey(userId);
try (Jedis jedis = jedisPool.getResource()) {
Map<String, String> counter = jedis.hgetAll(key);
if (counter.size() == 0) {
TokenBucket tokenBucket = new TokenBucket(System.currentTimeMillis(), limit - 1);
jedis.hmset(key, tokenBucket.toHash());
return true;
} else {
TokenBucket tokenBucket = TokenBucket.fromHash(counter);
long lastRefillTime = tokenBucket.getLastRefillTime();
long refillTime = System.currentTimeMillis();
long intervalSinceLast = refillTime - lastRefillTime;
long currentTokensRemaining;
if (intervalSinceLast > intervalInMills) {
currentTokensRemaining = limit;
} else {
long grantedTokens = (long) (intervalSinceLast / intervalPerPermit);
System.out.println(grantedTokens);
currentTokensRemaining = Math.min(grantedTokens + tokenBucket.getTokensRemaining(), limit);
}
tokenBucket.setLastRefillTime(refillTime);
assert currentTokensRemaining >= 0;
if (currentTokensRemaining == 0) {
tokenBucket.setTokensRemaining(currentTokensRemaining);
jedis.hmset(key, tokenBucket.toHash());
return false;
} else {
tokenBucket.setTokensRemaining(currentTokensRemaining - 1);
jedis.hmset(key, tokenBucket.toHash());
return true;
}
}
}
}
private String genKey(String userId) {
return "rate:limiter:" + intervalInMills + ":" + limit + ":" + userId;
}
@Getter
@Setter
public static class TokenBucket {
private long lastRefillTime;
private long tokensRemaining;
public TokenBucket(long lastRefillTime, long tokensRemaining) {
this.lastRefillTime = lastRefillTime;
this.tokensRemaining = tokensRemaining;
}
public static TokenBucket fromHash(Map<String, String> hash) {
long lastRefillTime = Long.parseLong(hash.get("lastRefillTime"));
int tokensRemaining = Integer.parseInt(hash.get("tokensRemaining"));
return new TokenBucket(lastRefillTime, tokensRemaining);
}
public Map<String, String> toHash() {
Map<String, String> hash = new HashMap<>();
hash.put("lastRefillTime", String.valueOf(lastRefillTime));
hash.put("tokensRemaining", String.valueOf(tokensRemaining));
return hash;
}
}
public static void main(String[] args) throws InterruptedException {
RateLimiter rateLimiter = new RateLimiter();
for (int i = 0; i < 3; i++) {
boolean yigwoo = rateLimiter.access("yigwoo");
System.out.println(yigwoo);
}
boolean yigwoo = rateLimiter.access("yigwoo");
System.out.println(yigwoo);
Thread.sleep(7000);
boolean yigwoo1 = rateLimiter.access("yigwoo");
System.out.println(yigwoo1);
}
}