手写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是云风设计的序列化协议,用 ...
随机推荐
- Spring 中的 Resource和ResourceLoader
Spring 内部框架使用org.springframework.core.io.Resource接口作为所有资源的抽象和访问接口.Resource接口可以根据资源的不同类型,或者资源所处的不同场合, ...
- 3280 easyfinding
3280 easyfinding 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题解 查看运行结果 题目描述 Description 给一个M ...
- docker-compose安装confluence
1.首先安装docker-compose pip install docker-compose 安装完成提示: 2.编写mysql-confluence-compose ...
- angularjs 遇见$scope,xxx=function()报错为该函数未定义
本包子今天遇见一个问题,就是明明写了$scope,xx=function()但是报错了,报错显示是该函数未定义,我就很着急的先将函数写成一个全局函数,就没问题,等下午有空的时候寻思了一下,为什么全局就 ...
- JavaScript数据结构与算法-列表练习
实现列表类 // 列表类 function List () { this.listSize = 0; // 列表的元素个数 this.pos = 0; // 列表的当前位置 this.dataStor ...
- 002-基本业务搭建【日志,工具类dbutils,dbcp等使用】
一.需求分析 1.1.概述 1.用户进入“客户管理”,通过列表方式查看用户: 2.客户名称,模糊查询用户列表 3.客户名称,可查看客户详细信息 4.新增.编辑.删除功能等 二.系统设计 需要对原始需求 ...
- 图像分割之mean shift
阅读目的:理解quick shift,同时理解mean shift原理,mean shift用于图像聚类,优点是不需要指定聚类中心个数,缺点是计算量太大(原因). mean shift主要用来寻找符合 ...
- Python替换文件内容
#!/usr/bin/env python import fileinput for line in fileinput.input('fansik',inplace=1): line = line. ...
- 使用 C#的 is 和 as 操作符来转型
在 C#语言中进行类型转换的另一种方式是使用 is 操作符. is 检查一个对象是否兼容于指定的类型,并返回一个 Boolean 值: true 或 false.注意 is 操作符永远不会抛出异常,以 ...
- Tips for Unix/Linux
@1: 在单个命令中创建目录树:不要逐层创建目录,尽量使用mkdir的-p选项: ~$ mkdir -p one/two/three # 假设目录one不存在 创建复杂的目录树: ~$ mkdir - ...