首頁 > 軟體

Java實現分散式系統限流

2022-08-12 14:04:53

為何使用分散式系統限流:

在分散式環境中,我們的系統都是叢集化部署,那麼使用了單機版的限流策略,比如我們對某一個介面的限流方案是每秒鐘最多10次請求,那麼因為各個範例都會自己維護一份請求次數,所以真實每秒的請求數是:
節點數 * 每秒最多請求數,這樣的話就超出了我們的預期;

分散式限流解決方案:

● 可以基於redis,做分散式限流
● 可以基於nginx做分散式限流
● 可以使用阿里開源的 sentinel 中介軟體

本次介紹使用 redis 做分散式限流

實現思路:

設計思路:假設一個使用者(用IP判斷)每分鐘存取某一個服務介面的次數不能超過10次,那麼我們可以在Redis中根據該使用者IP建立一個鍵,並此時我們就設定這個鍵的過期時間為60秒,當用戶請求到來的時候,先去redis中根據使用者ip獲取這個使用者當前分鐘請求了多少次,如果獲取不到,則說明這個使用者當前分鐘第一次存取,就建立這個健,並+1,如果獲取到了就判斷當前有沒有超過我們限制的次數,如果到了我們限制的次數則禁止存取。

使用技術:使用redis提供的:incr命令 實現

先引入redis的依賴:

<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
</dependency>

redis設定類:

package org.xhs.redis;


import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @Author: hu.chen
 * @Description:
 **/
public class RedisConfig {

    // 伺服器IP地址
    private static String ADDR = "127.0.0.1";
    // 埠
    private static int PORT = 6379;
    // 密碼
    private static String AUTH = null;
    // 連線範例的最大連線數
    private static int MAX_ACTIVE = 1024;
    // 控制一個pool最多有多少個狀態為idle(空閒的)的jedis範例,預設值也是8。
    private static int MAX_IDLE = 200;
    // 等待可用連線的最大時間,單位毫秒,預設值為-1,表示永不超時。如果超過等待時間,則直接丟擲JedisConnectionException
    private static int MAX_WAIT = 10000;
    // 連線超時的時間
    private static int TIMEOUT = 10000;
    // 在borrow一個jedis範例時,是否提前進行validate操作;如果為true,則得到的jedis範例均是可用的;
    private static boolean TEST_ON_BORROW = true;

        private static JedisPool jedisPool = null;
    // 資料庫模式是16個資料庫 0~15
    public static final int DEFAULT_DATABASE = 0;

    /**
     * 初始化Redis連線池
     */

    static {

        try {

            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH, DEFAULT_DATABASE);

        } catch (Exception e) {

            e.printStackTrace();
        }
    }

    /**
     * 獲取Jedis範例
     */
    public static Jedis getJedis() {
        try {

            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

}

redis工具類:

package org.xhs.redis;

import redis.clients.jedis.Jedis;

/**
 * @Author: hu.chen
 * @Description:
 * @DateTime: 2022/1/21 1:06 PM
 **/
public class RedisUtils {

    /**
     * 將指定的key遞增1(可用於樂觀鎖)
     *
     * @param key
     * @return
     */
    public static Long incr(final String key) {

        Jedis jedis = RedisConfig.getJedis();
        Long  incr = jedis.incr(key);

        returnJedis(jedis);
        return incr;
    }


    /**
     * 給指定key設定過期時間
     *
     * @param key
     * @param seconds
     * @author ruan 2013-4-11
     */
    public static void expire(String key, int seconds) {
        if (seconds <= 0) {
            return;
        }
        Jedis jedis = RedisConfig.getJedis();
        jedis.expire(key, seconds);
        // 將連線還回連線池
        returnJedis(jedis);
    }


    /**
     * 回收jedis
     *
     * @param jedis
     */
    private static void returnJedis(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}

實現:

package org.xhs.redis;


import java.util.ArrayList;
import java.util.List;


/**
 * @Author: hu.chen
 * @Description:
 **/
public class TestRedis {
    /**
     * 超時時間(單位秒)
     */
    private static int TIMEOUT = 30;

    /**
     * 每分鐘的請求次數限制
     */
    private static int COUNT = 10;


    public static void main(String[] args) {

        List<UserRequest> tasks = new ArrayList();
        // 準備工作,先初始化 10個執行緒(使用者),這10個使用者同時存取一個介面
        for (int i = 1; i <= 12; i++) {
            String ip = "127.0.0." + i;
            String userName = "chenhu_";
            String interfaceName = "user/find_" + i;
            tasks.add(new UserRequest(ip, userName, interfaceName));
        }


        for (UserRequest request : tasks) {
            // 以使用者名稱為鍵
            if (isAccess(request.getUserName(), COUNT)) {
                System.err.println("使用者:"+request.getUserName()+" 當前時間存取次數還未達到上限,可以存取");
            } else {
                System.err.println("當前時間存取失敗,"+request.getUserName()+"無法獲取令牌");
            }


        }
    }

    /**
     * 是否可以存取
     *
     * @return
     */
    private static boolean isAccess(String userName, long count) {
        Long incr = RedisUtils.incr(userName);
        if (incr == 1) {
            RedisUtils.expire(userName, TIMEOUT);
        }
        if (count < incr) {
            return false;
        }
        return true;
    }


    /**
     * 實體物件
     */
    private static class UserRequest {
        /**
         * 請求使用者ip
         */
        private String ip;
        /**
         * 使用者名稱
         */
        private String userName;
        /**
         * 請求的介面名
         */
        private String interfaceName;

        public UserRequest(String ip, String userName, String interfaceName) {
            this.ip = ip;
            this.userName = userName;
            this.interfaceName = interfaceName;
        }

        public String getIp() {return ip;}

        public String getUserName() { return userName;}

        public String getInterfaceName() {return interfaceName;}
    }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com