分布式---基于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. linux bash shell 入门教程()

    Shell Script(bash)简介 众所皆知地,UNIX上以小工具著名,利用许多简单的小工具,来完成原本需要大量软体开发的工作,这一点特色,使得UNIX成为许多人心目中理想的系统平台. 在众多的 ...

  2. Sublime之快捷操作

    列举常用的Sublime操作,涉及操作 1.每行默认需要统一添加逗号 1)全选 ctrl + a 2) 组合键 ctrl + shift + l 即可进行操作 (这里是L哦) 之后也可以使用HOME键 ...

  3. Linux之SaltStack

    SaltStack是基于Python开发的一套C/S架构配置管理工具(功能不仅仅是配置管理,如使用salt-cloud配置AWS EC2实例),它的底层使用ZeroMQ消息队列pub/sub方式通信, ...

  4. Blazor 组件库 BootstrapBlazor 中Carousel组件介绍

    组件介绍 Carousel 走马灯的作用是在有限空间内,循环播放同一类型的图片.文字等内容. 代码如下: <Carousel Images="@Images" Width=& ...

  5. 鸿蒙NEXT元服务:收藏、卡片、用户协议、隐私声明、分享链接、评分与评论

    相比应用,元服务的功能藏的比较深,这里记录一下常用功能的位置. 1.收藏(添加至我的服务) 打开元服务-->右上角四个点-->添加至我的服务-->手机滑到负一屏-->点击&qu ...

  6. 《JavaScript 模式》读书笔记(7)— 设计模式3

    这一篇,我们学习本篇中最为复杂的三个设计模式,代理模式.中介者模式以及观察者模式.这三个模式很重要!! 七.代理模式 在代理设计模式中,一个对象充当另一个对象的接口.它与外观模式的区别之处在于,外观模 ...

  7. vue3笔记 - 父子组件通信

    父传子 说明:父组件将数据绑定在组件标签上:子组件props接收 父组件: <template> <Child :msg="msg" /> </tem ...

  8. Delon ACL

    Delon ACL delon ACL Alain acl 路由守卫 使用 ACLService 核心是 ACLService,See:https://github.com/ng-alain/delo ...

  9. Spring 事务管理 基于配置文件

    事务概念: 原子性:要么都成功,有一个失败都失败 一致性:总量不变(A有100元,B有100元,总量是200元.A把100元转给B,B就有了200元,总量还是200元) 隔离性:两人操作同一条数据,不 ...

  10. 【前端】CSS:border

    border 是CSS中用于设置元素边框的. 第一个参数为线的粗细.除了数字型值外,还可以写: thin(细线) medium(中粗线) thick(粗线) 第二个参数是线条样式,可选参数如下: 小圆 ...