手写RateLimiter
自定义注解 封装
如果需要让接口实现限流RateLimiter使用
网关:一般拦截所有的接口 实现限流 秒杀 抢购 或者大流量的接口才会实现限流。灵活
不是所有接口都需要限流 秒杀等接口需要限流
设计: 加注解的才可以实现限流
注解形式而不是网关形式 只有需要限流的才加这个注解
传统的方式整合RateLimiter有很大缺点:代码重复量特别大,而且本身不支持注解方式
限流代码可以写在网关,相当于针对所有接口实现限流,维护性不强
不是所有的接口都需要限流 一般限流主要针对大流量,比如秒杀抢购
分析案例:
定义一个自定义注解
Spring Boot整合 spring aop
使用环绕通知
判断请求方法上是否有 注解
如果有 使用反射获取注解方法上的参数
调用原生RateLImiter方法创建令牌
如果获取令牌超时 直接调用服务降级(自己定义)
如果能够获取令牌 直接进入实际请求方法
本案例没有用到 扫包 直接请求过来走方法的
首先自定义注解:
引入maven依赖:
<!-- springboot 整合AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>pom
pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.toov5</groupId>
<artifactId>springboot-guava</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
<!-- springboot 整合AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>
封装注解:
package com.toov5.annotation; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtRateLimiter {
//以秒为单位 固定的速录往桶中添加
double permitsPerSecond(); //在规定的时间内,如果没有获取到令牌的话,直接走降级处理
long timeout();
}
aop封装:
package com.toov5.aop; import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; 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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import com.google.common.util.concurrent.RateLimiter;
import com.toov5.annotation.ExtRateLimiter; //aop环绕通知 判断拦截所有springmvc请求,判断请求方法上是否存在ExtRateLimiter @Aspect //aop两种方式 注解 和 xml方式
@Component
public class RateLimiterAop {
// 存放接口是否已经存在
private static ConcurrentHashMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<String, RateLimiter>(); //定义切入点 拦截
@Pointcut("execution(public * com.toov5.controller.*.*(..))") //所有类 所有方法 任意参数
public void rlAop() {
} //使用aop环绕通知判断拦截所有springmvc请求,判断方法上是否存在ExRanteLimiter
@Around("rlAop()")
public Object doBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//1、判断请求方法上是否存在@RxtRateLimiter注解
//2、如果请求方法上存在此注解@RxtRateLimiter注解 比如加上了RequestMapping表示请求方法
Method sinatureMethod = getSinatureMethod(proceedingJoinPoint);
if (sinatureMethod == null) {
//直接报错
return null;
} //3、使用Java的反射机制获取拦截方法上自定义注解的参数
ExtRateLimiter extRateLimiter = sinatureMethod.getDeclaredAnnotation(ExtRateLimiter.class);
if (extRateLimiter==null) { //方法上没有注解
//直接放行代码 进入实际请求方法中
proceedingJoinPoint.proceed();
}
//4、调用原生RateLimiter创建令牌
double permitsPerSecond = extRateLimiter.permitsPerSecond(); //获取参数
long timeout = extRateLimiter.timeout();
//调用原生的RateLimiter创建令牌 保证每个请求对应的是单例的RateLimiter 一个请求一个RateLimiter 使用hashMap
RateLimiter.create(permitsPerSecond);
String requestURI = getRequestUrl();
RateLimiter rateLimiter = null;
if (rateLimiterMap.containsKey(requestURI)) {
//如果检测到
rateLimiter = rateLimiterMap.get(requestURI);
}else {
//如果没有 则添加
rateLimiter = RateLimiter.create(permitsPerSecond);
rateLimiterMap.put(requestURI, rateLimiter);
} //5、获取桶中的令牌,如果没有有效期获取到令牌,直接调用降级方法。
boolean tryAcquire = rateLimiter.tryAcquire(timeout,TimeUnit.MILLISECONDS);
if (!tryAcquire) {
//服务降级
fallback();
return null;
}
//6、否则 直接进入到实际请求方法中 return proceedingJoinPoint.proceed();
}
private void fallback() throws IOException {
//在aop编程中获取响应
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = attributes.getResponse();
//防止乱码
response.setHeader("Content-type", "text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
try {
writer.println("亲,别抢了");
} catch (Exception e) {
writer.close();
} }
private String getRequestUrl() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return request.getRequestURI();
}
private Method getSinatureMethod(ProceedingJoinPoint proceedingJoinPoint) {
//获取到目标代理对象
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
//获取当前aop拦截的方法
Method method = signature.getMethod();
return method;
}
}
controller
package com.toov5.controller; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import com.google.common.util.concurrent.RateLimiter;
import com.toov5.annotation.ExtRateLimiter;
import com.toov5.service.OrderService; @RestController
public class IndexController {
@Autowired
private OrderService orderService;
//create方法中传入一个参数 以秒为单位固定的速率值 1r/s 往桶中存入一个令牌
RateLimiter rateLimiter = RateLimiter.create(1); //独立线程!它自己是个线程 //相当于接口每秒只能接受一个客户端请求
@RequestMapping("/addOrder")
public String addOrder() {
//限流放在网关 获取到当前 客户端从桶中获取对应的令牌 结果表示从桶中拿到令牌等待时间
//如果获取不到令牌 就一直等待
double acquire = rateLimiter.acquire();
System.out.println("从桶中获取令牌等待时间"+acquire);
boolean tryAcquire=rateLimiter.tryAcquire(500,TimeUnit.MILLISECONDS); //如果在500sms没有获取到令牌 直接走降级
if (!tryAcquire) {
System.out.println("别抢了,等等吧!");
return "别抢了,等等吧!";
}
//业务逻辑处理
boolean addOrderResult = orderService.addOrder();
if (addOrderResult) {
System.out.println("恭喜抢购成功!等待时间");
return "恭喜抢购成功!";
} return "抢购失败!";
}
//以每秒1个的速度往桶中添加令牌
@RequestMapping("/findIndex")
@ExtRateLimiter(permitsPerSecond=1.0,timeout=500)
public void findIndex() {
System.out.println("findIndex"+System.currentTimeMillis());
} }
service
package com.toov5.service; import org.springframework.stereotype.Service; @Service
public class OrderService { public boolean addOrder() {
System.out.println("db...正在操作订单表数据库");
return true;
}
}
启动类
package com.toov5; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args); }
}
疯狂点击:

手写RateLimiter的更多相关文章
- 【Win 10 应用开发】手写识别
记得前面(忘了是哪天写的,反正是前些天,请用力点击这里观看)老周讲了一个14393新增的控件,可以很轻松地结合InkCanvas来完成涂鸦.其实,InkCanvas除了涂鸦外,另一个大用途是墨迹识别, ...
- JS / Egret 单笔手写识别、手势识别
UnistrokeRecognizer 单笔手写识别.手势识别 UnistrokeRecognizer : https://github.com/RichLiu1023/UnistrokeRecogn ...
- 如何用卷积神经网络CNN识别手写数字集?
前几天用CNN识别手写数字集,后来看到kaggle上有一个比赛是识别手写数字集的,已经进行了一年多了,目前有1179个有效提交,最高的是100%,我做了一下,用keras做的,一开始用最简单的MLP, ...
- 【转】机器学习教程 十四-利用tensorflow做手写数字识别
模式识别领域应用机器学习的场景非常多,手写识别就是其中一种,最简单的数字识别是一个多类分类问题,我们借这个多类分类问题来介绍一下google最新开源的tensorflow框架,后面深度学习的内容都会基 ...
- caffe_手写数字识别Lenet模型理解
这两天看了Lenet的模型理解,很简单的手写数字CNN网络,90年代美国用它来识别钞票,准确率还是很高的,所以它也是一个很经典的模型.而且学习这个模型也有助于我们理解更大的网络比如Imagenet等等 ...
- 使用神经网络来识别手写数字【译】(三)- 用Python代码实现
实现我们分类数字的网络 好,让我们使用随机梯度下降和 MNIST训练数据来写一个程序来学习怎样识别手写数字. 我们用Python (2.7) 来实现.只有 74 行代码!我们需要的第一个东西是 MNI ...
- 手写原生ajax
关于手写原生ajax重要不重要,各位道友自己揣摩吧, 本着学习才能进步,分享大家共同受益,自己也在自己博客里写一下 function createXMLHTTPRequest() { //1.创建XM ...
- springmvc 动态代理 JDK实现与模拟JDK纯手写实现。
首先明白 动态代理和静态代理的区别: 静态代理:①持有被代理类的引用 ② 代理类一开始就被加载到内存中了(非常重要) 动态代理:JDK中的动态代理中的代理类是动态生成的.并且生成的动态代理类为$Pr ...
- 为sproto手写了一个python parser
这是sproto系列文章的第三篇,可以参考前面的<为sproto添加python绑定>.<为python-sproto添加map支持>. sproto是云风设计的序列化协议,用 ...
随机推荐
- 【Cocos游戏实战】功夫小子第八课之游戏打包和相关问题说明
至此,功夫小子系列的Cocos2d-x的实战文章就结束了. 源代码地址: https://github.com/SuooL/KungfuBoy 如须要资源请邮件我 1020935219@qq.com ...
- 用ElasticSearch搭建自己的搜索和分析引擎【转自腾讯Wetest】
本文大概地介绍了ES的原理,以及Wetest在使用ES中的一些经验总结.因为ES本身涉及的功能和知识点非常广泛,所以这里重点挑出了实际项目中可能会用到,也可能会踩坑的一些关键点进行了阐述. 一 重要概 ...
- Android OpenCV集成摄像头图片动态识别车牌号
最近两天开发一个使用OpenCV集成的一个识别车牌号的项目,困难重重,总结一下相关经验,以及开发注意事项: 一.开发环境: Android Studio 个人版本 3.1.4 NDK下载:14b CM ...
- 2、ACE-实用生活口语-介绍 Introductions
(1) (2) (3)认识你很荣幸,如何称呼您呢?It's a pleasure to meet you.I'm glad to meet you.May I have your name?How s ...
- 【BZOJ2140】稳定婚姻 Tarjan
[BZOJ2140]稳定婚姻 Description 我国的离婚率连续7年上升,今年的头两季,平均每天有近5000对夫妇离婚,大城市的离婚率上升最快,有研究婚姻问题的专家认为,是与简化离婚手续有关. ...
- kafka 集群安装过程
1.下载需要的安装包 http://kafka.apache.org/downloads.html 本文使用的 Scala 2.9.2 - kafka_2.9.2-0.8.2.2.tgz (asc, ...
- Introduction to Mathematical Thinking - Week 6 - Proofs with Quantifieers
Mthod of proof by cases 证明完所有的条件分支,然后得出结论. 证明任意 使用任意 注意,对于一个任意的东西,你不知道它的具体信息.比如对于任意正数,你不知道它是 1 还是 2等 ...
- KVC示例
KVC –key value Coding,可以让我们通过键值编码的形式进行属性值的赋值 参考苹果官网的图.. 1.KVC 定义一个Person类 .h文件 1: #import <Founda ...
- 一篇搞定spring Jpa操作数据库
开始之前你必须在项目配置好数据库,本文使用的spring boot,相比spring,spring boot省去了很多各种对以来组件复杂的配置,直接在pom配置组件,完后会自动帮我们导入组件 < ...
- VMware Workstation 虚拟机纯 Linux 终端如何安装 VMware Tools ?
VMware Workstation 虚拟机纯 Linux 终端如何安装 VMware Tools ? 1.首先在虚拟机设置里面设置一个共享文件夹 2.在虚拟机菜单栏中选择 VMware Tools ...