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

令牌桶限流算法

图片来自网上

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

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";

}

调用结果如下:

使用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狂刷新浏览器)效果如下图:

总结

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

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

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

  1. 微服务限流及熔断一:四种限流算法(计数器算法、滑动窗口算法、令牌限流算法、漏桶限流算法)

    引言 本篇内容根据<spring cloud alibaba 微服务原理与实战>中内容摘取,希望和大家分享限流的思想,本篇不涉及代码层面的实现. 限流的目的 目的:通过限制并发访问数或者限 ...

  2. 03.服务限流实现方案

    Sentinel概述 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流.流量整形.熔断降级.系统负载保护等 ...

  3. 互联网高并发解决方案(2)--高并发服务限流特技

    RPC和本地JAVA调用的区别 RPC远程调用:一般是可以跨平台使用的,采用Socket技术,只要语言支持socket技术就可以进行互相通信.其实就是socket+反射实现的. 本地调用:只能支持Ja ...

  4. Spring Cloud Alibaba | Sentinel: 服务限流高级篇

    Spring Cloud Alibaba | Sentinel: 服务限流高级篇 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如无特殊说明 ...

  5. 微服务架构服务限流方案详解

    话说在 Spring Cloud Gateway 问世之前,Spring Cloud 的微服务世界里,网关一定非 Netflix Zuul 莫属.但是由于 Zuul 1.x 存在的一些问题,比如阻塞式 ...

  6. 12张图带你彻底搞懂服务限流、熔断、降级、雪崩

    目录 一.服务雪崩 二.服务限流 1.限流指标 1)TPS 2)HPS 3)QPS 2.限流方法 1)流量计数器 2)滑动时间窗口 3)漏桶算法 4)令牌桶算法 5)分布式限流 6)hystrix限流 ...

  7. 亿级流量架构之服务限流思路与方法

    为什么要限流 日常生活中,有哪些需要限流的地方? 像我旁边有一个国家AAAA景区,平时可能根本没什么人前往,但是一到五一或者春节就人满为患,这时候景区管理人员就会实行一系列的政策来限制进入人流量, 为 ...

  8. 微服务架构 — 服务治理 — 服务限流、服务降级、服务熔断

    目录 文章目录 目录 服务限流 服务降级 服务熔断 服务限流 C ⇄ S 的异常问题:C 的请求太多,超出 S 的服务能力,导致 S 不可用.例如:DoS 攻击,企图耗尽被攻击对象的资源,让目标系统无 ...

  9. 架构设计之「服务限流」

    架构设计之「服务限流」 原文:架构设计之「服务限流」 上一篇我们聊过了架构设计中的「服务隔离」模式,今天我们继续来探索一下在分布式系统架构中的另一个常用的设计:服务限流. 那么,什么是「服务限流」呢? ...

  10. 通过Dapr实现一个简单的基于.net的微服务电商系统(七)——一步一步教你如何撸Dapr之服务限流...

    在一般的互联网应用中限流是一个比较常见的场景,也有很多常见的方式可以实现对应用的限流比如通过令牌桶通过滑动窗口等等方式都可以实现,也可以在整个请求流程中进行限流比如客户端限流就是在客户端通过随机数直接 ...

最新文章

  1. 客快物流大数据项目(九):Docker常用命令
  2. CIKM投稿数量1700篇,图神经网络成热门方向,最佳论文纷纷进行图研究
  3. 如何做好SOC的一点点体会
  4. java listfiles 使用_Java中list()和listFiles()方法之间的区别
  5. Selenium实战脚本集(3)--抓取infoq里的测试新闻
  6. python是什么语言汇编_编程语言及python介绍
  7. Windows Server 2008 部署权限管理RMS
  8. 列运算_DAX表操作基础第二招:增加列
  9. java常用的搜索引擎_我掏空了各大搜索引擎,给你整理了154道Java面试题!
  10. 腾讯QQ看点信息流推荐业务:内容分发场景的多目标架构实践
  11. 如何在 Quagga BGP 路由器中设置 IPv6 的 BGP 对等体和过滤
  12. STL:STL各种容器的使用时机详解
  13. 腾讯信鸽推送基本流程和数据的处理流程
  14. 中介者(Mediator)模式实例
  15. 2020年短视频元年火爆来袭
  16. JRebel安装、使用
  17. 消息队列RabbitMQ基本使用(Java代码实现)
  18. scp_linux之间互传文件
  19. Java C#分析WAV音频文件1Khz是否有声音
  20. (开源)简单的人脸识别考勤系统(python+opencv+dilb)

热门文章

  1. 最小可分辨温差四杆靶空间频率选择
  2. Geek(一个好用的强力卸载软件工具,包括注册表所有依赖项全部清理掉)
  3. 中英文网站googleSEO优化技巧
  4. PX4固件通过UART连接串口读取超声波,和树莓派3通信(似乎讲了怎么添加驱动程序,添加自定义msg,还有uORB消息订阅,佩服,感觉做了我想做的!)
  5. 计算机组装与维修标准教程,计算机组装与维护标准教程(2008版)
  6. 皮尔逊相关系数,斯皮尔曼等级相关系数,(易错!!)假设检验 ,SPSS
  7. c语言万年历查询程序代码,C语言实现万年历程序
  8. 新手成为黑客,需要掌握电脑网络命令汇总
  9. Vulkan_Ray Tracing 08_光照、材质、阴影
  10. FlashFxp 设置主动模式