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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值