zoukankan      html  css  js  c++  java
  • 限流(三)Redis + lua分布式限流

    一、简介

    1)分布式限流

    如果是单实例项目,我们使用Guava这样的轻便又高性能的堆缓存来处理限流。但是当项目发展为多实例了以后呢?这时候我们就需要采用分布式限流的方式,分布式限流可以以redis + lua 或者 nignx + lua这样的组合来实现。。

    分布式限流一般应用场景都是在业务上进行限流,所以本文不涉及niginx + lua,简单介绍redis + lua分布式限流的实现。如果是需要在接入层限流的话,应该直接采用nginx自带的连接数限流模块请求限流模块

    2)redis

    redis是一种键值对的单线程架构模型,所以它是线程安全的,也是分布式缓存常用的解决方案。(本文不涉及redis的分布式缓存,只是讲如何结合redis实现限流)

    3)lua

    lua是基于c语言的一种脚本语言,它可以很轻便地被使用在嵌入式方面。我们不会去重写redis,但是我们可以去使用lua来扩展redis的功能。而redis也内置了对lua支持的模块。

    二、示例

    示例图

    1)启动redis服务

    我们得确保redis安装,并使用: ./redis-server 命令启动redis服务端

    redis常用命令:

    ./redis-server 启动服务端
    ./redis-cli 启动客户端
    ./redis-cli shutdown 关闭服务
    keys * 查看所有key
    get 键 根据键获取值

    2)编写lua脚本

    创建limit.lua文件

    local key = KEYS[1] --限流KEY(一秒一个)
    local limit = tonumber(ARGV[1]) --限流大小
    local current = tonumber(redis.call('get', key) or "0")
    if current + 1 > limit then --如果超出限流大小
        return 0
    else --请求数+1,并设置2秒过期
        redis.call("INCRBY", key,"1")
        redis.call("expire", key,"1")
        return 1
    end

    1)我们通过KEYS[1] 获取传入的key参数

    2)通过ARGV[1]获取传入的limit参数

    3)redis.call方法,从缓存中get和key相关的值,如果为nil那么就返回0

    4)接着判断缓存中记录的数值是否会大于限制大小,如果超出表示该被限流,返回0

    5)如果未超过,那么该key的缓存值+1,并设置过期时间为1秒钟以后,并返回1

    3)Java调用

    java采用jedis来操作redis

    引入redis依赖和apache io的工具包(方便读取lua文件的内容)

     <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>

    创建RedisLua.java文件

    import org.apache.commons.io.FileUtils;
    import redis.clients.jedis.Jedis;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.URISyntaxException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    
    /**
     * @author lay
     * @date 2018/5/22.
     * @time 17:48
     */
    public class RedisLua {
    
        public static void main(String[] args) {
            final CountDownLatch latch = new CountDownLatch(1);
    
            for (int i = 0; i < 7; i++) {
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            latch.await();
                            System.out.println(accquire());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
    
            }
    
            latch.countDown();
        }
    
        public static boolean accquire() throws IOException, URISyntaxException {
            Jedis jedis = new Jedis("127.0.0.1");
            File luaFile = new File(RedisLua.class.getResource("/").toURI().getPath() + "limit.lua");
            String luaScript = FileUtils.readFileToString(luaFile);
    
            String key = "ip:" + System.currentTimeMillis()/1000; // 当前秒
            String limit = "5"; // 最大限制
            List<String> keys = new ArrayList<String>();
            keys.add(key);
            List<String> args = new ArrayList<String>();
            args.add(limit);
            Long result = (Long)(jedis.eval(luaScript, keys, args)); // 执行lua脚本,传入参数
            return result == 1;
        }
    }

    以上模拟了多线程下,使用jedis连接redis服务,并执行lua脚本

    4)输出结果

    true
    true
    false
    true
    false
    true
    true

    我们看到,7个线程,其中2个线程被判断为false

    参考文章:http://jinnianshilongnian.iteye.com/blog/2305117

  • 相关阅读:
    Ubuntu18.04下更改apt源为阿里云源
    sudo: pip:command not found问题解决
    CentOS8 中文输入法
    PyCharm2019 激活方式
    正则爬取京东商品信息并打包成.exe可执行程序
    学习springcloud bus+springcloud config实现刷新配置
    使用springcloud-config统一管理配置文件
    记录一下今天使用maven构建项目分多个模块时遇到的扫描properties问题
    spring+mybatis+thymeleaf
    回调地狱问题
  • 原文地址:https://www.cnblogs.com/lay2017/p/9074477.html
Copyright © 2011-2022 走看看