分布式---基于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组件的特性,实现一个分布式限流的更多相关文章

  1. 【分布式架构】--- 基于Redis组件的特性,实现一个分布式限流

    分布式---基于Redis进行接口IP限流 场景 为了防止我们的接口被人恶意访问,比如有人通过JMeter工具频繁访问我们的接口,导致接口响应变慢甚至崩溃,所以我们需要对一些特定的接口进行IP限流,即 ...

  2. 分布式限流组件-基于Redis的注解支持的Ratelimiter

    原文:https://juejin.im/entry/5bd491c85188255ac2629bef?utm_source=coffeephp.com 在分布式领域,我们难免会遇到并发量突增,对后端 ...

  3. Redis实现的分布式锁和分布式限流

    随着现在分布式越来越普遍,分布式锁也十分常用,我的上一篇文章解释了使用zookeeper实现分布式锁(传送门),本次咱们说一下如何用Redis实现分布式锁和分布限流. Redis有个事务锁,就是如下的 ...

  4. 限流(三)Redis + lua分布式限流

    一.简介 1)分布式限流 如果是单实例项目,我们使用Guava这样的轻便又高性能的堆缓存来处理限流.但是当项目发展为多实例了以后呢?这时候我们就需要采用分布式限流的方式,分布式限流可以以redis + ...

  5. 详解Redisson分布式限流的实现原理

    摘要:本文将详细介绍下RRateLimiter的具体使用方式.实现原理还有一些注意事项. 本文分享自华为云社区<详解Redisson分布式限流的实现原理>,作者: xindoo. 我们目前 ...

  6. 分布式接口幂等性、分布式限流:Guava 、nginx和lua限流

    接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用. 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此 ...

  7. 基于kubernetes的分布式限流

    做为一个数据上报系统,随着接入量越来越大,由于 API 接口无法控制调用方的行为,因此当遇到瞬时请求量激增时,会导致接口占用过多服务器资源,使得其他请求响应速度降低或是超时,更有甚者可能导致服务器宕机 ...

  8. springboot + aop + Lua分布式限流的最佳实践

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一.什么是限流?为什么要限流? 不知道大家有没有做过帝都的地铁, ...

  9. Sentinel整合Dubbo限流实战(分布式限流)

    之前我们了解了 Sentinel 集成 SpringBoot实现限流,也探讨了Sentinel的限流基本原理,那么接下去我们来学习一下Sentinel整合Dubbo及 Nacos 实现动态数据源的限流 ...

  10. 一个轻量级的基于RateLimiter的分布式限流实现

    上篇文章(限流算法与Guava RateLimiter解析)对常用的限流算法及Google Guava基于令牌桶算法的实现RateLimiter进行了介绍.RateLimiter通过线程锁控制同步,只 ...

随机推荐

  1. 3.11 Linux删除空目录(rmdir命令)

    和 mkdir 命令(创建空目录)恰好相反,rmdir(remove empty directories 的缩写)命令用于删除空目录,此命令的基本格式为: [root@localhost ~]# rm ...

  2. Linux Shell_read

    介绍 从控制台输入 基本语法 read [选项] 参数 常用选项 |选项|功能| |-|-| |-p|指定读取值时的提示符| |-t|指定取值时等待的时间(秒),如果没有在指定时间输入,就不再等待了| ...

  3. 本地代理web端口

    先配置 使用ssh 通过ProxyCommand:利用跳板机让不在同一局域网的机器ssh直连 代理访问 ssh -qTfnND 127.0.0.1:$代理端口 代理机器主机名

  4. Java基础语法闪过——纯小白

    Java语法突击 笔者因为学校奇葩选课原因,需要学习Java,考试所迫和大伙一起交流复习下基础的语法内容,大家都一把拿下考试 观前提醒:本文整理的有些仓促了,简单几分钟看看Java有什么内容还好,如果 ...

  5. Modbus调试、Modbus Slave、ModScan、Modbus Ploll、串口调试

    记录一下昨天调试Modbus调试. 上位机往下位机发送modbus指令.发送过去之后没有反应.后来才调试出来原来是下位机错一个位. 调试过程:用modScan 往modbus slave 发送modb ...

  6. 干货分享:Air700ECQ的硬件设计,第三部分

    ​ 5. 电器特性,可靠性,射频特性 5.1. 绝对最大值 下表所示是模块数字.模拟管脚的电源供电电压电流最大耐受值. 表格 17:绝对最大值 参数 最小 最大 单位 VBAT -0.3 4.7 V ...

  7. (Redis基础教程之十三) 如何从命令行更改Redis的配置

    介绍 Redis是一个开源的内存中键值数据存储.Redis有几个命令,可让您即时更改Redis服务器的配置设置.本教程将介绍其中一些命令,并说明如何使这些配置更改永久生效. 如何使用本指南 本指南以备 ...

  8. java应用详解

    java应用详解 文档介绍: 1.nio应用(ServerSocketChannel.FileChannel). 2.优化jvm参数提升eclipse运行速度. 3.maven3.0.3安装及入门例子 ...

  9. JVM性能优化, Part 5:Java的伸缩性

    本文由 ImportNew - ImportNew读者 翻译自 Javaworld.如需转载本文,请先参见文章末尾处的转载要求. ImportNew注: JVM性能优化系列文章前4篇由ImportNe ...

  10. 三、FreeRTOS学习笔记-系统配置

    FreeRTOSConfig.h配置文件作用:对FreeRTOS进行功能配置和裁剪,以及API函数的使能. 系统配置文件中主要是一些宏定义 相关宏定义可分为三类: 1."INCLUDE&qu ...