赞
踩
限流对于一个微服务架构系统来说具有非常重要的意义,否则其中的某个微服务将成为整个系统隐藏的雪崩因素,为什么这么说?
举例来讲,某个平台有100多个微服务应用,但是作为底层的某个或某几个应用来说,将会被所有上层应用频繁调用,业务高峰期时,如果底层应用不做限流处理,该应用必将面临着巨大的压力,尤其是那些个别被高频调用的接口来说,最直接的表现就是导致后续新进来的请求阻塞、排队、响应超时…最后直到该服务所在JVM资源被耗尽。
不管是哪种限流组件,其底层的限流实现算法大同小异,这里列举几种常用的限流算法以供了解。
点击了解Nginx中漏桶算法,令牌桶算法和滑动时间窗口算法
在微服务应用中,比较通用的做法是,利用 AOP技术
+自定义注解
实现对特定的方法或接口进行限流,下面基于这个思路来分别介绍下几种常用的限流方案的实现。
guava
为谷歌开源的一个比较实用的组件,利用这个组件可以帮助开发人员完成常规的限流操作,接下来看具体的实现步骤。
版本可以选择更高的或其他版本
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
自定义一个限流用的注解,后面在需要限流的方法或接口上面只需添加该注解即可;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface RateConfigAnno {
String limitType();
double limitCount() default 5d;
}
通过 AOP
前置通知的方式拦截添加了上述自定义限流注解的方法,解析注解中的属性值,并以该属性值作为guava提供的限流参数,该类为整个实现的核心所在。
import cn.annotation.RateConfigAnno; import cn.limit.RateLimitHelper; import com.alibaba.fastjson.JSONObject; import com.google.common.util.concurrent.RateLimiter; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; import java.util.Objects; @Slf4j @Aspect @Component public class GuavaLimitAop { //如果该类和注解在同一包下 //@Before("execution(@RateConfigAnno * *(..))") //如果该类和注解不在同一包下 @Before("execution(@cn.annotation.RateConfigAnno * *(..))") public void limit(JoinPoint joinPoint) { //1、获取当前的调用方法 Method currentMethod = getCurrentMethod(joinPoint); if (Objects.isNull(currentMethod)) { return; } //2、从方法注解定义上获取限流的类型 String limitType = currentMethod.getAnnotation(RateConfigAnno.class).limitType(); double limitCount = currentMethod.getAnnotation(RateConfigAnno.class).limitCount(); //使用guava的令牌桶算法获取一个令牌,获取不到先等待 RateLimiter rateLimiter = RateLimitHelper.getRateLimiter(limitType, limitCount); boolean b = rateLimiter.tryAcquire(); if (b) { System.out.println("获取到令牌"); }else { HttpServletResponse resp = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); JSONObject jsonObject=new JSONObject(); jsonObject.put("success",false); jsonObject.put("msg","限流中"); try { output(resp, jsonObject.toJSONString()); }catch (Exception e){ logger.error("error,e:{}",e); } } } private Method getCurrentMethod(JoinPoint joinPoint) { Method[] methods = joinPoint.getTarget().getClass().getMethods(); Method target = null; for (Method method : methods) { if (method.getName().equals(joinPoint.getSignature().getName())) { target = method; break; } } //或者使用如下方式获取method对象 //MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //Method method = signature.getMethod(); return target; } public void output(HttpServletResponse response, String msg) throws IOException { response.setContentType("application/json;charset=UTF-8"); ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); outputStream.write(msg.getBytes("UTF-8")); } catch (IOException e) { e.printStackTrace(); } finally { outputStream.flush(); outputStream.close(); } } }
其中限流的核心API即为RateLimiter这个对象,涉及到的RateLimitHelper类如下
import com.google.common.util.concurrent.RateLimiter; import java.util.HashMap; import java.util.Map; public class RateLimitHelper { private RateLimitHelper(){} private static Map<String,RateLimiter> rateMap = new HashMap<>(); public static RateLimiter getRateLimiter(String limitType,double limitCount ){ RateLimiter rateLimiter = rateMap.get(limitType); if(rateLimiter == null){ rateLimiter = RateLimiter.create(limitCount); rateMap.put(limitType,rateLimiter); } return rateLimiter; } }
下面添加一个测试接口,测试一下上面的代码是否生效
@RestController
public class OrderController {
@GetMapping("/save")
@RateConfigAnno(limitType = "saveOrder",limitCount = 1)
public String save(){
return "success";
}
}
在不少人的意识中,sentinel
通常是需要结合springcloud-alibaba
框架一起实用的,而且与框架集成之后,可以配合控制台一起使用达到更好的效果,实际上,sentinel
官方也提供了相对原生的SDK
可供使用,接下来就以这种方式进行整合。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
可以根据需要,添加更多的属性
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface SentinelLimitAnnotation {
String resourceName();
int limitCount() default 5;
}
该类的实现思路与上述使用guava类似,不同的是,这里使用的是sentinel
原生的限流相关的API
import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; 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.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Objects; @Aspect @Component public class SentinelMethodLimitAop { private static void initFlowRule(String resourceName,int limitCount) { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); //设置受保护的资源 rule.setResource(resourceName); //设置流控规则 QPS rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //设置受保护的资源阈值 rule.setCount(limitCount); rules.add(rule); //加载配置好的规则 FlowRuleManager.loadRules(rules); } @Pointcut(value = "@annotation(com.congge.sentinel.SentinelLimitAnnotation)") public void rateLimit() { } @Around("rateLimit()") public Object around(ProceedingJoinPoint joinPoint) { //1、获取当前的调用方法 Method currentMethod = getCurrentMethod(joinPoint); if (Objects.isNull(currentMethod)) { return null; } //2、从方法注解定义上获取限流的类型 String resourceName = currentMethod.getAnnotation(SentinelLimitAnnotation.class).resourceName(); if(StringUtils.isEmpty(resourceName)){ throw new RuntimeException("资源名称为空"); } int limitCount = currentMethod.getAnnotation(SentinelLimitAnnotation.class).limitCount(); initFlowRule(resourceName,limitCount); Entry entry = null; Object result = null; try { entry = SphU.entry(resourceName); try { result = joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } } catch (BlockException ex) { // 资源访问阻止,被限流或被降级 // 在此处进行相应的处理操作 System.out.println("blocked"); return "被限流了"; } catch (Exception e) { Tracer.traceEntry(e, entry); } finally { if (entry != null) { entry.exit(); } } return result; } private Method getCurrentMethod(JoinPoint joinPoint) { Method[] methods = joinPoint.getTarget().getClass().getMethods(); Method target = null; for (Method method : methods) { if (method.getName().equals(joinPoint.getSignature().getName())) { target = method; break; } } //或者使用如下方式获取method对象 //MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //Method method = signature.getMethod(); return target; } }
为了模拟效果,这里将QPS的数量设置为1
@GetMapping("/limit")
@SentinelLimitAnnotation(limitCount = 1,resourceName = "sentinelLimit")
public String sentinelLimit(){
return "sentinelLimit";
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。