限流神器之-Guava RateLimiter 实战
前段时间,项目中需要对某些访问量较高的路径进行访问并发数控制,以及有些功能,比如Excel导出下载功能,数据量很大的情况下,用户不断的点击下载按钮,重复请求数据库,导致线上数据库挂掉。于是在这样的情况下,这个限流组件应运而生,也许有人会提及SpringCloud zuul,其实它的现也是借助了RateLimiter。由于项目使用的是SpringBoot,也就没往外思考。反正最后功能实现了就行,毕竟殊途同归啊。本文只是用代码来快速的帮你理清整个限流的流程,至于RateLimiter中具体限流算法以及Semaphore信号量的具体实现还是得自己去深挖了,这里就不再展开了。正片时间到:
0、由于需要对限流的路径进行后台管理,那限流实体肯定是需要的
public class RateLimit {
private String rateLimitId;
/**
* 限流路径,支持通配符,示例 /user/**
*/
private String limitPath;
/**
* 每秒限流频率
*/
private Integer permitsPerSecond;
/**
* 限流等待超时时间,单位s
*/
private Integer permitsTimeOut;
/**
* 排序
*/private Integer orderNo;
/**
* 最大线程数
*/
private Integer maxThread;
/**
* 创建时间
*/
private Date gmtCreate;
//get、set略
}
1、因为要借助RateLimiter类,所以再封装一个限流信息类
/**
* @描述: 限流信息
*/
public class RateLimitInfo { private RateLimiter rateLimiter; private RateLimitVo rateLimitVo; private long lastUpdateTime; public RateLimitInfo(RateLimiter rateLimiter, RateLimitVo rateLimitVo, long lastUpdateTime) {
this.rateLimiter = rateLimiter;
this.rateLimitVo = rateLimitVo;
this.lastUpdateTime = lastUpdateTime;
}
//get、set略
}
2、定义限流策略RateLimitStrategist
/**
* @描述: 限流策略
*/
public class RateLimitStrategist { private PathMatcher pathMatcher = new AntPathMatcher(); private final Map<String, RateLimitInfo> limiterMap = new LinkedHashMap<>(); private final Map<String, Semaphore> threadMap = new LinkedHashMap<>(); /**
* 更新频率,意为后台配置路径后5分钟生效
*/
private static final long UPDATE_RATE = 1000*60*5; private long lastUpdateTime = 0; @Autowired
private RateLimitManager rateLimitManager; public void init() {
limiterMap.clear();
threadMap.clear();
List<RateLimitVo> rateLimitVos = rateLimitManager.findListForPriority(); //查询数据库中配置的路径信息,需要自己实现
if(CollectionUtils.isNotEmpty(rateLimitVos)) {
return;
}
for (RateLimitVo rateLimitVo : rateLimitVos) {
RateLimiter rateLimiter = RateLimiter.create(rateLimitVo.getPermitsPerSecond());
limiterMap.put(rateLimitVo.getLimitPath(), new RateLimitInfo(rateLimiter, rateLimitVo, System.currentTimeMillis()));
threadMap.put(rateLimitVo.getLimitPath(), new Semaphore(rateLimitVo.getMaxThread(), true));
}
lastUpdateTime = System.currentTimeMillis();
} public boolean tryAcquire(String requestUri) {
//目前设置5分钟更新一次
if(System.currentTimeMillis() - lastUpdateTime > UPDATE_RATE) {
synchronized (this) {
if(System.currentTimeMillis() - lastUpdateTime > UPDATE_RATE) {
init();
}
}
} for (Map.Entry<String, RateLimitInfo> entry : limiterMap.entrySet()) {
if(!pathMatcher.match(entry.getKey(), requestUri)) {
continue;
}
RateLimitInfo rateLimitInfo = entry.getValue();
RateLimitVo rateLimitVo = rateLimitInfo.getRateLimitVo();
RateLimiter rateLimiter = rateLimitInfo.getRateLimiter();
boolean concurrentFlag = rateLimiter.tryAcquire(1, rateLimitVo.getPermitsTimeOut(), TimeUnit.SECONDS);
if(!concurrentFlag) { //验证失败,直接返回
return concurrentFlag;
} else {
if(threadMap.get(requestUri).availablePermits() != 0) { //当前路径对应剩余可执行线程数不为0
try {
//申请可执行线程
threadMap.get(requestUri).acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
} else {
return false;
}
}
}
return true;
} public void setLastUpdateTime(long lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
} /**
* 释放路径对应的线程数
* @param requestURI
*/
public void releaseSemaphore(String requestURI) {
if(null != threadMap.get(requestURI)) {
threadMap.get(requestURI).release();
}
}
}
3、定义拦截器RateLimitFilter,在拦截器中调用限流策略
/**
* @描述: 限流过滤器,配置后生效
*/
public class RateLimitFilter implements Filter { private RateLimitStrategist rateLimitStrategist; private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitFilter.class); @Override
public void init(FilterConfig filterConfig) throws ServletException { } @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if(rateLimitStrategist == null) {
rateLimitStrategist = InstanceFactory.getInstance(RateLimitStrategist.class);
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String requestURI = req.getRequestURI();
String contextPath = req.getContextPath();
if(StringUtils.isNotBlank(contextPath)) {
requestURI = StringUtils.substring(requestURI, contextPath.length());
}
if(!rateLimitStrategist.tryAcquire(requestURI)) {
res.setContentType("text/html;charset=UTF-8");
res.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("当前服务器繁忙,请稍后再试!");
LOGGER.info(requestURI + "路径请求服务器繁忙,请稍后再试");
} else {
try {
chain.doFilter(request, response);
} catch (Exception e) {
e.printStackTrace();
} finally {
rateLimitStrategist.releaseSemaphore(requestURI);
}
} } @Override
public void destroy() { }
}
4、需要的配置(采用注解也可以)
先在web.xml中引入过滤器(开始处)
<filter>
<filter-name>rateLimiter</filter-name>
<filter-class>com.limit.filter.RateLimitFilter</filter-class>
</filter> <filter-mapping>
<filter-name>rateLimiter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
然后在context.xml中注入RateLimitStrategist
<bean id="rateLimitStrategist" class="com.limit.factory.RateLimitStrategist" />
5、代码tag-0.0.1是项目单节点部署可用,版本tag-0.0.2为适应多节点部署改为redis来实现对处理线程数的控制
**需要源码留邮箱**
限流神器之-Guava RateLimiter 实战的更多相关文章
- 常用限流算法与Guava RateLimiter源码解析
在分布式系统中,应对高并发访问时,缓存.限流.降级是保护系统正常运行的常用方法.当请求量突发暴涨时,如果不加以限制访问,则可能导致整个系统崩溃,服务不可用.同时有一些业务场景,比如短信验证码,或者其它 ...
- 服务限流 -- 自定义注解基于RateLimiter实现接口限流
1. 令牌桶限流算法 令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有浏览来时取走一个或者多个令牌,当发生高并发情况下拿到令牌的执行业务逻辑,没有获取到令牌的就会丢弃获取服务降级处理,提示一个 ...
- 限流神器Sentinel,不了解一下吗?
概述 书接上回:你来说说什么是限流? ,限流的整体概述中,描述了 限流是什么,限流方式和限流的实现.在文章尾部的 分布式限流,没有做过多的介绍,选择了放到这篇文章中.给大伙细细讲解一下 Sentine ...
- 阿里限流神器Sentinel夺命连环 17 问?
1.前言 这是<spring Cloud 进阶>专栏的第五篇文章,这篇文章介绍一下阿里开源的流量防卫兵Sentinel,一款非常优秀的开源项目,经过近10年的双十一的考验,非常成熟的一款产 ...
- 一个轻量级的基于RateLimiter的分布式限流实现
上篇文章(限流算法与Guava RateLimiter解析)对常用的限流算法及Google Guava基于令牌桶算法的实现RateLimiter进行了介绍.RateLimiter通过线程锁控制同步,只 ...
- 超详细的Guava RateLimiter限流原理解析
超详细的Guava RateLimiter限流原理解析 mp.weixin.qq.com 点击上方“方志朋”,选择“置顶或者星标” 你的关注意义重大! 限流是保护高并发系统的三把利器之一,另外两个是 ...
- 【Guava】使用Guava的RateLimiter做限流
一.常见的限流算法 目前常用的限流算法有两个:漏桶算法和令牌桶算法. 1.漏桶算法 漏桶算法的原理比较简单,请求进入到漏桶中,漏桶以一定的速率漏水.当请求过多时,水直接溢出.可以看出,漏桶算法可以强制 ...
- coding++:RateLimiter 限流算法之漏桶算法、令牌桶算法--简介
RateLimiter是Guava的concurrent包下的一个用于限制访问频率的类 <dependency> <groupId>com.google.guava</g ...
- ☕【Java技术指南】「并发编程专题」针对于Guava RateLimiter限流器的入门到精通(含实战开发技巧)
并发编程的三剑客 在开发高并发系统时有三剑客:缓存.降级和限流. 缓存 缓存的目的是提升系统访问速度和增大系统处理容量. 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题 ...
随机推荐
- 四种方法获取可执行程序的文件路径(.NET Core / .NET Framework)
原文:四种方法获取可执行程序的文件路径(.NET Core / .NET Framework) 本文介绍四种不同的获取可执行程序文件路径的方法.适用于 .NET Core 以及 .NET Framew ...
- windows桌面远程连接突然不能双向复制文件
远程桌面连接windows 2008,突然无法在本地和服务器之间互相复制文件.根据微软的说明,由rdpclip.exe进程来控制,打开远程服务器的任务管理器,看到rdpclip.exe进程存在,即可进 ...
- MVC学习笔记(四)---使用linq多表联查(SQL)
1.数据库原型(Students表中的ID和Scores表中的StudentID是对应的) 2.实现效果:查询出每个学生各个科目的成绩(用的是MVC学习笔记(三)—用EF向数据库中添加数据的架构) C ...
- JavaScript字符串转数值
JavaScript字符串转数值:方法主要有三种 转换函数.强制类型转换.利用js变量弱类型转换. 1. 转换函数 js提供了parseInt()和parseFloat()两个转换函数.前者把值转换成 ...
- Linux 软链接和硬链接简介
在Linux系统中,将文件分为两个部分:用户数据和元数据. 元数据(inode) 元数据即文件的索引节点(inode),用来记录文件的权限(r.w.x).文件的所有者和属组.文件的大小.文件的状态改变 ...
- springboot多环境下maven打包
前言: 最近在项目中使用springboot时发现,采用在pom中定义不同的profile,并且maven打包时 采用-P参数并不能替换我application.properties文件中指定占位符的 ...
- Flask--配置文件
配置文件 配置文件的方式有很多,下面介绍两种: 第一种:根据全局变量实现 App.py from flask import Flask, session app = Flask(__name__) # ...
- 【Oracle RAC】Linux系统Oracle18c RAC安装配置详细记录过程(图文并茂)
本文Oracle 18c GI/RAC on Oracle Linux step-by-step 的安装配置步骤,同时也包含dbca 创建数据库的过程. 1. 关闭SELINUX,防火墙vi /etc ...
- Flask入门到放弃(五)—— 蓝图
转载请在文章开头附上原文链接地址:https://www.cnblogs.com/Sunzz/p/10980094.html 蓝图 Blueprint 模块化 随着flask程序越来越复杂,我们需要对 ...
- 2020秋招嵌入式面经——地平线、小米、CVTE、大华、绿米
地平线提前批 一面凉: 投递简历过程:官网投递9月份之前投的都是提前批,投了北京.上海.南京地区的嵌入式软件研发岗,北京和上海的都被筛掉了,南京的捞了我,hr小姐姐打电话邀约面试. 一面凉: 电话面试 ...