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

利用redis lua腳本實現(xiàn)時間窗分布式限流

 更新時間:2024年03月26日 09:42:14   作者:DimonHo  
Lua是一種輕量小巧的腳本語言,Redis是高性能的key-value內(nèi)存數(shù)據(jù)庫,在部分場景下,是對關(guān)系數(shù)據(jù)庫的良好補充,本文給大家介紹了如何利用redis lua腳本實現(xiàn)時間窗分布式限流,需要的朋友可以參考下

需求背景:

限制某sql在30秒內(nèi)最多只能執(zhí)行3次

需求分析

微服務(wù)分布式部署,既然是分布式限流,首先自然就想到了結(jié)合redis的zset數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)。
分析對zset的操作,有幾個步驟,首先,判斷zset中符合rangeScore的元素個數(shù)是否已經(jīng)達(dá)到閾值,如果未達(dá)到閾值,則add元素,并返回true。如果已達(dá)到閾值,則直接返回false。

代碼實現(xiàn)

首先,我們需要根據(jù)需求編寫一個lua腳本

redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, tonumber(ARGV[3]))
local res = 0
if(redis.call('ZCARD', KEYS[1]) < tonumber(ARGV[5])) then
    redis.call('ZADD', KEYS[1], tonumber(ARGV[2]), ARGV[1])
    res = 1
end
redis.call('EXPIRE', KEYS[1], tonumber(ARGV[4]))
return res

ARGV[1]: zset element
ARGV[2]: zset score(當(dāng)前時間戳)
ARGV[3]: 30秒前的時間戳
ARGV[4]: zset key 過期時間30秒
ARGV[5]: 限流閾值

private final RedisTemplate<String, Object> redisTemplate;

public boolean execLuaScript(String luaStr, List<String> keys, List<Object> args){
	RedisScript<Boolean> redisScript = RedisScript.of(luaStr, Boolean.class)
	return redisTemplate.execute(redisScript, keys, args.toArray());
}

測試一下效果

@SpringBootTest
public class ApiApplicationTest {
    @Test
    public void test2() throws InterruptedException{
        String luaStr = "redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, tonumber(ARGV[3]))\n" +
                "local res = 0\n" +
                "if(redis.call('ZCARD', KEYS[1]) < tonumber(ARGV[5])) then\n" +
                "    redis.call('ZADD', KEYS[1], tonumber(ARGV[2]), ARGV[1])\n" +
                "    res = 1\n" +
                "end\n" +
                "redis.call('EXPIRE', KEYS[1], tonumber(ARGV[4]))\n" +
                "return res";
        for (int i = 0; i < 10; i++) {
            boolean res = execLuaScript(luaStr, Arrays.asList("aaaa"), Arrays.asList("ele"+i, System.currentTimeMillis(),System.currentTimeMillis()-30*1000, 30, 3));
            System.out.println(res);
            Thread.sleep(5000);
        }
    }
}

測試結(jié)果符合預(yù)期!

擴展閱讀

lua腳本每次都需要傳一長串腳本內(nèi)容來回傳輸,會增加網(wǎng)絡(luò)流量和延遲,而且每次都需要服務(wù)器重新解釋和編譯,效率較為低下。因此,不建議在實際生產(chǎn)環(huán)境中直接執(zhí)行l(wèi)ua腳本,而應(yīng)該使用lua腳本的hash值來進(jìn)行傳輸。

為了方便使用,我們先把方法封裝一下

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.RedisScriptingCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author 敖癸
 * @formatter:on
 * @since 2024/3/25
 */
@Component
@RequiredArgsConstructor
public class RedisService {

    private final RedisTemplate<String, Object> redisTemplate;
    private static RedisScriptingCommands commands;
    private static RedisSerializer keySerializer;
    private static RedisSerializer valSerializer;
    
    public String loadScript(String luaStr) {
        byte[] bytes = RedisSerializer.string().serialize(luaStr);
        return this.getCommands().scriptLoad(bytes);
    }

    public <T> T execLuaHashScript(String hash, Class<T> returnType, List<String> keys, Object[] args) {
        byte[][] keysAndArgs = toByteArray(this.getKeySerializer(), this.getValSerializer(), keys, args);
        return this.getCommands().evalSha(hash, ReturnType.fromJavaType(returnType), keys.size(), keysAndArgs);
    }

    private static byte[][] toByteArray(RedisSerializer keySerializer, RedisSerializer argsSerializer, List<String> keys, Object[] args) {
        final int keySize = keys != null ? keys.size() : 0;
        byte[][] keysAndArgs = new byte[args.length + keySize][];
        int i = 0;
        if (keys != null) {
            for (String key : keys) {
                keysAndArgs[i++] = keySerializer.serialize(key);
            }
        }
        for (Object arg : args) {
            if (arg instanceof byte[]) {
                keysAndArgs[i++] = (byte[]) arg;
            } else {
                keysAndArgs[i++] = argsSerializer.serialize(arg);
            }
        }
        return keysAndArgs;
    }

    private RedisScriptingCommands getCommands() {
        if (commands == null) {
            commands = redisTemplate.getRequiredConnectionFactory().getConnection().scriptingCommands();
        }
        return commands;
    }

    private RedisSerializer getKeySerializer() {
        if (keySerializer == null) {
            keySerializer = redisTemplate.getKeySerializer();
        }
        return keySerializer;
    }

    private RedisSerializer getValSerializer() {
        if (valSerializer == null) {
            valSerializer = redisTemplate.getValueSerializer();
        }
        return valSerializer;
    }
}
  • 測試一下:
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ApiApplicationTest implements ApplicationContextAware {

    private static ApplicationContext context;
    private static RedisService redisService;
    public static String luaHash;

    private final static String LUA_STR = "redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, tonumber(ARGV[3]))\n" +
            "local res = 0\n" +
            "if(redis.call('ZCARD', KEYS[1]) < tonumber(ARGV[5])) then\n" +
            "    redis.call('ZADD', KEYS[1], tonumber(ARGV[2]), ARGV[1])\n" +
            "    res = 1\n" +
            "end\n" +
            "redis.call('EXPIRE', KEYS[1], tonumber(ARGV[4]))\n" +
            "return res";

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    @BeforeAll
    public static void before(){
        redisService = context.getBean(RedisService.class);
        luaHash = redisService.loadScript(LUA_STR);
        System.out.println("lua腳本hash: "+ luaHash);
    }


    @Test
    public void testLuaHash() throws InterruptedException {
        for (int i = 0; i < 50; i++) {
            List<String> keys = Collections.singletonList("aaaa");
            Object[] args = new Object[]{"ele" + i, System.currentTimeMillis(), System.currentTimeMillis() - 30 * 1000, 30, 3};
            Boolean b = redisService.execLuaHashScript(luaHash, Boolean.class, keys, args);
            System.out.println(b);
            Thread.sleep(3000);
        }
    }
}

使用的時候在項目啟動時候,把腳本load一下,后續(xù)直接用hash值就行了

搞定收工!

以上就是利用redis lua腳本實現(xiàn)時間窗分布式限流的詳細(xì)內(nèi)容,更多關(guān)于redis lua時間窗分布式限流的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Redis配置文件redis.conf詳細(xì)配置說明

    Redis配置文件redis.conf詳細(xì)配置說明

    本文列出了Redis的配置文件redis.conf的各配置項的詳細(xì)說明,簡單易懂
    2018-03-03
  • redis yml配置的用法小結(jié)

    redis yml配置的用法小結(jié)

    RedisYML配置是Redis的一種配置文件格式,,對Redis的配置進(jìn)行統(tǒng)一管理,本文就來介紹了redis yml配置的用法小結(jié),具有一定的參考價值,感興趣的可以了解一下
    2024-02-02
  • redis中刪除操作命令

    redis中刪除操作命令

    這篇文章主要介紹了redis中刪除操作命令,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Redis消息隊列實現(xiàn)秒殺教程

    Redis消息隊列實現(xiàn)秒殺教程

    這篇文章主要介紹了Redis消息隊列實現(xiàn)秒殺教程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Redis的數(shù)據(jù)淘汰策略使用及注意事項

    Redis的數(shù)據(jù)淘汰策略使用及注意事項

    Redis的數(shù)據(jù)淘汰策略是指在內(nèi)存不足時,如何決定哪些數(shù)據(jù)需要被淘汰以釋放內(nèi)存空間,Redis提供了多種數(shù)據(jù)淘汰策略,如noeviction、allkeys-lru、allkeys-lfu、volatile-lru、volatile-lfu、volatile-random、allkeys-random和volatile-ttl
    2025-12-12
  • Redis五種數(shù)據(jù)結(jié)構(gòu)在JAVA中如何封裝使用

    Redis五種數(shù)據(jù)結(jié)構(gòu)在JAVA中如何封裝使用

    本篇博文就針對Redis的五種數(shù)據(jù)結(jié)構(gòu)以及如何在JAVA中封裝使用做一個簡單的介紹。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-11-11
  • Redis隊列和阻塞隊列的實現(xiàn)

    Redis隊列和阻塞隊列的實現(xiàn)

    本文主要介紹了Redis隊列和阻塞隊列的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • Redis壓縮列表的設(shè)計與實現(xiàn)

    Redis壓縮列表的設(shè)計與實現(xiàn)

    壓縮列表(Ziplist)是 Redis 為了節(jié)省內(nèi)存而設(shè)計的一種緊湊型數(shù)據(jù)結(jié)構(gòu),主要用于存儲長度較短且數(shù)量較少的元素集合,本文給大家介紹了Redis壓縮列表的設(shè)計與實現(xiàn),文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下
    2024-08-08
  • Redis優(yōu)惠券秒殺解決方案

    Redis優(yōu)惠券秒殺解決方案

    這篇文章主要介紹了Redis解決優(yōu)惠券秒殺應(yīng)用案例,本文先講了搶購問題,指出其中會出現(xiàn)的多線程問題,提出解決方案采用悲觀鎖和樂觀鎖兩種方式進(jìn)行實現(xiàn),然后發(fā)現(xiàn)在搶購過程中容易出現(xiàn)一人多單現(xiàn)象,需要的朋友可以參考下
    2022-12-12
  • redis中key使用冒號分隔的原理小結(jié)

    redis中key使用冒號分隔的原理小結(jié)

    Redis是一種高性能的鍵值對非關(guān)系型數(shù)據(jù)庫,通過redis不同類型命令可以為其中的鍵指定不同的數(shù)據(jù)類型,其中每個鍵的命名規(guī)范通常使用冒號符號分隔字符串,本文主要介紹了redis中key使用冒號分隔的原理小結(jié),感興趣的可以了解一下
    2024-01-01

最新評論

桂东县| 白水县| 霸州市| 英德市| 抚顺县| 德庆县| 疏附县| 临洮县| 东乡族自治县| 福海县| 土默特左旗| 绍兴县| 洛宁县| 农安县| 腾冲县| 五华县| 抚宁县| 昌邑市| 英德市| 香格里拉县| 防城港市| 内乡县| 兴义市| 客服| 荥经县| 涟水县| 朝阳市| 桐庐县| 封开县| 金寨县| 辉县市| 澄江县| 五华县| 襄城县| 商河县| 南汇区| 潞城市| 盐池县| 崇阳县| 佛教| 通渭县|