之前参与过sendcloud API接口的开发,开放给用户的API接口,在用户恶意调用或者数据容量增长过快,接口查询造成系统cpu负载过大,数据库宕机的事故,为了避免这种致命事故出现,我们API需要保护,需要对API进行限流策略。下面我们以接口:http://api2.sendcloud.net/api/data/emailStatus
一、redis实现
private boolean overRateLimit(rateLimitKey ) {
int maxValue = 5000;
if (RedisUtil.exists(rateLimitKey)) {
Long total = RedisUtil.incr(rateLimitKey, 1);
if (total == null) {
return false;
}
if (total > maxValue) {
//下面RedisUtil.ttl(rateLimitKey)==-1 片段解决下面2个问题,非常重要
//1.如果客户端产生了一个相同的,不过期的rateLimitKey key该rateLimitKey 会一直计数,造成该接口拉黑,不可用
//2.redis主从切换,导致过期数据没有同步丢失,也会产生一个相同的,不过期的rateLimitKey key,造成接口不可用
if(RedisUtil.ttl(rateLimitKey)==-1){
RedisUtil.del(rateLimitKey);
}
return true;
}
} else {
RedisUtil.setnx(rateLimitKey, "1", RedisUtil.DEFAULT_EXPIRE);
}
return false;
}
二、lua+redis实现
redis+lua 有几个有点
- 减少网络开销:本来N次网络请求的操作,可以用一个请求完成。
- 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。
(1)创建status.lua 脚本
----设置KEY=rateLimitKey
---每次请求给KEY=rateLimitKey 计数+1 设置过期时间为1秒钟
---如果KEY=rateLimitKey 计数>2000 触发限流操作
---KEYS[1]
---ARGV
---ARGV[2] 2000
local visitTimes=redis.call('INRC',KEYS[1])
if (visitTimes==1) then
redis.call('expire',KEYS[1],ARGV[1])
end
if( visitTimes>ARGV[2] ) then
local ttl_time=redis.call('ttl',KEYS[1])
if ( tonumber(ttl_time)==-1 ) then
redis.call('del',KEYS[1])
end
return true
else
return false
end
(2)配置bean(springboot框架)
@Configuration
public class LuaConfiguration {
@Bean
public DefaultRedisScript<Boolean> statusRedisScript() {
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("status.lua")));
redisScript.setResultType(Boolean.class);
return redisScript;
}
}
(3)调用
@RestController
public class EmailStatusController {
@Resource
private DefaultRedisScript<Boolean> statusRedisScript;
@Resource
private PRedisTemplate pRedisTemplate;
@GetMapping("/status")
public ResponseEntity status() {
boolean
result=pRedisTemplate.getRedisTemplateString().execute(statusRedisScript,
Collections.singletonList("rateLimitKey"), 60,2000);
return ResponseEntity.ok(execute);
..............
}
}