在开发高并发系统时,可以使用三种工具来保护系统:缓存、降级和限流。电流限制可以被视为一种服务降级。限流通过限制请求的流量来达到保护系统的目的。
一般来说,系统的吞吐量可以作为一个阈值来计算。为了保证系统的稳定运行,一旦达到这个阈值,就需要进行流量限制,并采取一些措施来达到限制流量的目的。例如:延迟处理、拒绝处理、或者部分拒绝处理等。否则很容易导致服务器宕机。
逆流限流算法是最简单粗暴的解决方案。主要用于限制并发的总数。比如数据库连接池的大小、线程池的大小、接口并发访问数等都采用计数器算法。
例如:使用AomicInteger来统计并发执行的数量。如果超过域值,则请求将被直接拒绝,表明系统正忙。
漏桶算法的思想非常简单。我们将水与请求进行比较,将漏桶与系统处理能力限制进行比较。水首先进入漏桶,漏桶中的水以一定的速率流出。当流出流量小于流入流量时,由于漏桶容量有限,后续进水直接溢出(拒绝请求),从而实现限流。
令牌桶算法的原理也比较简单。我们可以理解为就医的医院挂号。拿到号码后医生才能诊断。
系统会维护一个令牌桶,并以恒定的速度将令牌放入桶中。这时候,如果有请求进来,想要被处理,就需要先从桶中获取token。当桶中没有可用的令牌时,请求将被拒绝服务。令牌桶算法通过控制桶的容量和发放令牌的速率来限制请求。
Google开源工具包Guava提供了ratelimiter工具类,基于令牌桶算法实现流量限制。使用起来很方便,效率也很高
引入依赖pom
<依赖关系> <groupId>com.google.guavagroupId> <artifactId>番石榴artifactId> <版本>30.1-jre版本> 依赖性>
创建注释限制
package com.example.demo.common.annotation;导入java.lang.annotation.*;导入 java.util。并发.TimeUnit;@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})@Documentedpublic@界面 限制 默认 ""; | //时间 长 超时(); //时间类型 TimeUnit 时间单位() 默认 TimeUnit.MILLISECONDS; //提示消息 msg() 默认“系统繁忙,请稍后重试”;}
注解aop实现
package com.example.demo.common.aspect;import com.example.demo.common.annotation.Limit;import com.example.demo.common .dto.R;导入com.example.demo.common.exception.LimitException;导入com.google.common.collect.Maps;导入com. google.common.util.concurrent.RateLimiter;导入lombok.extern.slf4j.Slf4j;导入org.aspectj.lang.ProceedingJoinPoint;导入 org.aspectj .lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org. springframework.stereotype.Component;导入 java.lang.reflect.Method;导入www.sychzs.cn;@Slf4j @Aspect@Componentpublic class LimitAspect { 私人 最终 地图 limitMap = Maps.newConcurrentMap(); @Around("@annotation(www.sychzs.cn)符号.Limi" 抛出可抛出{ MethodSignature 签名 = (MethodSignature)pjp.getSignature(); 方法方法 = 签名.getMethod(); 限制 limit = method.getAnnotation(Limit .类);//按键功能:接口不同,流量控制不同 String key=limit.key(); 是否有按一下键 if (!limitMap.containsKey( key)) { limitMap.put(key,rateLimiter); ( 》新令牌桶={},容量= {}",key,limit.permitsPerSecond()); Map.get(key); // 获取令牌 boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit()); //如果获取不到命令,直接返回异常提示 ;令牌失败“,密钥); 抛出newLimitException(limit.msg( )); } } return pjp.proceed(); }}
使用