一.使用
1.POM文件
<dependency>
<groupId>cn.yueshutong</groupId>
<artifactId>snowjean-spring-boot-starter</artifactId>
<version>3.0.0.RELEASE</version>
</dependency>
2.配置
import cn.yueshutong.commoon.entity.RateLimiterRule;
import cn.yueshutong.commoon.entity.RateLimiterRuleBuilder;
import cn.yueshutong.core.config.RateLimiterConfig;
import cn.yueshutong.core.limiter.RateLimiter;
import cn.yueshutong.core.limiter.RateLimiterFactory;
import cn.yueshutong.core.observer.RateLimiterObserver;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
/**
* @Classname RateLimiterConfig
* @Description TODO
* @Date 2020/5/27 13:41
* @Created zzf
*/
@Configuration
public class RateLimiterRuleConfig {
@PostConstruct
public void register() {
RateLimiterRule rateLimiterRule = new RateLimiterRuleBuilder()
//ID很重要,對應註解@Limiter中的value
.setId("limiter")
.setLimit(1)
.setBatch(1)
// .setLimiterModel(LimiterModel.CLOUD)
.build();
// 2.配置TicketServer地址(支持集羣、加權重)
Map<String, Integer> map = new HashMap<>();
map.put("127.0.0.1:8521", 1);
// 3.全局配置
RateLimiterConfig config = RateLimiterConfig.getInstance();
config.setTicketServer(map);
//生產限流器
RateLimiter rateLimiter = RateLimiterFactory.of(rateLimiterRule, config);
//隨時隨地獲取已生產的限流器
RateLimiter rateLimiter1 = RateLimiterObserver.getMap().get(rateLimiter.getId());
}
}
3.使用
@RestController
@RequestMapping("/api/v3/")
@Api(value = "open_api", description = "open_api基礎接口", tags = {"open_api"})
public class TestController {
@ApiOperation(value = "測試", notes = "測試")
@ApiResponses({
@ApiResponse(code = 400, message = "參數非法"),
@ApiResponse(code = 500, message = "服務器錯誤"),
@ApiResponse(code = 200, message = "成功")
})
@ApiImplicitParams({
})
@Limiter(value = "limiter")
@RequestMapping(value = "/test", method = RequestMethod.GET)
public CommonResult<String> getUserInfo() {
return new CommonResult<>("999");
}
}
4.測試
當你在短時間內快速調用時就會進行限制!
二.原理
這裏我只分析本地限流:
首先它會通過在方法上的註解@Limiter對調用方法進行攔截:
package cn.yueshutong.annotation.aspect;
import cn.yueshutong.annotation.entity.Limiter;
import cn.yueshutong.core.observer.RateLimiterObserver;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
@Aspect
public class RateLimiterAspect {
@Pointcut("@annotation(cn.yueshutong.annotation.entity.Limiter)")
public void pointcut() {
}
@Around("pointcut() && @annotation(limiter)")
public Object around(ProceedingJoinPoint pjp, Limiter limiter) throws Throwable {
cn.yueshutong.core.limiter.RateLimiter rateLimiter = RateLimiterObserver.getMap().get(limiter.value());
if (rateLimiter.tryAcquire()) {
return pjp.proceed();
}
Signature sig = pjp.getSignature();
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("This annotation can only be used in methods.");
}
MethodSignature msg = (MethodSignature) sig;
Object target = pjp.getTarget();
Method fallback = target.getClass().getMethod(limiter.fallback(), msg.getParameterTypes());
return fallback.invoke(target,pjp.getArgs());
}
}
首先根據limiter的value獲取相應的限流器,然後通過調用內部的tryAcquire方法判斷是否通過,如果是true則通過pjp.proceed()進入下一個流程,否則他會獲取@Limiter中的fallback方法進行回調。
下面來看看它的具體實現邏輯:
/**
* 2.Check
*/
@Override
public boolean tryAcquire() {
if (rule.isEnable()) {
//限流功能已關閉
return true;
}
return tryAcquireMonitor();
}
/**
* 3.Monitor
*/
private boolean tryAcquireMonitor() {
if (rule.getLimiterModel() == LimiterModel.POINT) {
//本地限流不支持監控
return tryAcquirePut();
}
MonitorBean monitor = new MonitorBean();
monitor.setLocalDateTime(LocalDateTime.now());
monitor.setPre(1);
monitor.setApp(rule.getApp());
monitor.setId(rule.getId());
monitor.setName(rule.getName());
monitor.setMonitor(rule.getMonitor());
boolean b = tryAcquirePut(); //fact
if (b) {
monitor.setAfter(1);
}
config.getScheduledThreadExecutor().execute(() -> { //異步執行
monitorService.save(monitor);
});
return b;
}
/**
* 4.putCloudBucket
*/
private boolean tryAcquirePut() {
boolean result = tryAcquireFact();
//分佈式方式下檢查剩餘令牌數
putCloudBucket();
return result;
}
進入tryAcquire方法後他會判斷是否開啓限流功能,然後通過tryAcquireMonitor方法進行調用。通過調用可以看到不管是分佈式還是本地,它內部的核心都是tryAcquireFact方法!
private final AtomicLong bucket = new AtomicLong(0); //令牌桶初始容量:0
/**
* 5.tryAcquireFact
*/
private boolean tryAcquireFact() {
if (rule.getLimit() == 0) {
return false;
}
boolean result = false;
switch (rule.getAcquireModel()) {
case FAILFAST:
result = tryAcquireFailed();
break;
case BLOCKING:
result = tryAcquireSucceed();
break;
}
return result;
}
/**
* CAS獲取令牌,沒有令牌立即失敗
*/
private boolean tryAcquireFailed() {
long l = bucket.longValue();
while (l > 0) {
if (bucket.compareAndSet(l, l - 1)) {
return true;
}
l = bucket.longValue();
}
return false;
}
/**
* CAS獲取令牌,阻塞直到成功
*/
private boolean tryAcquireSucceed() {
long l = bucket.longValue();
while (!(l > 0 && bucket.compareAndSet(l, l - 1))) {
sleep();
l = bucket.longValue();
}
return true;
}
這裏可以看到其內部是通過對一個原子對象的cas操作,如果backet值大於0且cas操作成功則返回true,否則就是false。這就是其內部取令牌的操作。如果是分佈式限流話它還有一個putCloudBucket方法對總數進行一個同步:
/**
* 集羣限流,取批令牌
*/
private void putCloudBucket() {
//校驗
if (!rule.getLimiterModel().equals(LimiterModel.CLOUD) ||
bucket.get() / 1.0 * rule.getBatch() > rule.getRemaining()) {
return;
}
//異步任務
config.getScheduledThreadExecutor().execute(() -> {
//DCL,再次校驗
if (bucket.get() / 1.0 * rule.getBatch() <= rule.getRemaining()) {
synchronized (bucket) {
if (bucket.get() / 1.0 * rule.getBatch() <= rule.getRemaining()) {
String result = config.getTicketServer().connect(RateLimiterConfig.http_token, JSON.toJSONString(rule));
if (result != null) {
bucket.getAndAdd(Long.parseLong(result));
}
}
}
}
});
}
public String connect(String path, String data) {
String server = getServer();
try {
return HttpUtil.connect("http://" + server + "/" + path)
.setData("data", data)
.setMethod("POST")
.execute()
.getBody();
} catch (IOException e) {
if (System.currentTimeMillis() - start >3000) {
logger.error("{} The server is not available.", server);
start = System.currentTimeMillis();
}
serverList.remove(server);
backupsList.add(server);
}
return null;
}
他會啓動一個定時任務,通過遠程調用的方式對令牌進行一個同步操作!所以其實SnowJean的原理很簡單。
最後看看生成令牌的操作:
/**
* 本地限流,放入令牌
*/
private void putPointBucket() {
if (this.scheduledFuture != null) {
this.scheduledFuture.cancel(true);
}
if (rule.getLimit() == 0 || !rule.getLimiterModel().equals(LimiterModel.POINT)) {
return;
}
this.scheduledFuture = config.getScheduledThreadExecutor().scheduleAtFixedRate(() -> bucket.set(rule.getLimit()), rule.getInitialDelay(), rule.getPeriod(), rule.getUnit());
}
如果是本地限流的話它其實是通過一個定時任務去不斷生成的!
文檔:https://github.com/ystcode/SnowJena/blob/master/CN_README.md