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

