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

SpringBoot+Redis+Lua實現(xiàn)接口限流的示例代碼

 更新時間:2025年09月19日 10:37:09   作者:月生_  
本文主要介紹了SpringBoot+Redis+Lua實現(xiàn)接口限流的示例代碼,利用AOP攔截請求,通過Redis記錄訪問次數(shù),達到限流效果,具有一定的參考價值,感興趣的可以了解一下

序言

Lua 是一種輕量小巧的腳本語言,用標準C語言編寫并以源代碼形式開放, 其設(shè)計目的是為了嵌入應(yīng)用程序中,從而為應(yīng)用程序提供靈活的擴展和定制功能。這篇文章圍繞Radis和Lua腳本來實現(xiàn)接口的限流

1.導(dǎo)入依賴

Lua腳本其在Redis2.6及以上的版本就已經(jīng)內(nèi)置了,所以需要導(dǎo)入的依賴如下:

<dependencies> 
    <dependency> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-web</artifactId> 
    </dependency> 
    <dependency> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-aop</artifactId> 
    </dependency> 
    <dependency> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-data-redis</artifactId> 
    </dependency> 
</dependencies>

2.配置Redis環(huán)境

依賴導(dǎo)入成功后,需要在項目當中配置Redis的環(huán)境,這是程序和Redis交互的重要步驟。 在SpringBoot項目的資源路徑下找到application.yml配置文件,內(nèi)容如下:

spring: 
    redis: host: 127.0.0.1 
    port: 6379 
    database: 0 
    password: 
    timeout: 10s 
    lettuce: 
        pool: 
            min-idle: 0  #連接池中的最小空閑連接數(shù)為 0。這意味著在沒有任何請求時,連接池可以沒有空閑連接。
            max-idle: 8  #連接池中的最大空閑連接數(shù)為 8。當連接池中的空閑連接數(shù)超過這個值時,多余的連接可能會被關(guān)閉以節(jié)省資源。
            max-active: 8  #連接池允許的最大活動連接數(shù)為 8。在并發(fā)請求較高時,連接池最多可以創(chuàng)建 8 個連接來滿足需求。
            max-wait: -1ms  #當連接池中的連接都被使用且沒有空閑連接時,新的連接請求等待獲取連接的最大時間。這里設(shè)置為 -1ms,表示無限等待,直到有可用連接為止。
           

3.創(chuàng)建限流類型

我們既然需要對一個接口進行限流,那么就需要配置應(yīng)該以何種規(guī)則進行限流,比如ip地址、地理位置限流等,我們這里以ip限流為例。創(chuàng)建限流枚舉類:

public enum LimitType { 
/** * 針對某一個ip進行限流 */ 
    IP("IP") ; 
    
    private final String type; 
    
    LimitType(String type) { 
        this.type = type; 
    } 
    
    
    public String getType() { 
        return type; 
    } 
}

4.創(chuàng)建限流注解

自定義限流類型完成以后,需要定義限流注解,然后在需要被限流訪問的接口上添加上限流注解,結(jié)合AOP切面即可實現(xiàn)限流的操作。限流注解定義如下:

import java.lang.annotation.*; 


@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
public @interface RateLimiter { 
    /** * 限流類型 * @return */ 
    LimitType limitType() default LimitType.IP; 
    
    /** * 限流key * @return */ 
    String key() default ""; 
    
    /** * 限流時間 * @return */ 
    int time() default 60; 
    
    /** * 限流次數(shù) * @return */ 
    int count() default 100; 
}

5.編寫限流的Lua腳本

我這里是創(chuàng)建了一個.lua結(jié)尾的文件,并把文章放在了項目資源的根路徑下,也可以不創(chuàng)建文件,而是使用文本字符串的方式來編寫腳本內(nèi)容(稍后說明)。Lua腳本內(nèi)容如下:

local key = KEYS[1] 
local time = tonumber(ARGV[1]) 
local count = tonumber(ARGV[2]) 

local current = redis.call('get', key) 

if current and tonumber(current) > count then 
    return tonumber(current) 
end 

current = redis.call('incr', key) 

if tonumber(current) == 1 then 
    redis.call('expire', key, time)
end 

return tonumber(current) 

說明:redis.call('incr', key)命令可以使在Lua腳本調(diào)用Redis中的命令,該行代碼的意思是使緩存中Key所對應(yīng)的value值自增,如果Redis中Key所對應(yīng)的值超過了count (限流次數(shù)),則直接返回count數(shù)量,如果沒有超過count數(shù)量,則使value值+1

6.配置RedisConfig

接下來,需要配置RedisConfig的內(nèi)容,比如以哪種序列化方式來序列化Key和Value,以及腳本執(zhí)行器。代碼如下:

import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.core.annotation.Order; 
import org.springframework.core.io.ClassPathResource; 
import org.springframework.data.redis.connection.RedisConnectionFactory; 
import org.springframework.data.redis.core.RedisTemplate; 
import org.springframework.data.redis.core.script.DefaultRedisScript; 
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 
import org.springframework.data.redis.serializer.StringRedisSerializer; 
import org.springframework.scripting.support.ResourceScriptSource; 
import java.nio.charset.Charset; 
import java.nio.charset.StandardCharsets; 


@Configuration public class RedisConfig { 

    /** * RedisTemplate配置 * * @param factory * @return */ 
    @Bean 
    RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {  
            RedisTemplate<String, Object> template = new RedisTemplate<>(); 
            template.setConnectionFactory(factory); 
            StringRedisSerializer serializer = new StringRedisSerializer(StandardCharsets.UTF_8); 
            
            // 使用StringRedisSerializer來序列化和反序列化redis的key值 
            template.setKeySerializer(serializer); 
            template.setValueSerializer(serializer); 
            
            template.setHashKeySerializer(serializer); 
            template.setHashValueSerializer(serializer); 
            
            template.afterPropertiesSet(); 
            
            return template; 
    } 
    
    
    
    /** * Redis Lua 腳本 * * @return */ 
    @Bean DefaultRedisScript<Long> limitScript() { 
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(); 
        script.setResultType(Long.class); 
        // 我這里是以資源文件的形式來加載的lua腳本
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua"))); 
        
        return script; 
       
    } 
}

也可以使用字符串文本的形式來加載

@Bean 
DefaultRedisScript<Long> limitScript() { 
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(); 
        script.setResultType(Long.class);
        
         // 也可以使用文本字符串的形式來加載Lua腳本
        script.setScriptText("local key = KEYS[1]\n" +
                "local time = tonumber(ARGV[1])\n" +
                "local count = tonumber(ARGV[2])\n" +
                "local current = redis.call('get', key)\n" +
                "\n" +
                "if current and tonumber(current) > count then\n" +
                "    return tonumber(current)\n" +
                "end\n" +
                "\n" +
                "current = redis.call('incr', key)\n" +
                "\n" +
                "if tonumber(current) == 1 then\n" +
                "    redis.call('expire', key, time)\n" +
                "end\n" +
                "\n" +
                "return tonumber(current)\n" +
                "\n");
        return script;
}

7.編寫限流切面 RateLimitAspect

前面的步驟完成之后,到了最后一步,編寫限流的注解的AOP切換,在切面中通過Redis調(diào)用Lua腳本來判斷當前請求是否達到限流的條件,如果達到則為拋出錯誤,由全局異常捕獲返回給前端

import com.example.luatest.annotition.RateLimiter; 
import com.example.luatest.exception.IPException; 
import lombok.extern.java.Log; 
import org.aspectj.lang.JoinPoint; 
import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.Before; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.context.annotation.Lazy; 
import org.springframework.data.redis.core.RedisTemplate; 
import org.springframework.data.redis.core.StringRedisTemplate; 
import org.springframework.data.redis.core.script.DefaultRedisScript; 
import org.springframework.stereotype.Component; 
import javax.annotation.Resource; 
import javax.servlet.http.HttpServletRequest; 
import java.util.Collections; 
import java.util.List; 

@Aspect 
@Component //切面類也需要加入到ioc容器 
public class RateLimitAspect { 

    private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitAspect.class); 
    
    private final RedisTemplate<String, Object> redisTemplate; 
    
    private final DefaultRedisScript<Long> limitScript; 
    
    public RateLimitAspect(RedisTemplate<String, Object> redisTemplate, DefaultRedisScript<Long> limitScript) { 
        this.redisTemplate = redisTemplate; 
        this.limitScript = limitScript; 
    } 
    
    @Before("@annotation(rateLimiter)") 
    public void isAllowed(JoinPoint proceedingJoinPoint, RateLimiter rateLimiter) throws IPException, InstantiationException, IllegalAccessException { 
        String ip = null; 
        Object[] args = proceedingJoinPoint.getArgs(); 
        for (Object arg : args) { 
            if (arg instanceof HttpServletRequest) { 
                HttpServletRequest request = (HttpServletRequest) arg; 
                ip = request.getRemoteHost(); break;
            } 
        } 
    
        LOGGER.info("ip:{}", ip); 
        if (ip == null) { 
            throw new IPException("ip is null"); 
        } 
    
        //拼接redis建 
        String key = rateLimiter.key() + ip; 

        // 執(zhí)行 Lua 腳本進行限流判斷
        List<String> keyList = Collections.singletonList(key); 
    
        Long result = redisTemplate.execute(limitScript, keyList, key, Integer.toString(rateLimiter.count()), Integer.toString(rateLimiter.time())); 

        LOGGER.info("result:{}", result); 

        if (result != null && result > rateLimiter.count()) { 
            throw new IPException("IP [" + ip + "] 訪問過于頻繁,已超出限流次數(shù)"); 
        } 
    }
}

8.使用注解

最后在方法上使用該限流注解即可

import com.example.luatest.annotition.RateLimiter; 
import com.example.luatest.enum_.LimitType; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController; 
import javax.servlet.http.HttpServletRequest; 

@RestController 
@RequestMapping("/rate") 
public class RateController { 

    @RateLimiter(count = 100, time = 60, limitType = LimitType.IP) @GetMapping("/someMethod") 
    public void someMethod(HttpServletRequest request) { 
        // 方法的具體邏輯 
    } 
}

總結(jié):

這篇文章到這里就結(jié)束,總的來說具體思路比較簡單,我們通過創(chuàng)建限流注解,定義限流次數(shù)和間隔時間,然后對該注解進行AOP切面,在切面當中調(diào)用Lua腳本來判斷是否達到限流條件,如果達到就拋出錯誤,由全局異常捕獲,沒有則代碼繼續(xù)執(zhí)行。

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

相關(guān)文章

  • Java 類加載機制的應(yīng)用案例

    Java 類加載機制的應(yīng)用案例

    Java類加載機制通過雙親委派模型分階段加載類(加載、驗證、準備、解析、初始化等),確保類唯一性與安全性,支持熱部署、插件隔離等場景,是解決類沖突和開發(fā)框架的核心技術(shù),下面給大家介紹Java 類加載機制的應(yīng)用案例,感興趣的朋友跟隨小編一起看看吧
    2025-08-08
  • java中hasNextInt判斷后無限循環(huán)輸出else項的解決方法

    java中hasNextInt判斷后無限循環(huán)輸出else項的解決方法

    這篇文章主要介紹了java中hasNextInt判斷后無限循環(huán)輸出else項的解決方法的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • SpringBoot常見錯誤圖文總結(jié)

    SpringBoot常見錯誤圖文總結(jié)

    最近在使用idea+Springboot開發(fā)項目中遇到一些問題,這篇文章主要給大家介紹了關(guān)于SpringBoot常見錯誤總結(jié)的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-06-06
  • 一篇文章帶你入門Java多線程

    一篇文章帶你入門Java多線程

    這篇文章主要介紹了java多線程編程實例,分享了幾則多線程的實例代碼,具有一定參考價值,加深多線程編程的理解還是很有幫助的,需要的朋友可以參考下
    2021-08-08
  • Java中AutoCloseable接口使用小結(jié)

    Java中AutoCloseable接口使用小結(jié)

    Java7引入的AutoCloseable接口,用于自動管理資源的關(guān)閉,避免資源泄露,下面就來介紹一下Java中AutoCloseable接口使用,具有一定的參考價值,感興趣的可以了解一下
    2025-01-01
  • MyBatis-Plus?Mapper?接口方法操作詳解

    MyBatis-Plus?Mapper?接口方法操作詳解

    MyBatis-Plus的BaseMapper提供了豐富的開箱即用方法,所有方法都不需要編寫SQL即可使用,可以滿足95%以上的日常開發(fā)需求,本文給大家介紹MyBatis-Plus?Mapper?接口方法詳解,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • SpringMvc自動裝箱及GET請求參數(shù)原理解析

    SpringMvc自動裝箱及GET請求參數(shù)原理解析

    這篇文章主要介紹了SpringMvc自動裝箱及GET請求參數(shù)原理解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-09-09
  • 深入解析Java中的JDBC事務(wù)

    深入解析Java中的JDBC事務(wù)

    這篇文章主要介紹了深入解析Java中的JDBC事務(wù),包含了Java多線程的事務(wù)并發(fā)等知識,需要的朋友可以參考下
    2015-08-08
  • Thymeleaf中th:each及th:if使用方法解析

    Thymeleaf中th:each及th:if使用方法解析

    這篇文章主要介紹了Thymeleaf中th:each及th:if使用方法解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-08-08
  • spring boot的健康檢查HealthIndicators實戰(zhàn)

    spring boot的健康檢查HealthIndicators實戰(zhàn)

    這篇文章主要介紹了spring boot的健康檢查HealthIndicators實戰(zhàn),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10

最新評論

衡水市| 高邮市| 烟台市| 上杭县| 东乡县| 荥阳市| 满洲里市| 巴青县| 蒙自县| 左云县| 长垣县| 方正县| 盐津县| 安图县| 阿荣旗| 大渡口区| 镇平县| 容城县| 东乡| 德江县| 石嘴山市| 阿拉善盟| 信阳市| 江津市| 永济市| 呼玛县| 板桥市| 镇原县| 从江县| 荆门市| 武宣县| 巩义市| 鄂伦春自治旗| 左云县| 都江堰市| 镇雄县| 邯郸市| 融水| 子长县| 河源市| 睢宁县|