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

外部服务或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 Framework,在任何情况下都可以使用方面。

@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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/357705.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Block(Closure) Tips

使用 Block 的时候谨记以下几点&#xff1a; 1.Block类型&#xff1a;全局块&#xff08;Global Block&#xff09;和堆块&#xff08;Heap Block&#xff09;&#xff0c;以及栈块&#xff08;Stack Block&#xff09;。2.变量捕获: 默认无法修改变量&#xff0c;需要添加 __b…

【APICloud系列|33】通过程序循环数据集合的时候闭包加入imageCache方法

导读:一般实现的两种思路 1、通过程序循环数据集合的时候闭包加入imageCache方法。 2、通过递归数据集合实现 发现都不是我理想的效果,数据集合量较大的时候imageCache处理的时间比不用imageCache展示的时间要慢很多,展示会有明显延迟,最终采取以下方法 还是for循环将html拼…

Linux最危险的几个命令

Linux最危险的几个命令 Linux最危险的几个命令危险命令介绍删除文件和目录命令rmLinux 的 dd 命令mkfs 格式化硬盘分区shutdown> fileMore Linux最危险的几个命令 仅个人想法&#xff0c;会持续不间断更新和改进。 Linux系统中的命令最美妙也最危险。 如果几个操作系统&…

php维护session,维护带有cookie的PHP session_start()

我有一个PHP代码,可以使用session_start()启动会话.好了,在用户登录后,将被带到profile.php,其中显示了该用户信息.但是,当用户重新加载页面时,会话消失了.例如,有什么办法可以维持一个小时吗&#xff1f;我已经尝试过cookie,但是我不知道如何告诉PHP该会话已经开始.谢谢&#…

【APICloud系列|34】免费使用的ChromeDebug 模块

导读&#xff1a;很多人想要能够实现debug功能&#xff0c;即能够调试js&#xff0c;都说WeX5能够实现。。。 其实这种方式很简单的&#xff0c;在模块专区也有专门的模块提供&#xff0c;但是有人却抱怨收费&#xff0c;现在提供一个免费模块包&#xff0c;方便开发者使用。 使…

java asm tree_使用ASM 4处理Java类文件–第二部分:Tree API

java asm tree什么是ASM树API&#xff1a; ASM树API是ASM的一部分&#xff0c;可让您创建/修改内存中的类。 该类被视为信息树。 像整个类一样&#xff0c;它是ClassNode的实例&#xff0c;其中包含FieldNode对象的列表&#xff0c;MethodNode对象的列表等。本文假设读者已经在…

php 验证码文件,php实现的验证码文件类实例

本文实例讲述了php实现的验证码文件类。分享给大家供大家参考。具体如下&#xff1a;/*** file* version 1.0* author 网海浪子* brief 验证码文件类**/class ccheckcodefile{//验证码位数private $mcheckcodenum 4;//产生的验证码private $mcheckcode ;//验证码的图片privat…

【概率与期望】[UVA11021]Tribles

题目大意 k只麻球&#xff0c;每活一天就会死亡&#xff0c;但第二天可能会生一些麻球&#xff0c;具体是 生i个麻球的概率为pi ,求m天后所有麻球都死亡的概率。 LRJ such a dog&#xff0c;你给我个错的翻译。 分析 用f(i)表示一开始有1只麻球&#xff0c;i天后死亡的概率。 …

【APICloud系列|35】APICLloud开源官方模块

目前官方开源的模块有: 1,bMap(百度地图):https://github.com/apicloudcom/bMap 2,UIListView(可侧滑item的列表):https://github.com/apicloudcom/UIListView 3,aMap(高德地图):https://github.com/apicloudcom/aMap 4,UIPullRefresh(下拉刷新):https://gith…

Java无处不在:使用DukeScript在任何地方运行一次编写

在相当长一段时间内&#xff0c;Java都未能兑现“一次编写&#xff0c;随处运行”的承诺。 DukeScript希望通过在跨平台应用程序中实现视图和逻辑的清晰分离来改变这种状况。 在本文中&#xff0c;一个简单的场景用于介绍DukeScript的基础。 多年来&#xff0c;Java Swing使开…

数据仓库建设中的数据建模方法(转)

简介&#xff1a; 本文的主要内容不是介绍现有的比较流行的主要行业的一些数据模型&#xff0c;而是将笔者在数据仓库建设项目中的一些经验&#xff0c;在这里分享给大家。希望帮助大家在数据仓库项目建设中总结出一套能够合乎目前业界规范的&#xff0c;满足大部分行业数据仓库…

php实现标签云,php标签云的实现代码

数据库中&#xff0c;存放文章的表中有“Tag”字段&#xff0c;用来存放标签。标签之间用“,”分隔。比如“PHP,VB,随笔”。下面的实现代码&#xff0c;将标签从数据库中搜出来&#xff0c;并格式化处理&#xff0c;使其以出现的次数为依据显示出不同大小的文字连接。其中的细节…

【APICloud系列|36】 mobVerify免费短信验证码的实现

使用mobVerify之前,请到mob官网注册开发者账号,并申请api,大致流程如下: 1、网址:http://www.mob.com 2、注册登陆后鼠标放在右上角头像处即可看到"进入后台",点击进入 3、点击SecurityCodeSDK进入短信管理界面 4、点击顶部导航中的创建应用,填写信息即可获取…

PAT 1065 A+B and C (64bit) (20)

1065. AB and C (64bit) (20) 时间限制 100 ms内存限制 65536 kB代码长度限制 16000 B判题程序 Standard作者 HOU, QimingGiven three integers A, B and C in [-263, 263], you are supposed to tell whether AB > C. Input Specification: The first line of the input gi…

十大有用但又偏执的Java编程技术

经过一段时间的编码&#xff08;以我为例&#xff0c;大约20年左右&#xff0c;当您玩得开心时光飞逝&#xff09;&#xff0c;人们开始接受这些习惯。 因为&#xff0c;你知道... 任何可能出错的事情都会发生。 这就是为什么人们会采用“防御性编程”的原因&#xff0c;即偏执…

php 接入微信 验证,PHP实现微信公众平台企业号验证接口

这篇文章主要介绍了PHP编程之微信公众平台企业号验证接口,是通过回调操作实现的企业号验证功能接口,需要的朋友可以参考下本文实例讲述了PHP微信公众平台企业号验证接口。分享给大家供大家参考&#xff0c;具体如下&#xff1a;微信公众平台企业号验证接口、回调 PHP版&#xf…

【APICloud系列|37】 银联支付的实现

正式使用请与银联签约端调用方法&#xff1a;xlUnionPayapi.require(xlUnionPay); xlUnionPay .xlUnionPay({params},callback(ret,err))params: spId &#xff1a;银联保留参数&#xff0c;默认为null&#xff08;Android用&#xff09; sysProvider &#xff1a;银联保留参数…

NGUI 3.5教程(二)Label 标签 (Hello world)、多行文本

写在前面&#xff1a; 本文将创建NGUI的第一个样例。依照编程传统&#xff0c;第一个样例&#xff0c;就是做一个Hello world 显示出来。NGUI。我们用Label来实现 。欢迎大家纠错、拍砖&#xff01;原创非常辛苦&#xff0c;如有转载&#xff0c;请注明出处&#xff1a;htt…

【APICloud系列|38】 微信登录分享、QQ登录分享实现方法

对微信登录分享、QQ登录分享进行一个流程性的讲解。在微信分享经常是分享不成功或者图片不显示,主要问题是图片过大或者是没有本地化。 1、流程:使用auth进行授权--->getToken获取用户信息---->同步至服务端 注意:在安卓端可以提示用户没有安装微信端,但是ios端切…

PHP发送数据到指定方法,php通过header发送自定义数据方法_php技巧

下面小编就为大家分享一篇php通过header发送自定义数据方法&#xff0c;具有很好的参考价值&#xff0c;希望对大家有所帮助。一起跟随小编过来看看吧本文将介绍如何通过header发送自定义数据。发送请求时&#xff0c;除了可以使用$_GET/$_POST发送数据&#xff0c;也可以把数据…