Java實現(xiàn)分布式系統(tǒng)限流
為何使用分布式系統(tǒng)限流:
在分布式環(huán)境中,我們的系統(tǒng)都是集群化部署,那么使用了單機版的限流策略,比如我們對某一個接口的限流方案是每秒鐘最多10次請求,那么因為各個實例都會自己維護一份請求次數(shù),所以真實每秒的請求數(shù)是:
節(jié)點數(shù) * 每秒最多請求數(shù),這樣的話就超出了我們的預期;
分布式限流解決方案:
● 可以基于redis,做分布式限流
● 可以基于nginx做分布式限流
● 可以使用阿里開源的 sentinel 中間件
本次介紹使用 redis 做分布式限流
實現(xiàn)思路:
設(shè)計思路:假設(shè)一個用戶(用IP判斷)每分鐘訪問某一個服務接口的次數(shù)不能超過10次,那么我們可以在Redis中根據(jù)該用戶IP創(chuàng)建一個鍵,并此時我們就設(shè)置這個鍵的過期時間為60秒,當用戶請求到來的時候,先去redis中根據(jù)用戶ip獲取這個用戶當前分鐘請求了多少次,如果獲取不到,則說明這個用戶當前分鐘第一次訪問,就創(chuàng)建這個健,并+1,如果獲取到了就判斷當前有沒有超過我們限制的次數(shù),如果到了我們限制的次數(shù)則禁止訪問。
使用技術(shù):使用redis提供的:incr命令 實現(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 {
? ? // 服務器IP地址
? ? private static String ADDR = "127.0.0.1";
? ? // 端口
? ? private static int PORT = 6379;
? ? // 密碼
? ? private static String AUTH = null;
? ? // 連接實例的最大連接數(shù)
? ? private static int MAX_ACTIVE = 1024;
? ? // 控制一個pool最多有多少個狀態(tài)為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;
? ? // 數(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實例
? ? ?*/
? ? 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();
? ? ? ? }
? ? }
}實現(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();
? ? ? ? // 準備工作,先初始化 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()+" 當前時間訪問次數(shù)還未達到上限,可以訪問");
? ? ? ? ? ? } 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;}
? ? }
}以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
解決IDEA使用springBoot創(chuàng)建項目,lombok標注實體類后編譯無報錯,但是運行時報錯問題
文章詳細描述了在使用lombok的@Data注解標注實體類時遇到編譯無誤但運行時報錯的問題,分析了可能的原因,并提供了解決步驟2025-01-01
Java CompletableFuture如何實現(xiàn)超時功能
這篇文章主要為大家介紹了實現(xiàn)超時功能的基本思路以及CompletableFuture(之后簡稱CF)是如何通過代碼實現(xiàn)超時功能的,需要的小伙伴可以了解下2025-01-01
注解@TableName,@TableField,pgsql的模式對應方式
這篇文章主要介紹了注解@TableName,@TableField,pgsql的模式對應方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04

