AOP+AtomicLong限流

限制某个接口的总并发/请求数

如果接口可能会有并发流量,但又担心访问量太大造成奔溃,那么久需要限制这个接口的总并发/请求数了。因为粒度比较细,可以为每个接口设置相应的阈值。可以使用Java中的AtomicLong或者Semaphore进行限流。

简单实现伪代码如下:

try {
    if (atomic.incrementAndGet() > 限流数) {
        //拒绝请求
    }
    //处理请求
} finally {
    atomic.decrementAndGet();
}

 限流接口(方法)每秒的请求数

限制每秒的请求数,可以使用Guava的Cache来存储计数器,设置过期时间为2S(保证能记录1S内的计数)。下面代码使用当前时间戳的秒数作为key进行统计,这种限流的方式也比较简单。

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.aspectj.lang.ProceedingJoinPoint;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;


@Component
@Scope
@Aspect
public class CountLimitAspect {

    private Logger logger = LoggerFactory.getLogger(CountLimitAspect.class);

    // 用来存放当前时间戳下的总的请求数
    private LoadingCache<Long, AtomicLong> counter = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS)
            .build(new CacheLoader<Long, AtomicLong>() {
                @Override
                public AtomicLong load(Long aLong) throws Exception {
                    return new AtomicLong(0);
                }
            });

    @Pointcut("@annotation(ratelimit.annotation.RateLimit)")
    public void rateLimit() {
    }

    @Around("rateLimit()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        // 获取拦截的方法名
        MethodSignature msig = (MethodSignature) joinPoint.getSignature();
        Method currentMethod = msig.getMethod();
        // 获取注解信息
        RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);
        int limitNum = annotation.limitNum(); //每秒限流的数量
        // 注解所在方法名区分不同的限流策略

        
        long currentSeconds = System.currentTimeMillis() / 1000;
        try {
            // 超过阀值时,直接返回错误
            if (counter.get(currentSeconds).incrementAndGet() > limitNum) {
                logger.info("被限流了:{}", currentSeconds);
                return "系统繁忙,稍后重试";
            }
        
            // 执行方法
            obj = joinPoint.proceed();
            
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return obj;
    }

}

其它几个类不贴了,和《AOP+Semaphore实现单节点的接口(方法)限流》一样的。

结果:

 上面介绍的“限制某个接口的总并发/请求数”和"限流接口每秒的请求数"限流方案都是对于单机接口的限流,当系统进行多机部署时,就无法实现整体对外功能的限流了。当然这也看具体的应用场景,如果多个节点需要共享限流阀值指标,可以使用Redis作为共享的计数器,只是性能有所下降。

 

posted on 2014-11-30 18:40  duanxz  阅读(2940)  评论(0编辑  收藏  举报