Java實(shí)現(xiàn)分布式系統(tǒng)限流
為何使用分布式系統(tǒng)限流:
在分布式環(huán)境中,我們的系統(tǒng)都是集群化部署,那么使用了單機(jī)版的限流策略,比如我們對某一個接口的限流方案是每秒鐘最多10次請求,那么因?yàn)楦鱾€實(shí)例都會自己維護(hù)一份請求次數(shù),所以真實(shí)每秒的請求數(shù)是:
節(jié)點(diǎn)數(shù) * 每秒最多請求數(shù),這樣的話就超出了我們的預(yù)期;
分布式限流解決方案:
● 可以基于redis,做分布式限流
● 可以基于nginx做分布式限流
● 可以使用阿里開源的 sentinel 中間件
本次介紹使用 redis 做分布式限流
實(shí)現(xiàn)思路:
設(shè)計(jì)思路:假設(shè)一個用戶(用IP判斷)每分鐘訪問某一個服務(wù)接口的次數(shù)不能超過10次,那么我們可以在Redis中根據(jù)該用戶IP創(chuàng)建一個鍵,并此時我們就設(shè)置這個鍵的過期時間為60秒,當(dāng)用戶請求到來的時候,先去redis中根據(jù)用戶ip獲取這個用戶當(dāng)前分鐘請求了多少次,如果獲取不到,則說明這個用戶當(dāng)前分鐘第一次訪問,就創(chuàng)建這個健,并+1,如果獲取到了就判斷當(dāng)前有沒有超過我們限制的次數(shù),如果到了我們限制的次數(shù)則禁止訪問。
使用技術(shù):使用redis提供的:incr命令 實(shí)現(xiàn)
先引入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 { ? ? // 服務(wù)器IP地址 ? ? private static String ADDR = "127.0.0.1"; ? ? // 端口 ? ? private static int PORT = 6379; ? ? // 密碼 ? ? private static String AUTH = null; ? ? // 連接實(shí)例的最大連接數(shù) ? ? private static int MAX_ACTIVE = 1024; ? ? // 控制一個pool最多有多少個狀態(tài)為idle(空閑的)的jedis實(shí)例,默認(rèn)值也是8。 ? ? private static int MAX_IDLE = 200; ? ? // 等待可用連接的最大時間,單位毫秒,默認(rèn)值為-1,表示永不超時。如果超過等待時間,則直接拋出JedisConnectionException ? ? private static int MAX_WAIT = 10000; ? ? // 連接超時的時間 ? ? private static int TIMEOUT = 10000; ? ? // 在borrow一個jedis實(shí)例時,是否提前進(jìn)行validate操作;如果為true,則得到的jedis實(shí)例均是可用的; ? ? private static boolean TEST_ON_BORROW = true; ? ? ? ? private static JedisPool jedisPool = null; ? ? // 數(shù)據(jù)庫模式是16個數(shù)據(jù)庫 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實(shí)例 ? ? ?*/ ? ? 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設(shè)置過期時間 ? ? ?* ? ? ?* @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(); ? ? ? ? } ? ? } }
實(shí)現(xiàn):
package org.xhs.redis; import java.util.ArrayList; import java.util.List; /** ?* @Author: hu.chen ?* @Description: ?**/ public class TestRedis { ? ? /** ? ? ?* 超時時間(單位秒) ? ? ?*/ ? ? private static int TIMEOUT = 30; ? ? /** ? ? ?* 每分鐘的請求次數(shù)限制 ? ? ?*/ ? ? private static int COUNT = 10; ? ? public static void main(String[] args) { ? ? ? ? List<UserRequest> tasks = new ArrayList(); ? ? ? ? // 準(zhǔn)備工作,先初始化 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()+" 當(dāng)前時間訪問次數(shù)還未達(dá)到上限,可以訪問"); ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? System.err.println("當(dāng)前時間訪問失敗,"+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; ? ? } ? ? /** ? ? ?* 實(shí)體對象 ? ? ?*/ ? ? 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;} ? ? } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Spring的@Scheduled 如何動態(tài)更新cron表達(dá)式
這篇文章主要介紹了Spring的@Scheduled 如何動態(tài)更新cron表達(dá)式的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07SpringBoot整合Security安全框架實(shí)現(xiàn)控制權(quán)限
本文主要介紹了SpringBoot整合Security安全框架實(shí)現(xiàn)控制權(quán)限,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01詳解java WebSocket的實(shí)現(xiàn)以及Spring WebSocket
這篇文章主要介紹了詳解java WebSocket的實(shí)現(xiàn)以及Spring WebSocket ,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01簡單了解java標(biāo)識符的作用和命名規(guī)則
這篇文章主要介紹了簡單了解java標(biāo)識符的作用和命名規(guī)則,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01spring boot 默認(rèn)異常處理的實(shí)現(xiàn)
這篇文章主要介紹了spring boot 默認(rèn)異常處理的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04