SnowJean使用以及原理(SpringBoot)

一.使用

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章