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作为共享的计数器,只是性能有所下降。