令牌桶 java_服务限流(自定义注解令牌桶算法)

令牌桶 java_服务限流(自定义注解令牌桶算法)服务限流自定义注解基于 RateLimiter 实现接口限流令牌桶限流算法图片来自网上令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌 当有浏览来时取走一个或者多个令牌 当发生高并发情况下拿到令牌的执行业务逻辑 没有获取到令牌的就会丢弃获取服务降级处理 提示一个友好的错误信息给用户 2 RateLimiter 简单实现 maven 依赖 com google guavaguava18 0 本人使用的

服务限流 — 自定义注解基于RateLimiter实现接口限流

令牌桶限流算法

1372c15b2a0dc00927b3c1843830f3a0.png

图片来自网上

令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有浏览来时取走一个或者多个令牌,当发生高并发情况下拿到令牌的执行业务逻辑,没有获取到令牌的就会丢弃获取服务降级处理,提示一个友好的错误信息给用户。

2. RateLimiter简单实现

maven依赖

com.google.guava

guava

18.0

本人使用的是SpringBoot 2.0.4.RELEASE,Jdk1.8环境下编写,部分代码贴出:

/

* 以1r/s往桶中放入令牌

*/

private RateLimiter limiter = RateLimiter.create(1.0);

private SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

@GetMapping(“/indexLimiter”)

public String indexLimiter() {

// 如果用户在500毫秒内没有获取到令牌,就直接放弃获取进行服务降级处理

boolean tryAcquire = limiter.tryAcquire(500, TimeUnit.MILLISECONDS);

if (!tryAcquire) {

log.info(“Error —时间:{},获取令牌失败.”, sdf.format(new Date()));

return “系统繁忙,请稍后再试.”;

}

log.info(“Success —时间:{},获取令牌成功.”, sdf.format(new Date()));

return “success”;

}

调用结果如下:

70918202c4bb019f6cbc2f5a609c1c95.png

使用RateLimiter注意的地方:

允许先消费,后付款,意思就是它可以来一个请求的时候一次性取走几个或者是剩下所有的令牌甚至多取,但是后面的请求就得为上一次请求买单,它需要等待桶中的令牌补齐之后才能继续获取令牌。

3.自定义注解实现基于接口限流

仔细看会发现上面的简单实现会造成我每个接口都要写一次限流方法代码很冗余,所以采用aop来使用自定义注解来实现。

maven依赖

org.springframework.boot

spring-boot-starter-aop

org.springframework.boot

spring-boot-starter-web

com.google.guava

guava

18.0

org.projectlombok

lombok

true

首先定义一个自定义注解:

package com.limiting.annotation;

import java.lang.annotation.*;

import java.util.concurrent.TimeUnit;

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.METHOD})

@Documented

public @interface AnRateLimiter {

//以固定数值往令牌桶添加令牌

double permitsPerSecond () ;

//获取令牌最大等待时间

long timeout();

// 单位(例:分钟/秒/毫秒) 默认:毫秒

TimeUnit timeunit() default TimeUnit.MILLISECONDS;

// 无法获取令牌返回提示信息 默认值可以自行修改

String msg() default “系统繁忙,请稍后再试.”;

}

然后使用aop的环绕通知来拦截注解,使用了一个ConcurrentMap来保存每个请求对应的令牌桶,key是没有url请求,防止出现每个请求都会新建一个令牌桶这么会达不到限流效果.

package com.limiting.aspect;

import com.google.common.collect.Maps;

import com.google.common.util.concurrent.RateLimiter;

import com.limiting.annotation.AnRateLimiter;

import lombok.extern.slf4j.Slf4j;

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 org.springframework.util.StringUtils;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.io.PrintWriter;

import java.lang.reflect.Method;

import java.util.Map;

import java.util.Objects;

/

*  * 描述:

*

* @author 只写BUG的攻城狮

*  * @date 2018-09-12 12:07

*/

@Slf4j

@Aspect

@Component

public class RateLimiterAspect {

/

* 使用url做为key,存放令牌桶 防止每次重新创建令牌桶

*/

private Map limitMap = Maps.newConcurrentMap();

@Pointcut(“@annotation(com.limiting.annotation.AnRateLimiter)”)

public void anRateLimiter() {

}

@Around(“anRateLimiter()”)

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

// 获取request,response

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

// 或者url(存在map集合的key)

String url = request.getRequestURI();

// 获取自定义注解

AnRateLimiter rateLimiter = getAnRateLimiter(joinPoint);

if (rateLimiter != null) {

RateLimiter limiter = null;

// 判断map集合中是否有创建有创建好的令牌桶

if (!limitMap.containsKey(url)) {

// 创建令牌桶

limiter = RateLimiter.create(rateLimiter.permitsPerSecond());

limitMap.put(url, limiter);

log.info(“<<================= 请求{},创建令牌桶,容量{} 成功!!!", url, rateLimiter.permitsPerSecond());

}

limiter = limitMap.get(url);

// 获取令牌

boolean acquire = limiter.tryAcquire(rateLimiter.timeout(), rateLimiter.timeunit());

if (!acquire) {

responseResult(response, 500, rateLimiter.msg());

return null;

}

}

return joinPoint.proceed();

}

/

* 获取注解对象

* @param joinPoint 对象

* @return ten LogAnnotation

*/

private AnRateLimiter getAnRateLimiter(final JoinPoint joinPoint) {

Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods();

String name = joinPoint.getSignature().getName();

if (!StringUtils.isEmpty(name)) {

for (Method method : methods) {

AnRateLimiter annotation = method.getAnnotation(AnRateLimiter.class);

if (!Objects.isNull(annotation) && name.equals(method.getName())) {

return annotation;

}

}

}

return null;

}

/

* 自定义响应结果

*

* @param response 响应

* @param code 响应码

* @param message 响应信息

*/

private void responseResult(HttpServletResponse response, Integer code, String message) {

response.resetBuffer();

response.setHeader(“Access-Control-Allow-Origin”, “*”);

response.setHeader(“Access-Control-Allow-Credentials”, “true”);

response.setContentType(“application/json”);

response.setCharacterEncoding(“UTF-8”);

PrintWriter writer = null;

try {

writer = response.getWriter();

writer.println(“{\”code\”:” + code + ” ,\”message\” :\”” + message + “\”}”);

response.flushBuffer();

} catch (IOException e) {

log.error(” 输入响应出错 e = {}”, e.getMessage(), e);

} finally {

if (writer != null) {

writer.flush();

writer.close();

}

}

}

}

最后来试试自己定义的注解是否生效,能否达到限流效果.

@GetMapping(“/index”)

@AnRateLimiter(permitsPerSecond = 1, timeout = 500, timeunit = TimeUnit.MILLISECONDS,msg = “亲,现在流量过大,请稍后再试.”)

public String index() {

return System.currentTimeMillis() + “”;

}

访问请求(按F5狂刷新浏览器)效果如下图:

447f7e7fb5def1a6f8053aa19d8223c4.png

总结

至此已基本上使用注解实现了接口限流,后期可以根据自己需求自行修改,这个只适于单个应用进行接口限流,如果是分布式项目或者微服务项目可以采用redis来实现,后期有时间来一个基于redis自定义注解来实现接口限流。

本人也是刚入Java开发行业没多久的小菜鸟,在文章中可能存在一些说的不对,代码不严谨的地方欢迎各位大神指出,本人表示由衷的感谢和耐心的学习,希望能在开发中给大家一些帮助。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/179340.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午3:48
下一篇 2026年3月26日 下午3:49


相关推荐

  • pycharm2021.8.2激活码_最新在线免费激活

    (pycharm2021.8.2激活码)好多小伙伴总是说激活码老是失效,太麻烦,关注/收藏全栈君太难教程,2021永久激活的方法等着你。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.htmlS32PGH0SQB-eyJsaWNlbnNlSWQi…

    2022年3月25日
    83
  • 安卓(android)建立项目时失败,出现Android Manifest.xml file missing几种解决方法?(总结中)[通俗易懂]

    安卓(android)建立项目时失败,出现Android Manifest.xml file missing几种解决方法?(总结中)

    2022年2月1日
    53
  • 菜单权限表sql语句「建议收藏」

    菜单权限表sql语句「建议收藏」selectm.*     fromt_menum     wherem.state=’1′      and((menu_type=1andexists        (select1          fromv_user_menub          wherem.menu_id…

    2025年6月13日
    5
  • 百度快照更新方法「建议收藏」

    百度快照更新方法「建议收藏」大家在建站过程中,可能都会遇到百度快照不更新的问题。我也不例外,帮朋友维护的一网站(QQ空间留言代码)百度快照时间停在了3月1号,一直到7号也没更新,而同类网站的百度快照几乎天天更新。我这里说的是百度快照不更新,但网站的关键字排名却没有发生变化。分析原因。这个网站(www.xkyy18.cn)也没有作弊的地方,有的只是适当的优化;在1号当天网站首页增加了一个信息小版块,链接增加了28个,然后

    2022年10月4日
    4
  • pytest运行_pytest执行多个py文件

    pytest运行_pytest执行多个py文件前言pytest运行完用例之后会生成一个.pytest_cache的缓存文件夹,用于记录用例的ids和上一次失败的用例。方便我们在运行用例的时候加上–lf和–ff参数,快速运行上一

    2022年7月31日
    12
  • 跨平台移动APP开发进阶(一):mui开发注意事项

    跨平台移动APP开发进阶(一):mui开发注意事项mui开发注意事项MuiHTML5开发框架mui是一个高性能的HTML5开发框架,从UI到效率,都在极力追求原生体验;这个框架自身有一些规则,刚接触的同学不很熟悉,特总结本文;想了解mui更详细的信息,请访问mui官网。DOM结构关于mui页面的dom,你需要知道如下规则:固定栏靠前所谓的固定栏,也就是带有.mui-bar(类选择器)属性的节点,都是基于fixed定位的元

    2022年5月31日
    34

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号