1. 程式人生 > >限流(三)Redis + lua分布式限流

限流(三)Redis + lua分布式限流

cti nts 應用場景 add 啟動服務 lis serve key common

一、簡介

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

限流(三)Redis + lua分布式限流