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

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

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

需求背景:

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

需求分析

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

代碼實(shí)現(xiàn)

首先,我們需要根據(jù)需求編寫一個(gè)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)前時(shí)間戳)
ARGV[3]: 30秒前的時(shí)間戳
ARGV[4]: zset key 過期時(shí)間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ù)期!

擴(kuò)展閱讀

lua腳本每次都需要傳一長串腳本內(nèi)容來回傳輸,會(huì)增加網(wǎng)絡(luò)流量和延遲,而且每次都需要服務(wù)器重新解釋和編譯,效率較為低下。因此,不建議在實(shí)際生產(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);
        }
    }
}

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

搞定收工!

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

相關(guān)文章

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

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

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

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

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

    redis中刪除操作命令

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

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

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

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

    Redis的數(shù)據(jù)淘汰策略是指在內(nèi)存不足時(shí),如何決定哪些數(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中如何封裝使用

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

    Redis隊(duì)列和阻塞隊(duì)列的實(shí)現(xiàn)

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

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

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

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

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

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

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

最新評(píng)論

福鼎市| 景谷| 鄂伦春自治旗| 汪清县| 民和| 桃园县| 嘉祥县| SHOW| 彭州市| 宽甸| 珲春市| 灵山县| 教育| 辽阳县| 曲阜市| 岳阳县| 广东省| 和硕县| 镇巴县| 平山县| 西青区| 朝阳区| 涪陵区| 宜城市| 株洲市| 东阳市| 澜沧| 河津市| 陇南市| 新龙县| 通城县| 元氏县| 扶沟县| 河池市| 九台市| 石城县| 丹东市| 西峡县| 岳普湖县| 丰城市| 彭阳县|