外部服务或API可能有使用限制,或者它们不能失败就无法处理大量请求。 这篇文章解释了如何创建一个基于Spring Framework的方面,该方面可以用来限制使用Guava速率限制器的任何建议方法调用。 以下实现需要Java 8,Spring AOP和Guava。

让我们从注释开始,该注释用于建议任何启用Spring AOP的方法调用。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {/*** @return rate limit in queries per second*/int value();/*** @return rate limiter identifier (optional)*/String key() default "";}

注释定义了两件事:每秒查询(或许可)中的速率限制,以及标识速率限制器的可选键。 如果密钥相等,则多种方法可以使用相同的速率限制器。 例如,当使用来自不同方法的不同参数调用API时,每秒所需的总查询次数将不会超过。

接下来是实际的节流方面,它是作为Spring Framework组件实现的。 无论有没有Spring框架,在任何情况下都可以使用方面。

@Aspect
@Component
public class RateLimiterAspect {public interface KeyFactory {String createKey(JoinPoint jp, RateLimit limit);}private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterAspect.class);private static final KeyFactory DEFAULT_KEY_FACTORY = (jp, limit) -> JoinPointToStringHelper.toString(jp);private final ConcurrentHashMap<String, RateLimiter> limiters;private final KeyFactory keyFactory;@Autowiredpublic RateLimiterAspect(Optional<KeyFactory> keyFactory) {this.limiters = new ConcurrentHashMap<>();this.keyFactory = keyFactory.orElse(DEFAULT_KEY_FACTORY);}@Before("@annotation(limit)")public void rateLimit(JoinPoint jp, RateLimit limit) {String key = createKey(jp, limit);RateLimiter limiter = limiters.computeIfAbsent(key, createLimiter(limit));double delay = limiter.acquire();LOGGER.debug("Acquired rate limit permission ({} qps) for {} in {} seconds", limiter.getRate(), key, delay);}private Function<String, RateLimiter> createLimiter(RateLimit limit) {return name -> RateLimiter.create(limit.value());}private String createKey(JoinPoint jp, RateLimit limit) {return Optional.ofNullable(Strings.emptyToNull(limit.key())).orElseGet(() -> keyFactory.createKey(jp, limit));}
}

该类定义了密钥工厂的附加接口和默认实现,如果注释未为速率限制器提供显式密钥,则使用该默认工厂。 密钥工厂可以使用连接点(基本上是方法调用)和提供的注释为速率限制器创建合适的密钥。 该方面还使用并发哈希图来存储速率限制器实例。 方面被定义为单例,但是可以从多个线程调用rateLimit方法,因此并发哈希图确保我们为每个唯一键仅分配单个速率限制器。 方面的构造器注入利用了Spring Framework的可选注入支持。 如果在上下文中未定义KeyFactory bean,则使用默认的密钥工厂。

该类使用@Aspect和@Component进行注释,以便Spring理解已定义的方面并启用@Before建议。 @Before通知仅包含一个切入点,该切入点需要RateLimit批注并将其绑定到方法的limit参数。 节流的实现非常简单。 首先,为速率限制器创建一个密钥。 然后,使用密钥查找或创建限制器,最后获取限制器以获取许可。

速率限制器密钥创建中有一个小陷阱。 注释定义的键将转换为可选键,但由于性能原因,不能使用可选键的orElse方法。 可选的orElse方法采用一个在任何情况下(无论是否存在)可选的值。 另一方面,另一种方法或orElseGet使用了供应商,该供应商仅当不存在可选值时才允许懒惰地评估值。 密钥工厂的createKey可能是一项昂贵的操作,因此使用供应商版本。

并发哈希图包含一个方便的方法computeIfAbsent ,该方法自动基于键和已定义的函数查找或创建一个值。 这允许对映射值进行简单明了的延迟初始化。 速率限制器是按需创建的,并且保证每个唯一的限制器密钥只有一个实例。

默认的密钥工厂实现使用JoinPointToStringHelper中的帮助程序方法将连接点转换为文本表示形式。

public class JoinPointToStringHelper {public static String toString(JoinPoint jp) {StringBuilder sb = new StringBuilder();appendType(sb, getType(jp));Signature signature = jp.getSignature();if (signature instanceof MethodSignature) {MethodSignature ms = (MethodSignature) signature;sb.append("#");sb.append(ms.getMethod().getName());sb.append("(");appendTypes(sb, ms.getMethod().getParameterTypes());sb.append(")");}return sb.toString();}private static Class<?> getType(JoinPoint jp) {return Optional.ofNullable(jp.getSourceLocation()).map(SourceLocation::getWithinType).orElse(jp.getSignature().getDeclaringType());}private static void appendTypes(StringBuilder sb, Class<?>[] types) {for (int size = types.length, i = 0; i < size; i++) {appendType(sb, types[i]);if (i < size - 1) {sb.append(",");}}}private static void appendType(StringBuilder sb, Class<?> type) {if (type.isArray()) {appendType(sb, type.getComponentType());sb.append("[]");} else {sb.append(type.getName());}}
}

最后,只需添加@RateLimit批注,即可将限制应用于任何启用Spring的方法。

@Service
public class MyService {...@RateLimit(5)public String callExternalApi() {return restTemplate.getForEntity(url, String.class).getBody();}}

有人可能想知道这种解决方案能否很好地扩展? 不,实际上不是。 Guava的速率限制器会阻止当前线程,因此,如果对受限制的服务进行异步调用,则大量线程将被阻止,并可能导致空闲线程耗尽。 如果在多个应用程序或JVM实例中复制服务,则会引起另一个问题。 没有限制器速率的全局同步。 对于驻留在单个JVM中的单个应用程序以及节流方法的适当负载,此实现效果很好。

进一步阅读:

  • 使用Spring进行面向方面的编程
  • 番石榴RateLimiter
  • RateLimiter –发现Google Guava
  • 有序Java多通道异步节流器
  • 节流演员信息

翻译自: https://www.javacodegeeks.com/2015/07/throttle-methods-with-spring-aop-and-guava-rate-limiter.html

使用Spring AOP和番石榴速率限制器的节气门方法相关推荐

  1. 使用Spring AOP和Guava速率限制器的节气门方法

    外部服务或API可能有使用限制,或者它们无法处理请求负载而不会失败. 这篇文章解释了如何创建一个基于Spring Framework的方面,该方面可以用来限制使用Guava速率限制器的任何建议方法调用 ...

  2. 哪些方法不能够实施Spring AOP事务

    2019独角兽企业重金招聘Python工程师标准>>> 哪些方法不能够实施Spring AOP事务 由于Spring事务管理是基于接口代理或动态字节码技术.通过AOP实施事务增强. ...

  3. Spring AOP 切点(pointcut)表达式

    概括 这遍文章将介绍Spring AOP切点表达式(下称表达式)语言,首先介绍两个面向切面编程中使用到的术语. 连接点(Joint Point):广义上来讲,方法.异常处理块.字段这些程序调用过程中可 ...

  4. 顺序执行_执行流程 | 你真的了解Spring AOP的执行顺序吗?

    Hi! 我是小小,我们又见面了,今天的主要内容是,你真的了解Spring AOP的执行顺序吗?跟随着我的脚步,一块丈量世界,了解世界,重新认识,重新了解Spring AOP的执行顺序. 聊一聊毕业四个 ...

  5. gtw-050090|执行拦截器时发生异常_执行流程 | 你真的了解Spring AOP的执行顺序吗?...

    Hi! 我是小小,我们又见面了,今天的主要内容是,你真的了解Spring AOP的执行顺序吗?跟随着我的脚步,一块丈量世界,了解世界,重新认识,重新了解Spring AOP的执行顺序. 聊一聊毕业四个 ...

  6. Spring AOP知识点简介

    文章目录 1.什么是AOP 1.1.AOP术语 1.2.AOP框架 2.动态代理 2.1.JDK动态代理 2.2.CGLIB动态代理 3.基于代理类的AOP实现 3.1.Spring的通知类型 3.2 ...

  7. Spring AOP 功能使用详解

    前言 AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,可以用来管理一些和主业务无关的周边业务,如日志记录,事务管理等:陌生是因为在工作中基本没有使用过,AOP ...

  8. 执行流程 | 你真的了解Spring AOP的执行顺序吗?

    Hi! 我是小小,我们又见面了,今天的主要内容是,你真的了解Spring AOP的执行顺序吗?跟随着我的脚步,一块丈量世界,了解世界,重新认识,重新了解Spring AOP的执行顺序. 聊一聊毕业四个 ...

  9. spring aop 会根据实际情况(有无接口)自动选择 两种 动态代理(jdk和cglib)之一...

    资料: (1)Cglib的简单使用: https://blog.csdn.net/zhanghongjie0302/article/details/45648947 (2)关于java字节码框架ASM ...

最新文章

  1. ORU-10027: buffer overflow, limit of 10000 bytes
  2. HDU-4456 Crowd 二维树状数组+坐标转换
  3. Linux环境下C语言模拟内存负载测试
  4. 深圳linux测试题库,Linux认证考试题库及答案
  5. bgb邻居关系建立模型_今日 Paper | 新闻推荐系统;多路编码;知识增强型预训练模型等...
  6. ruby-gems 常用命令笔记
  7. JavaScript初学者系列一:JavaScript基础(上)
  8. Mysql主从占用大量cpu_Mysql占用过高CPU时的优化手段
  9. Java基础(1):Java简介和开发环境配置
  10. 【SAS系列】SAS入门书籍推荐
  11. 做好4项防护,保障服务器安全
  12. 微信小程序实现图片预览(闭眼cv)
  13. 人工智能资料库:第37辑(20170220)
  14. 统一诊断服务(UDS) - 诊断会话控制(Diagnostic Session Control)
  15. 论文精度MISC: A MIxed Strategy-Aware Model Integrating COMET for Emotional Support Conversation
  16. win10计算机睡眠怎么设置密码,windows10系统设置待机密码的图文教程
  17. 髂嵴最高点在哪里_两侧髂嵴最高点连线约平 ( )
  18. Google Earth Engine(GEE)——S2影像异常值
  19. 使用XGBoost在Python中进行特征重要性分析和特征选择
  20. 协议森林10 魔鬼细节 (TCP滑窗管理)

热门文章

  1. POJ2083-Fractal【分形,分治】
  2. 初二模拟赛总结(2019.8.7)
  3. [XSY3383]多线程(笛卡尔树,DP)
  4. SpringCloud Greenwich(五)之nacos、dubbo、Zuul和 gateway集成
  5. SpringCloud Zuul(十)之配置路由prefix坑
  6. hashCode到底有什么用?
  7. jquery动画与事件案例
  8. Servelt 中文乱码
  9. 在gitee上创建自己的仓库步骤
  10. java中的tostring_java 中重写toString()方法