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

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

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

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

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

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

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

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

local key = KEYS[1]   --限流KEY
local limitCount = tonumber(ARGV[1])       --限流大小
local limitTime = tonumber(ARGV[2])        --限流時(shí)間
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 {
 
    /**
     * 限流唯一標(biāo)識(shí)
     * @return
     */
    String key() default "rate.limit:";
 
    /**
     * 限流時(shí)間
     * @return
     */
    int time() default 1;
 
    /**
     * 限流次數(shù)
     * @return
     */
    int count() default 100;
 
    /**
     *是否限制IP,默認(rèn) 否
     * @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("限流時(shí)間段內(nèi)訪問第:{} 次", number.toString());
                    return joinPoint.proceed();
                }else {
                    logger.error("已經(jīng)到設(shè)置限流次數(shù),當(dāng)前次數(shù):{}",number.toString());
                    throw new RateLimiterException("服務(wù)器繁忙,請(qǐng)稍后再試");
                }
            } 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();
            }
            // 對(duì)于通過多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)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進(jìn)行交互,具體的詳情網(wǎng)上很多文章,這里使用ThreadLocal是因?yàn)镮P存在可變的,保證自己的線程的IP不會(huì)被其他線程所修改,切記要最后清理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);
        // 最大建立連接等待時(shí)間
        jedisPoolConfig.setMaxWaitMillis(5000);
        // 逐出連接的最小空閑時(shí)間 默認(rèn)1800000毫秒(30分鐘)
        jedisPoolConfig.setMinEvictableIdleTimeMillis(100);
        // 每次逐出檢查時(shí) 逐出的最大數(shù)目 如果為負(fù)數(shù)就是 : 1/abs(n), 默認(rèn)3
//        jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
        // 逐出掃描的時(shí)間間隔(毫秒) 如果為負(fù)數(shù),則不運(yùn)行逐出線程, 默認(rèn)-1
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(600);
        // 是否在從池中取出連接前進(jìn)行檢驗(yàn),如果檢驗(yàn)失敗,則從池中去除連接并嘗試取出另一個(gè)
        jedisPoolConfig.setTestOnBorrow(true);
        // 在空閑時(shí)檢查有效性, 默認(rèn)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);
        // 端口號(hào)
        JedisConnectionFactory.setPort(redisPort);
        // 如果Redis設(shè)置有密碼
         JedisConnectionFactory.setPassword(redisPassword);
        // 客戶端超時(shí)時(shí)間單位是毫秒
        JedisConnectionFactory.setTimeout(10000);
        return JedisConnectionFactory;
    }
 
    /**
     * 實(shí)例化 RedisTemplate 對(duì)象代替原有的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,那么存儲(chǔ)的時(shí)候缺省使用String,比如如果用User類型存儲(chǔ),那么會(huì)提示錯(cuò)誤User can't cast
        // to String!
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        // 開啟事務(wù)/true必須手動(dòng)釋放連接,false會(huì)自動(dòng)釋放連接 如果調(diào)用方有用@Transactional做事務(wù)控制,可以開啟事務(wù),Spring會(huì)處理連接問題
        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)錯(cuò)誤:", 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)錯(cuò)誤:", e);
        logger.error(e.getMessage());
        if (cause instanceof JsonMappingException) {
            return ResultData.getResultError("參數(shù)錯(cuò)誤");
        }
        return ResultData.getResultError(StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : "處理失敗");
    }
 
}

使用就很簡(jiǎn)單了,一個(gè)注解搞定

補(bǔ)充:優(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分布式限流的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot Redis Lua分布式限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring中propagation的傳播機(jī)制詳解

    Spring中propagation的傳播機(jī)制詳解

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

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

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

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

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

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

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

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

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

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

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

    Java ConcurrentHashMap鎖分段機(jī)制使用及代碼實(shí)例

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

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

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

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

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

    SpringBoot2零基礎(chǔ)到精通之?dāng)?shù)據(jù)庫專項(xiàng)精講

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

最新評(píng)論

沅江市| 邵阳市| 日照市| 左云县| 德兴市| 太和县| 颍上县| 清苑县| 泽普县| 普陀区| 馆陶县| 若尔盖县| 白沙| 普定县| 南阳市| 阿巴嘎旗| 临汾市| 滦平县| 台湾省| 台山市| 呼玛县| 察雅县| 鄄城县| 曲沃县| 牟定县| 莲花县| 六枝特区| 永泰县| 紫阳县| 长岭县| 四会市| 云梦县| 安徽省| 留坝县| 读书| 安义县| 比如县| 满城县| 济南市| 英山县| 三都|