代理公司注册商标,998元网站建设优化,12306网站开发,徐州关键字优化资讯文章目录 1. 什么是限流2. 常见的限流策略2.1 漏斗算法2.2 令牌桶算法2.3 次数统计 3. 令牌桶代码编写4. 接口测试5. 测试结果 1. 什么是限流
限流就是在用户访问次数庞大时#xff0c;对系统资源的一种保护手段。高峰期#xff0c;用户可能对某个接口的访问频率急剧升高对系统资源的一种保护手段。高峰期用户可能对某个接口的访问频率急剧升高后端接口通常需要进行DB操作接口访问频率升高DB的IO次数就显著增高从而极大的影响整个系统的性能。如果不对用户访问频率进行限制高频的访问容易打跨整个服务
2. 常见的限流策略
2.1 漏斗算法
我们想象一个漏斗大口用于接收客户端的请求小口用于流出用户的请求。漏斗能够保证流出请求数量的稳定。 2.2 令牌桶算法
令牌桶算法每个请求想要通过就必须从令牌桶中取出一个令牌。否则无法通过。而令牌会内部会维护每秒钟产生的令牌的数量使得每秒钟能够通过的请求数量得到控制 2.3 次数统计
次数统计的方式非常直接每一次请求都进行计数并统计时间戳。如果下一次请求携带的时间戳在一定的频率内进行次数的累加。如果次数达到一定阈值则拒绝后续请求。直到下一次请求时间戳大于初始时间戳重置接口次数与时间戳 3. 令牌桶代码编写
令牌桶算法我们可以使用Google guava包下的封装好的RateLimiter紧紧抱住大爹大腿
另外ip频率限制是一个横向逻辑该功能应该保护所有后端接口因此我们可以采用Spring AOP增强所有后端接口
另外我们需要对同一个用户对同一个接口访问次数进行限流这意味着我们需要限制的是——用户接口这样的一对元组。用户可以通过ip进行限定也就是说后端是同一个ip针对同一个请求的访问进行限流
因此我们需要为每一个这样的ipmethod使用令牌桶限流ipmethod- RateLimiter。ip method这一对元组唯一确定一个RateLimiter
我们可以采用Map缓存这样的一一对应的关系
ButHashMap显然不适合应为HashMap不防并发另外ConcurrentHashMap也不合适假如一个用户发出一个请求后就下线了那么这个key就会长久的存活于内存中这极大的增加了内存的压力
因此我们采用Google的Cache
Google大爹提供的Cache功能极其强大读者可以自行阅读下面文档
/*** A builder of {link LoadingCache} and {link Cache} instances having any combination of the* following features:** ul* liautomatic loading of entries into the cache* lileast-recently-used eviction when a maximum size is exceeded* litime-based expiration of entries, measured since last access or last write* likeys automatically wrapped in {code WeakReference}* livalues automatically wrapped in {code WeakReference} or {code SoftReference}* linotification of evicted (or otherwise removed) entries* liaccumulation of cache access statistics* /ul* /IpLimiterAspect.java
import com.fgbg.demo.utils.RequestUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
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.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;/*** 限制每个ip对同一个接口的访问频率*/
Component
Aspect
Slf4j
RestController
public class IpLimiterAspect {Autowiredprivate RequestUtils requestUtils;// 每秒生成1个令牌, 同个ip访问同个接口的QPS为1private final double PERMIT_PER_SECOND 1;// 创建本地缓存private final CacheString, RateLimiter limiterCache CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build();Around(execution(* com.fgbg.demo.controller..*.*(..)))public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {// 构造keySignature signature proceedingJoinPoint.getSignature();MethodSignature methodSignature (MethodSignature) signature;String methodName proceedingJoinPoint.getTarget().getClass().getName() . methodSignature.getName();String key requestUtils.getCurrentIp() - methodName;// 获取key对应的RateLimiterRateLimiter rateLimiter limiterCache.get(key, () - RateLimiter.create(PERMIT_PER_SECOND));if (! rateLimiter.tryAcquire()) {// 如果不能立刻获取令牌, 说明访问速度大于1 次/s, 触发限流log.warn(访问过快, 触发限流);throw new RuntimeException(访问过快, 触发限流);}log.info(接口放行...);return proceedingJoinPoint.proceed();}
}
RequestUtils.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;Component
public class RequestUtils {Autowiredprivate HttpServletRequest httpServletRequest;public String getCurrentIp() {return httpServletRequest.getHeader(X-Real-IP);}
}
4. 接口测试
接口测试这块就比较随意了笔者这里采用apifox进行接口测试。因为AOP逻辑是增强所有接口因此这里选择了项目曾经暴露出的一个查询接口。点击运行即可开始测试
5. 测试结果 2.6s分别在012s开始时允许接口访问。10个请求中通过3个失败7个QPS 1限流成功 测试量达到40QPS维持1说明代码逻辑基本没有问题Google yyds