• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

springboot:用redis+lua实现基于ip地址的分布式流量限制(限流/简单计数器算法)(spring ...

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

一,限流有哪些环节?

1,为什么要限流?

      目的:通过对并发请求进行限速或者一个时间单位内的的请求进行限速,目的是保护系统可正常提供服务,避免被压力太大无法响应服务.

      如果达到限制速率则可以采取预定的处理:

      例如:

      拒绝服务(定向到错误页面或返回错误提示信息)

      排队或等待(秒杀/评论/下单)

      降级(只返回兜底数据或默认数据)

 

2,需要应用限流的环节

       防火墙:firewalld/iptables层的限流,针对每台机器

      接入层:nginx的limit_req模块,对每单位时间的平均速率限流,针对某个站点或某个url

      应用层:可以针对某个url或某个方法

 

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

         对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: [email protected]

 

二,演示项目的说明

1,项目的原理:

  如果仅仅是单机上对某个接口做限流,

  可以直接使用google的guava包中的流量限制功能,

  但如果是有多台机器上统一做限流,

  则需要借助redis的功能

  当后端接收到请求时,会把限流的方法名和ip做为key值保存到redis,

  每次接收到请求,对key值加1,

  当请求数量在指定时间内超过了限制数量,

  则返回'访问过于频繁'的提示信息  

 

2,项目在github上的地址:

https://github.com/liuhongdi/ratelimiter

 

3,项目的目录结构:

 

三,lua代码的说明:

ratelimit.lua

local key = KEYS[1]
local limit = tonumber(KEYS[2])
local length = tonumber(KEYS[3])
--redis.log(redis.LOG_NOTICE,' length: '..length)
local current = redis.call('GET', key)
if current == false then
   --redis.log(redis.LOG_NOTICE,key..' is nil ')
   redis.call('SET', key,1)
   redis.call('EXPIRE',key,length)
   --redis.log(redis.LOG_NOTICE,' set expire end')
   return '1'
else
   --redis.log(redis.LOG_NOTICE,key..' value: '..current)
   local num_current = tonumber(current)
   if num_current+1 > limit then
       return '0'
   else
       redis.call('INCRBY',key,1)
       return '1'
   end
end

说明:

key:在redis中记录访问次数的index,在这里我们用method的名字加ip地址进行限制

limit:  单ip对此method最多可以访问的次数

length: 限制次数生效的时长

原理:

key不存在时,新建一个key,value设置为1,并设定过期时间

如果key存在,看是否超过单位时间内允许访问的最高次数,

如果超过,返回0,

如果不超过,返回1

说明:为什么使用lua脚本?

redis上的lua脚本的执行是原子性的,不存在多个线程的并发问题,

使用lua脚本能保证高并发时也不会出现超出流量限制

 

四,java代码的说明:

1,RedisLuaUtil.java

@Service
public class RedisLuaUtil {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    private static final Logger logger = LogManager.getLogger("bussniesslog");
    /*
    run a lua script
    luaFileName: lua file name,no path
    keyList: list for redis key
    return 0: fail
           1: success
    */
    public String runLuaScript(String luaFileName,List<String> keyList) {
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/"+luaFileName)));
        redisScript.setResultType(String.class);
        String result = "";
        String argsone = "none";
        try {
            result = stringRedisTemplate.execute(redisScript, keyList,argsone);
        } catch (Exception e) {
            logger.error("发生异常",e);
        }
        return result;
    }
}

说明:

DefaultRedisScript:负责封装lua脚本

luaFileName: lua文件名

keyList:   redis中的key列表,我们把参数放在这里面传递

stringRedisTemplate:负责执行脚本

argsone:值参数,我们传一个空字串即可

 

2,RedisRateLimiterAspect,它负责调用RedisLuaUtil类,执行lua脚本:

    /*
    * check is reach limit in time
    * run by lua
    * */
    private boolean checkByRedis(RedisRateLimiter limit, String key) {
        List<String> keyList = new ArrayList();
        keyList.add(key);
        keyList.add(String.valueOf(limit.count()));
        keyList.add(String.valueOf(limit.time()));
        String res = redisLuaUtil.runLuaScript("ratelimit.lua",keyList);
        System.out.println("------------------lua res:"+res);
        if (res.equals("1")) {
            return true;
        } else {
            return false;
        }
    }

说明:

keyList中我们添加了三个变量:

key:   在redis中记录访问次数的index,在这里我们用method的名字加ip地址进行限制

count:  同一个ip限制访问的次数

time: 限制访问的时间段

 

3,其他java代码的说明:

RedisRateLimiter:定义了一个注解

RedisRateLimiterAspect:AOP的切面程序,使限流不侵入业务代码

RateController: 控制器

在spring框架中使用AOP或Interceptor可以使通用的一些功能例如安全、检验等不影响业务代码,

我们在这个例子中使用的是AOP,也可以选择Interceptor,这里仅供参考

 

五,测试限流的效果:

1,查看controller中定义的值:

@RestController
@RequestMapping("/rate")
public class RateController {
    @RequestMapping("/redislimit")
    @RedisRateLimiter(count = 3, time = 1)
    public Object redisLimit() {
        return ServerResponseUtil.success();
    }
}

可以看到流量限制的值为:对redisLimit方法,同一个ip在1秒钟内最多可访问3次

 

2,用ab测试并发情况下的流量限制是否生效?

#-c:指定请求的并发数量

#-n:指定请求的总数量

[liuhongdi@localhost ~]$ ab -c 20 -n 20 http://127.0.0.1:8080/rate/redislimit

查看代码运行中打印出的数据:

------------------lua res:1
------------------lua res:1
------------------lua res:1
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0

可见在20个并发中,只有3个是生效的,允许正常访问,其他的超出了访问的数量限制

 

六,查看spring boot的版本

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)

 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
编译lua开发固件,配置自己想要的功能发布时间:2022-07-22
下一篇:
Nginx编译安装Lua模块遇到的大坑发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap