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


    一,限流有哪些环节?

    1,为什么要限流?

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

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

          例如:

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

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

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

    2,需要应用限流的环节

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

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

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

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

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

    说明:作者:刘宏缔 邮箱: 371125307@qq.com

    二,演示项目的说明

    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)
  • 相关阅读:
    02-Java 数组和排序算法
    Spring Security 入门
    mysql外键理解
    redis能否对set数据的每个member设置过期时间
    Redis sortedset实现元素自动过期
    mysql之触发器trigger
    一篇很棒的 MySQL 触发器学习教程
    mysql触发器
    云游戏
    mysql触发器个人实战
  • 原文地址:https://www.cnblogs.com/architectforest/p/13039634.html
Copyright © 2020-2023  润新知