最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot+Redis+Lua分布式限流的實現(xiàn)

 更新時間:2022年08月10日 15:57:39   作者:ypp91zr  
本文主要介紹了SpringBoot+Redis+Lua分布式限流的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

Redis支持LUA腳本的主要優(yōu)勢

LUA腳本的融合將使Redis數(shù)據(jù)庫產(chǎn)生更多的使用場景,迸發(fā)更多新的優(yōu)勢:

  • 高效性:減少網(wǎng)絡(luò)開銷及時延,多次redis服務(wù)器網(wǎng)絡(luò)請求的操作,使用LUA腳本可以用一個請求完成
  • 數(shù)據(jù)可靠性:Redis會將整個腳本作為一個整體執(zhí)行,中間不會被其他命令插入。
  • 復用性:LUA腳本執(zhí)行后會永久存儲在Redis服務(wù)器端,其他客戶端可以直接復用
  • 可嵌入性:可嵌入JAVA,C#等多種編程語言,支持不同操作系統(tǒng)跨平臺交互
  • 簡單強大:小巧輕便,資源占用率低,支持過程化和對象化的編程語言

自己也是第一次在工作中使用lua這種語言,記錄一下

創(chuàng)建Lua文件req_ratelimit.lua

local key = KEYS[1]   --限流KEY
local limitCount = tonumber(ARGV[1])       --限流大小
local limitTime = tonumber(ARGV[2])        --限流時間
local current = redis.call('get', key);
if current then
    if current + 1 > limitCount then --如果超出限流大小
        return 0
    else
        redis.call("INCRBY", key,"1")
        return current + 1
    end
else
    redis.call("set", key,"1")
    redis.call("expire", key,limitTime)
    return 1
end

自定義注解RateLimiter

package com.shinedata.ann;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
 
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
 
    /**
     * 限流唯一標識
     * @return
     */
    String key() default "rate.limit:";
 
    /**
     * 限流時間
     * @return
     */
    int time() default 1;
 
    /**
     * 限流次數(shù)
     * @return
     */
    int count() default 100;
 
    /**
     *是否限制IP,默認 否
     * @return
     */
    boolean restrictionsIp() default false;
}

定義切面RateLimiterAspect

package com.shinedata.aop;
 
import com.shinedata.ann.RateLimiter;
import com.shinedata.config.redis.RedisUtils;
import com.shinedata.exception.RateLimiterException;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
 
/**
 * @ClassName RateLimiterAspect
 * @Author yupanpan
 * @Date 2020/5/6 13:46
 */
@Aspect
@Component
public class RateLimiterAspect {
 
    private final Logger logger	= LoggerFactory.getLogger(this.getClass());
 
    private static ThreadLocal<String> ipThreadLocal=new ThreadLocal();
 
    private DefaultRedisScript<Number> redisScript;
 
    @PostConstruct
    public void init(){
        redisScript = new DefaultRedisScript<Number>();
        redisScript.setResultType(Number.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/req_ratelimit.lua")));
    }
 
    @Around("@annotation(com.shinedata.ann.RateLimiter)")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Class<?> targetClass = method.getDeclaringClass();
            RateLimiter rateLimit = method.getAnnotation(RateLimiter.class);
 
            if (rateLimit != null) {
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                boolean restrictionsIp = rateLimit.restrictionsIp();
                if(restrictionsIp){
                    ipThreadLocal.set(getIpAddr(request));
                }
 
                StringBuffer stringBuffer = new StringBuffer();
                stringBuffer.append(rateLimit.key());
                if(StringUtils.isNotBlank(ipThreadLocal.get())){
                    stringBuffer.append(ipThreadLocal.get()).append("-");
                }
                stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName());
 
                List<String> keys = Collections.singletonList(stringBuffer.toString());
 
                Number number = RedisUtils.execute(redisScript, keys, rateLimit.count(), rateLimit.time());
 
                if (number != null && number.intValue() != 0 && number.intValue() <= rateLimit.count()) {
                    logger.info("限流時間段內(nèi)訪問第:{} 次", number.toString());
                    return joinPoint.proceed();
                }else {
                    logger.error("已經(jīng)到設(shè)置限流次數(shù),當前次數(shù):{}",number.toString());
                    throw new RateLimiterException("服務(wù)器繁忙,請稍后再試");
                }
            } else {
                return joinPoint.proceed();
            }
        }finally {
            ipThreadLocal.remove();
        }
    }
 
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
            }
            // 對于通過多個代理的情況,第一個IP為客戶端真實IP,多個IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) {
                // "***.***.***.***".length()= 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress = "";
        }
        return ipAddress;
    }
}

Spring data redis提供了DefaultRedisScript來使用lua和redis進行交互,具體的詳情網(wǎng)上很多文章,這里使用ThreadLocal是因為IP存在可變的,保證自己的線程的IP不會被其他線程所修改,切記要最后清理ThreadLocal,防止內(nèi)存泄漏

RedisUtils工具類(方法太多,只展示execute方法)

package com.shinedata.config.redis;
 
import org.checkerframework.checker.units.qual.K;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
 
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
 
/**
 * @ClassName RedisUtils
 * @Author yupanpan
 * @Date 2019/11/20 13:38
 */
@Component
public class RedisUtils {
 
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate<String, Object> redisTemplate;
 
    private static RedisUtils redisUtils;
    
    @PostConstruct
    public void init() {
        redisUtils = this;
        redisUtils.redisTemplate = this.redisTemplate;
    }
 
    public static Number execute(DefaultRedisScript<Number> script, List keys, Object... args) {
        return redisUtils.redisTemplate.execute(script, keys,args);
    }
}

自己配置的RedisTemplate

package com.shinedata.config.redis;
 
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
 
/**
 * @ClassName RedisConfig
 * @Author yupanpan
 * @Date 2019/11/20 13:26
 */
@Configuration
public class RedisConfig extends RedisProperties{
 
    protected Logger log = LogManager.getLogger(RedisConfig.class);
 
    /**
     * JedisPoolConfig 連接池
     * @return
     */
    @Bean("jedisPoolConfig")
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大空閑數(shù)
        jedisPoolConfig.setMaxIdle(500);
        jedisPoolConfig.setMinIdle(100);
        // 連接池的最大數(shù)據(jù)庫連接數(shù)
        jedisPoolConfig.setMaxTotal(6000);
        // 最大建立連接等待時間
        jedisPoolConfig.setMaxWaitMillis(5000);
        // 逐出連接的最小空閑時間 默認1800000毫秒(30分鐘)
        jedisPoolConfig.setMinEvictableIdleTimeMillis(100);
        // 每次逐出檢查時 逐出的最大數(shù)目 如果為負數(shù)就是 : 1/abs(n), 默認3
//        jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
        // 逐出掃描的時間間隔(毫秒) 如果為負數(shù),則不運行逐出線程, 默認-1
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(600);
        // 是否在從池中取出連接前進行檢驗,如果檢驗失敗,則從池中去除連接并嘗試取出另一個
        jedisPoolConfig.setTestOnBorrow(true);
        // 在空閑時檢查有效性, 默認false
        jedisPoolConfig.setTestWhileIdle(false);
        return jedisPoolConfig;
    }
 
    /**
     * JedisConnectionFactory
     * @param jedisPoolConfig
     */
    @Bean("jedisConnectionFactory")
    public JedisConnectionFactory jedisConnectionFactory(@Qualifier("jedisPoolConfig")JedisPoolConfig jedisPoolConfig) {
        JedisConnectionFactory JedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
        // 連接池
        JedisConnectionFactory.setPoolConfig(jedisPoolConfig);
        // IP地址
        JedisConnectionFactory.setHostName(redisHost);
        // 端口號
        JedisConnectionFactory.setPort(redisPort);
        // 如果Redis設(shè)置有密碼
         JedisConnectionFactory.setPassword(redisPassword);
        // 客戶端超時時間單位是毫秒
        JedisConnectionFactory.setTimeout(10000);
        return JedisConnectionFactory;
    }
 
    /**
     * 實例化 RedisTemplate 對象代替原有的RedisTemplate<String, String>
     * @return
     */
    @Bean("redisTemplate")
    public RedisTemplate<String, Object> functionDomainRedisTemplate(@Qualifier("jedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
        return redisTemplate;
    }
 
    /**
     * 設(shè)置數(shù)據(jù)存入 redis 的序列化方式
     * @param redisTemplate
     * @param factory
     */
    private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
        // 如果不配置Serializer,那么存儲的時候缺省使用String,比如如果用User類型存儲,那么會提示錯誤User can't cast
        // to String!
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        // 開啟事務(wù)/true必須手動釋放連接,false會自動釋放連接 如果調(diào)用方有用@Transactional做事務(wù)控制,可以開啟事務(wù),Spring會處理連接問題
        redisTemplate.setEnableTransactionSupport(false);
        redisTemplate.setConnectionFactory(factory);
    }
}

全局Controller異常處理GlobalExceptionHandler

package com.shinedata.exception;
 
import com.fasterxml.jackson.databind.JsonMappingException;
import com.shinedata.util.ResultData;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
 
@RestControllerAdvice
public class GlobalExceptionHandler {
 
    private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
 
    @ExceptionHandler(value = RateLimiterException.class)
    @ResponseStatus(HttpStatus.OK)
    public ResultData runtimeExceptionHandler(RateLimiterException e) {
        logger.error("系統(tǒng)錯誤:", e);
        return ResultData.getResultError(StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : "處理失敗");
    }
 
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.OK)
    public ResultData runtimeExceptionHandler(RuntimeException e) {
        Throwable cause = e.getCause();
        logger.error("系統(tǒng)錯誤:", e);
        logger.error(e.getMessage());
        if (cause instanceof JsonMappingException) {
            return ResultData.getResultError("參數(shù)錯誤");
        }
        return ResultData.getResultError(StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : "處理失敗");
    }
 
}

使用就很簡單了,一個注解搞定

補充:優(yōu)化了lua為

local key = KEYS[1]
local limitCount = tonumber(ARGV[1])
local limitTime = tonumber(ARGV[2])
local current = redis.call('get', key);
if current then
    redis.call("INCRBY", key,"1")
    return current + 1
else
    redis.call("set", key,"1")
    redis.call("expire", key,limitTime)
    return 1
end

到此這篇關(guān)于SpringBoot+Redis+Lua分布式限流的實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot Redis Lua分布式限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring中propagation的傳播機制詳解

    Spring中propagation的傳播機制詳解

    這篇文章主要介紹了Spring中propagation的傳播機制詳解,要搞懂事務(wù)的傳播機制,那么就要明白邏輯事務(wù)中各個事務(wù)的關(guān)系,才能徹底理解事務(wù)傳播特性,在Spring事務(wù)中,各個邏輯事務(wù)的關(guān)系可以是并列、覆蓋或包含,需要的朋友可以參考下
    2023-12-12
  • 重新實現(xiàn)hashCode()方法

    重新實現(xiàn)hashCode()方法

    hashCode()是Java中的一個重要方法,用于計算對象的哈希碼。本文介紹了如何重新實現(xiàn)hashCode()方法,包括使用對象的屬性計算哈希碼、使用字符串拼接計算哈希碼、使用隨機數(shù)計算哈希碼等方法。同時,還介紹了如何避免哈希沖突,提高哈希表的效率。
    2023-04-04
  • Intellij IDEA 關(guān)閉和開啟自動更新的提示?

    Intellij IDEA 關(guān)閉和開啟自動更新的提示?

    這篇文章主要介紹了Intellij IDEA 關(guān)閉和開啟自動更新的提示操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • java微信開發(fā)API第一步 服務(wù)器接入

    java微信開發(fā)API第一步 服務(wù)器接入

    這篇文章主要為大家分享了java微信開發(fā)API的第一步操作服務(wù)器接入,感興趣的小伙伴們可以參考一下
    2016-05-05
  • 詳解java設(shè)計模式中的門面模式

    詳解java設(shè)計模式中的門面模式

    門面模式又叫外觀模式(Facade?Pattern),主要用于隱藏系統(tǒng)的復雜性,并向客戶端提供了一個客戶端可以訪問系統(tǒng)的接口,本文通過實例代碼給大家介紹下java門面模式的相關(guān)知識,感興趣的朋友一起看看吧
    2022-02-02
  • Spring中容器的創(chuàng)建流程詳細解讀

    Spring中容器的創(chuàng)建流程詳細解讀

    這篇文章主要介紹了Spring中容器的創(chuàng)建流程詳細解讀,Spring?框架其本質(zhì)是作為一個容器,提供給應(yīng)用程序需要的對象,了解容器的誕生過程,有助于我們理解?Spring?框架,也便于我們“插手”這個過程,需要的朋友可以參考下
    2023-10-10
  • Java ConcurrentHashMap鎖分段機制使用及代碼實例

    Java ConcurrentHashMap鎖分段機制使用及代碼實例

    ConcurrentHashMap是Java中的一種線程安全的哈希表,通過鎖分段機制提高了并發(fā)性能,在Java 8中,ConcurrentHashMap引入了CAS操作和更復雜的節(jié)點繼承結(jié)構(gòu),進一步優(yōu)化了并發(fā)操作
    2025-01-01
  • spring security的基本原理、配置使用方式

    spring security的基本原理、配置使用方式

    本文詳細介紹了SpringSecurity的基本原理、配置方式、常見使用場景以及常見問題的解決方法,還介紹了SpringSecurity與OAuth2、SSO等系統(tǒng)的整合,并提供了調(diào)試與排錯建議,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • idea查看java文件的全部接口的操作方法

    idea查看java文件的全部接口的操作方法

    在Java項目開發(fā)中,快速查看和理解接口至關(guān)重要,通過IntelliJ IDEA,開發(fā)人員可以高效地瀏覽Java文件中的所有接口信息,本文介紹了在IDEA中實現(xiàn)查看Java文件全部接口的方法,感興趣的朋友一起看看吧
    2024-10-10
  • SpringBoot2零基礎(chǔ)到精通之數(shù)據(jù)庫專項精講

    SpringBoot2零基礎(chǔ)到精通之數(shù)據(jù)庫專項精講

    SpringBoot是一種整合Spring技術(shù)棧的方式(或者說是框架),同時也是簡化Spring的一種快速開發(fā)的腳手架,本篇我們來學習如何連接數(shù)據(jù)庫進行操作
    2022-03-03

最新評論

庆安县| 无极县| 綦江县| 拜城县| 宝鸡市| 呼玛县| 平原县| 车致| 长岛县| 德令哈市| 松江区| 南召县| 布尔津县| 泰宁县| 南平市| 扎兰屯市| 沙雅县| 称多县| 上蔡县| 泰宁县| 玉田县| 阜平县| 山东| 罗定市| 东海县| 沐川县| 湖北省| 甘谷县| 清远市| 霞浦县| 西畴县| 乐陵市| 新疆| 高青县| 靖州| 渑池县| 忻州市| 常山县| 通州市| 汕尾市| 凤翔县|