基于Redis组件的特性,实现一个分布式限流
分布式---基于Redis进行接口IP限流
场景 为了防止我们的接口被人恶意访问,比如有人通过JMeter工具频繁访问我们的接口,导致接口响应变慢甚至崩溃,所以我们需要对一些特定的接口进行IP限流,即一定时间内同一IP访问的次数是有限的。
实现原理 用Redis作为限流组件的核心的原理,将用户的IP地址当Key,一段时间内访问次数为value,同时设置该Key过期时间。
比如某接口设置相同IP10秒内请求5次,超过5次不让访问该接口。
1. 第一次该IP地址存入redis的时候,key值为IP地址,value值为1,设置key值过期时间为10秒。
2. 第二次该IP地址存入redis时,如果key没有过期,那么更新value为2。
3. 以此类推当value已经为5时,如果下次该IP地址在存入redis同时key还没有过期,那么该Ip就不能访问了。
4. 当10秒后,该key值过期,那么该IP地址再进来,value又从1开始,过期时间还是10秒,这样反反复复。
说明从上面的逻辑可以看出,是一时间段内访问次数受限,不是完全不让该IP访问接口。
技术框架 SpringBoot + RedisTemplate (采用自定义注解完成)
这个可以用于真实项目开发场景。
一、代码
1、自定义注解
这边采用自定义注解的目的就是,在接口上使用自定义注解,让代码看去非常整洁。
IpLimiter
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IpLimiter {
/**
* 限流ip
*/
String ipAdress() ;
/**
* 单位时间限制通过请求数
*/
long limit() default 10;
/**
* 单位时间,单位秒
*/
long time() default 1;
/**
* 达到限流提示语
*/
String message();
}
2、测试接口
在接口上使用了自定义注解@IpLimiter
@Controller
public class IpController {
private static final Logger LOGGER = LoggerFactory.getLogger(IpController.class);
private static final String MESSAGE = "请求失败,你的IP访问太频繁";
//这里就不获取请求的ip,而是写死一个IP
@ResponseBody
@RequestMapping("iplimiter")
@IpLimiter(ipAdress = "127.198.66.01", limit = 5, time = 10, message = MESSAGE)
public String sendPayment(HttpServletRequest request) throws Exception {
return "请求成功";
}
@ResponseBody
@RequestMapping("iplimiter1")
@IpLimiter(ipAdress = "127.188.145.54", limit = 4, time = 10, message = MESSAGE)
public String sendPayment1(HttpServletRequest request) throws Exception {
return "请求成功";
}
}
3、处理IpLimter注解的AOP
这边采用切面的方式处理自定义注解。同时为了保证原子性,这边写了redis脚本ipLimiter.lua来执行redis命令,来保证操作原子性。
@Aspect
@Component
public class IpLimterHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(IpLimterHandler.class);
@Autowired
RedisTemplate redisTemplate;
/**
* getRedisScript 读取脚本工具类
* 这里设置为Long,是因为ipLimiter.lua 脚本返回的是数字类型
*/
private DefaultRedisScript<Long> getRedisScript;
@PostConstruct
public void init() {
getRedisScript = new DefaultRedisScript<>();
getRedisScript.setResultType(Long.class);
getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("ipLimiter.lua")));
LOGGER.info("IpLimterHandler[分布式限流处理器]脚本加载完成");
}
/**
* 这个切点可以不要,因为下面的本身就是个注解
*/
// @Pointcut("@annotation(com.jincou.iplimiter.annotation.IpLimiter)")
// public void rateLimiter() {}
/**
* 如果保留上面这个切点,那么这里可以写成
* @Around("rateLimiter()&&@annotation(ipLimiter)")
*/
@Around("@annotation(ipLimiter)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, IpLimiter ipLimiter) throws Throwable {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("IpLimterHandler[分布式限流处理器]开始执行限流操作");
}
Signature signature = proceedingJoinPoint.getSignature();
if (!(signature instanceof MethodSignature)) {
throw new IllegalArgumentException("the Annotation @IpLimter must used on method!");
}
/**
* 获取注解参数
*/
// 限流模块IP
String limitIp = ipLimiter.ipAdress();
Preconditions.checkNotNull(limitIp);
// 限流阈值
long limitTimes = ipLimiter.limit();
// 限流超时时间
long expireTime = ipLimiter.time();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("IpLimterHandler[分布式限流处理器]参数值为-limitTimes={},limitTimeout={}", limitTimes, expireTime);
}
// 限流提示语
String message = ipLimiter.message();
/**
* 执行Lua脚本
*/
List<String> ipList = new ArrayList();
// 设置key值为注解中的值
ipList.add(limitIp);
/**
* 调用脚本并执行
*/
Long result = (Long) redisTemplate.execute(getRedisScript, ipList, expireTime, limitTimes);
if (result == 0) {
String msg = "由于超过单位时间=" + expireTime + "-允许的请求次数=" + limitTimes + "[触发限流]";
LOGGER.debug(msg);
// 达到限流返回给前端信息
return message;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("IpLimterHandler[分布式限流处理器]限流执行结果-result={},请求[正常]响应", result);
}
return proceedingJoinPoint.proceed();
}
}
4、RedisCacheConfig(配置类)
@Configuration
public class RedisCacheConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisCacheConfig.class);
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
LOGGER.info("Springboot RedisTemplate 加载完成");
return template;
}
}
5、ipLimiter.lua 脚本
优点减少网络的开销: 脚本只执行一次,不需要发送多次请求, 减少网络传输;保证原子操作: 整个脚本作为一个原子执行, 就不用担心并发问题;
--获取KEY
local key1 = KEYS[1]
local val = redis.call('incr', key1)
local ttl = redis.call('ttl', key1)
--获取ARGV内的参数并打印
local expire = ARGV[1]
local times = ARGV[2]
redis.log(redis.LOG_DEBUG,tostring(times))
redis.log(redis.LOG_DEBUG,tostring(expire))
redis.log(redis.LOG_NOTICE, "incr "..key1.." "..val);
if val == 1 then
redis.call('expire', key1, tonumber(expire))
else
if ttl == -1 then
redis.call('expire', key1, tonumber(expire))
end
end
if val > tonumber(times) then
return 0
end
return 1
6、application.properties
#redis
spring.redis.hostName=
spring.redis.host=
spring.redis.port=6379
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=10
spring.redis.timeout=100ms
spring.redis.password=
logging.path= /Users/xub/log
logging.level.com.jincou.iplimiter=DEBUG
server.port=8888
7、SpringBoot启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
8、测试

完美上面这个测试非常符合我们的预期,前五次访问接口是成功的,后面就失败了,直到10秒后才可以重新访问,这样反反复复。
其它的这边就不一一展示了,附上该项目源码。
Github地址 https://github.com/yudiandemingzi/spring-boot-redis-ip-limiter
基于Redis组件的特性,实现一个分布式限流的更多相关文章
- 【分布式架构】--- 基于Redis组件的特性,实现一个分布式限流
分布式---基于Redis进行接口IP限流 场景 为了防止我们的接口被人恶意访问,比如有人通过JMeter工具频繁访问我们的接口,导致接口响应变慢甚至崩溃,所以我们需要对一些特定的接口进行IP限流,即 ...
- 基于redis实现的四种常见的限流策略
引言 在web开发中功能是基石,除了功能以外运维和防护就是重头菜了.因为在网站运行期间可能会因为突然的访问量导致业务异常.也有可能遭受别人恶意攻击 所以我们的接口需要对流量进行限制.俗称的QPS也是对 ...
- 分布式限流组件-基于Redis的注解支持的Ratelimiter
原文:https://juejin.im/entry/5bd491c85188255ac2629bef?utm_source=coffeephp.com 在分布式领域,我们难免会遇到并发量突增,对后端 ...
- springboot + aop + Lua分布式限流的最佳实践
整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一.什么是限流?为什么要限流? 不知道大家有没有做过帝都的地铁, ...
- 基于kubernetes的分布式限流
做为一个数据上报系统,随着接入量越来越大,由于 API 接口无法控制调用方的行为,因此当遇到瞬时请求量激增时,会导致接口占用过多服务器资源,使得其他请求响应速度降低或是超时,更有甚者可能导致服务器宕机 ...
- Redis实现的分布式锁和分布式限流
随着现在分布式越来越普遍,分布式锁也十分常用,我的上一篇文章解释了使用zookeeper实现分布式锁(传送门),本次咱们说一下如何用Redis实现分布式锁和分布限流. Redis有个事务锁,就是如下的 ...
- 限流(三)Redis + lua分布式限流
一.简介 1)分布式限流 如果是单实例项目,我们使用Guava这样的轻便又高性能的堆缓存来处理限流.但是当项目发展为多实例了以后呢?这时候我们就需要采用分布式限流的方式,分布式限流可以以redis + ...
- 详解Redisson分布式限流的实现原理
摘要:本文将详细介绍下RRateLimiter的具体使用方式.实现原理还有一些注意事项. 本文分享自华为云社区<详解Redisson分布式限流的实现原理>,作者: xindoo. 我们目前 ...
- 分布式接口幂等性、分布式限流:Guava 、nginx和lua限流
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用. 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此 ...
- rate-limit 一款 java 开源渐进式分布式限流框架使用介绍
项目简介 rate-limit 是一个为 java 设计的渐进式限流工具. 目的是为了深入学习和使用限流,后续将会持续迭代. 特性 渐进式实现 支持独立于 spring 使用 支持整合 spring ...
随机推荐
- VL4AD:让语义分割认识未知类别,无需额外数据和训练的OOD语义分割 | ECCV'24
来源:晓飞的算法工程笔记 公众号,转载请注明出处 论文: VL4AD: Vision-Language Models Improve Pixel-wise Anomaly Detection 论文地址 ...
- 从0搭建一个FIFO模块-02(系统架构)
一.异步FIFO需要注意的问题 所谓异步FIFO,指的是写时钟与读时钟可以不同步,读时钟可以比写时钟快,反之亦然.思考一下,这样会直接地造成两个问题: 由于异步FIFO的基本存储单元是双端口RAM,因 ...
- UUID和雪花(Snowflake)算法该如何选择?
UUID 和 Snowflake 都可以生成唯一标识,在分布式系统中可以说是必备利器,那么我们该如何对不同的场景进行不同算法的选择呢,UUID 简单无序十分适合生成 requestID, Snowfl ...
- python常用模块汇总
os模块 os.remove() 删除文件 os.unlink() 删除文件 os.rename() 重命名文件 os.listdir() 列出指定目录下所有文件 os.chdir() 改变当前工作目 ...
- 一个新的音乐管理软件--JxAudio
介绍 JxAudio是一个基于.net core的音频管理系统,支持音乐的播放.上传.下载.删除等功能. 兼容Subsonic协议,可以使用Subsonic客户端进行访问. 支持Windows.Lin ...
- Blazor 组件库 BootstrapBlazor 中CheckboxList组件介绍
组件介绍 CheckboxList 多选框组控件用于创建多选的复选框组. 他的样子是这样的: 代码也很简单,绑定一个List就行了. <CheckboxList @bind-Value=&quo ...
- IEDA 控制台乱码解决
1.修改idea 目录下的文件 idea64.exe.vmoptions idea.exe.vmoptions 在这个文件尾部增加 -Dfile.encoding=UTF-8 2.编辑ieda 文件配 ...
- QEMU固件模拟技术分析-luaqemu实现分析
文章首发于 https://forum.butian.net/share/123 概述 在嵌入式安全领域常常需要分析各种不同形态的固件,如果需要动态执行某些代码或者对固件进行Fuzzing测试,则需要 ...
- Gitlab误删用户导致项目丢失莫慌
Gitlab让小朋友不小心把离职员工的账号给删了,可是离职员工有好几个项目都是他是owner,造成Gitlab上项目全部丢失. 遇到这种情况,莫慌. 一般,本地都有完整的Git备份,离职员工走了,肯定 ...
- Centos查找Tomcat路径并重启
[root@devrestcloud ~\]# find / -name \*tomcat\* [root@devrestcloud ~]# cd /usr/tomcat/apache-tomcat- ...