SpringBoot3使用lua脚本实现常见的四种限流策略

背景

限流是一种用于控制系统资源利用率或确保服务质量的策略。

在Web应用中,限流通常用于控制接口请求的频率,防止过多的请求导致系统负载过大或者防止恶意攻击。

此篇文章将详细介绍SpringBoot项目中使用自定义注解和切面优雅的实现固定窗口限流,滑动窗口限流,漏桶限流,令牌桶限流。

并且会提供提供源码地址,欢迎大家一起交流。

源码地址GitHub - ray-shi-os/io.github.ray-shi-os: 基于redis集成的一套限流组件

 基本配置类

一、项目结构 

1、pom文件配置
<dependencies>
       <dependency>
           <groupId>cn.hutool</groupId>
           <artifactId>hutool-all</artifactId>
       </dependency>
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-databind</artifactId>
       </dependency>
       <dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-lang3</artifactId>
       </dependency>

       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</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、基础的类 
(1)自定义注解

        com.limit.rule.annotation.LimitRule

package com.limit.rule.annotation;


import com.limit.rule.enums.LimitRuleEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author shilei
 */
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitRule {
    LimitRuleEnum rule();

    String resource();

    String limitCount() default "0";

    String time() default "0";

    String rate() default "100";

    String capacity() default "9999999999";

    String message() default "接口请求频繁,请稍后再试";


}
 (2)自定义切面

         com.limit.rule.aspect.LimitRuleAspect 

package com.limit.rule.aspect;


import com.limit.rule.annotation.LimitRule;
import com.limit.rule.core.LimitService;
import com.limit.rule.enums.LimitRuleEnum;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author shilei
 */
@Aspect
@Slf4j
@Data
public class LimitRuleAspect {

    @Autowired
    private Map<String, LimitService> limitServiceMap;

    @Autowired
    private Map<String, DefaultRedisScript<Long>> scriptMap;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Before("@annotation(limitRule)")
    public void limitRule(JoinPoint joinPoint, LimitRule limitRule) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        log.info("methodName:{}触发限流, 限流规则:{}", method.getName(), limitRule.rule().name());
        LimitRuleEnum rule = limitRule.rule();
        LimitService limitService = limitServiceMap.get(rule.getLimitBean());
        DefaultRedisScript<Long> script = scriptMap.get(rule.getScriptName());
        limitService.handle(limitRule, redisTemplate, script);
    }
}
(3) 自定义异常

        com.limit.rule.exception.LimitException

package com.limit.rule.exception;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * @author shilei
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class LimitException extends RuntimeException {
    private String code;

    private String message;

    public LimitException(String message) {
        super();
        this.message =message;

    }
}
 (4)自定义枚举

        com.limit.rule.enums.LimitRuleEnum

package com.limit.rule.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author shilei
 */

@Getter
@AllArgsConstructor
public enum LimitRuleEnum {
    /**
     * 计数器算法
     */
    COUNTER("counterLimitServiceImpl", "counterLimitScript"),

    /**
     * 滑动窗口算法
     */
    ROLLING_WINDOW("rollingWindowLimitServiceImpl", "rollingWindowLimitScript"),

    /**
     * 漏桶算法
     */
    LEAKY_BUCKET("leakyBucketLimitServiceImpl", "leakyBucketLimitScript"),

    /**
     * 令牌桶算法
     */
    TOKEN_BUCKET("tokenBucketLimitServiceImpl", "tokenBucketLimitScript"),
    ;

    private final String limitBean;
    private final String scriptName;
}
3、在resources下定义lua脚本 
(1)固定窗口限流:counterLimit.lua
--获取KEY
local key = KEYS[1]
-- 限流时间/缓存时间
local time = tonumber(ARGV[1])

redis.log(redis.LOG_NOTICE, time)
--当前请求数
local currentCount = redis.call('incr', key)
if (tonumber(currentCount) == 1) then
    redis.call('expire', key,  time)
end
return currentCount


(2)滑动窗口限流:rollingWindowLimit.lua

--获取KEY
local key = KEYS[1];
--获取ARGV内的参数
-- 缓存时间
local expire = tonumber(ARGV[1]);

-- 当前时间
local currentMs = tonumber(ARGV[2]);

-- 最大次数
local count = tonumber(ARGV[3]);

--窗口开始时间
local windowStartMs = currentMs - expire * 1000;

--获取key的次数
local current = redis.call('zcount', key, windowStartMs, currentMs);

--如果key的次数存在且大于预设值直接返回当前key的次数
if current and tonumber(current) > count then
    return tonumber(current);
end

-- 清除上一个窗口的数据
redis.call("ZREMRANGEBYSCORE", key, 0, windowStartMs);

-- 添加当前成员
redis.call("zadd", key, tostring(currentMs), currentMs);
redis.call("expire", key, expire);

--返回key的次数
return tonumber(current)
(3)漏桶限流:leakyBucketLimit.lua
-- 限流器的键名
local key = KEYS[1]
-- 漏桶的容量
local capacity = tonumber(ARGV[1])
-- 漏桶的速率
local rate = tonumber(ARGV[2])
-- 当前时间戳
local current_time = tonumber(ARGV[3])
-- 获取漏桶中的水滴数量
local water = tonumber(redis.call('get', key) or "0")
-- 上次漏水的时间戳
local lastLeakTime = tonumber(redis.call('get', key .. ':last_leak_time') or current_time)
-- 计算当前时间与上次漏水的时间间隔
local elapsed = math.max(0, current_time - lastLeakTime)
-- 根据时间间隔计算漏水数量,并更新漏桶中的水滴数量
water = water - math.floor(rate*(elapsed/1000))
-- 水滴数量不会低于0
if water < 0 then
    water = 0
end
-- 新的请求加入漏桶中
water = water + 1
if water > capacity then
    -- 漏桶已满,拒绝请求
    return 0
else
    -- 接受请求,更新漏桶中的水滴数量
    redis.call('set', key, water)
    -- 更新上次漏水的时间戳
    redis.call('set', key .. ':last_leak_time', current_time)
    return 1
end
(4)令牌桶限流:tokenBucketLimit.lua
--获取key
local bucket_key = KEYS[1]
--每秒生成的令牌数
local rate = tonumber(ARGV[1])
-- 桶容量
local bucket_count = tonumber(ARGV[2])
--当前时间
local current_time = tonumber(ARGV[3])

-- 检查令牌桶的存在性,不存在则创建
if redis.call('exists', bucket_key) == 0 then
    redis.call('hset', bucket_key, 'token_rate', rate)
    redis.call('hset', bucket_key, 'token_count', bucket_count)
    redis.call('hset', bucket_key, 'token_time', current_time)
end

-- 读取当前令牌桶状态
local token_rate = tonumber(redis.call('hget', bucket_key, "token_rate"))
local token_count = tonumber(redis.call('hget', bucket_key, "token_count"))
local token_time = tonumber(redis.call('hget', bucket_key, "token_time"))

-- 更新时间戳
if current_time > token_time then
    local time_delta = current_time - token_time
    local tokens_to_add = math.floor(time_delta * (token_rate / 1000))
    bucket_count = math.min(token_count + tokens_to_add, bucket_count)
    redis.call('hset', bucket_key, 'token_count', bucket_count)
    redis.call('hset', bucket_key, 'token_time', current_time)
end

-- 判断请求令牌是否足够
if bucket_count < 1 then
    -- 不足
    return 0
else
    bucket_count = bucket_count - 1
    redis.call('hset', bucket_key, 'token_count', bucket_count)
    -- 足够
    return 1
end
4、定义处理限流的抽象接口

        com.limit.rule.core.LimitService

package com.limit.rule.core;


import com.limit.rule.annotation.LimitRule;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author shilei
 */
public interface LimitService {

    RedisSerializer redisSerializer = new StringRedisSerializer();


    /**
     * 限流处理
     *
     * @param limitRule     @see LimitRule
     * @param redisTemplate redisTemplate
     * @param script        lua脚本
     */
    void handle(LimitRule limitRule, RedisTemplate<String, Object> redisTemplate , DefaultRedisScript<Long> script);
}
 5、限流策略的实现
(1)固定窗口限流策略

        com.limit.rule.core.CounterLimitServiceImpl

package com.limit.rule.core;


import com.limit.rule.annotation.LimitRule;
import com.limit.rule.exception.LimitException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * @author shilei
 */
@Slf4j
public class CounterLimitServiceImpl implements LimitService {
    @Override
    public void handle(LimitRule limitRule, RedisTemplate<String, Object> redisTemplate, DefaultRedisScript<Long> script) {
        List<String> keys = Collections.singletonList(limitRule.resource());
        Long increment = redisTemplate.execute(script, redisSerializer, redisSerializer, keys, limitRule.time());

        if (Objects.nonNull(increment) && increment.intValue() > Integer.parseInt(limitRule.limitCount())) {
            throw new LimitException(limitRule.message());
        }
    }
}
(2)滑动窗口限流策略

         com.limit.rule.core.RollingWindowLimitServiceImpl

package com.limit.rule.core;

import com.limit.rule.annotation.LimitRule;
import com.limit.rule.exception.LimitException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.Objects;

/**
 * @author shilei
 */
public class RollingWindowLimitServiceImpl implements LimitService{
    @Override
    public void handle(LimitRule limitRule, RedisTemplate<String, Object> redisTemplate, DefaultRedisScript<Long> script) {

        Long count = redisTemplate.execute(script, redisSerializer, redisSerializer, Collections.singletonList(limitRule.resource()),
                limitRule.time(), String.valueOf(System.currentTimeMillis()), limitRule.limitCount());
        if (Objects.nonNull(count) && count.intValue() > Integer.parseInt(limitRule.limitCount())) {
            throw new LimitException(limitRule.message());
        }
    }
}
(3)漏桶限流策略

         com.limit.rule.core.LeakyBucketLimitServiceImpl

package com.limit.rule.core;

import com.limit.rule.annotation.LimitRule;
import com.limit.rule.exception.LimitException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.Objects;


/**
 * @author shilei
 */
public class LeakyBucketLimitServiceImpl implements LimitService{
    @Override
    public void handle(LimitRule limitRule, RedisTemplate<String, Object> redisTemplate, DefaultRedisScript<Long> script) {
        Long count = redisTemplate.execute(script, redisSerializer, redisSerializer, Collections.singletonList(limitRule.resource()),
                limitRule.capacity(), limitRule.rate(), String.valueOf(System.currentTimeMillis()));
        if (Objects.nonNull(count) && count.intValue() == 0) {
            throw new LimitException(limitRule.message());
        }
    }
}
(4)令牌桶限流策略 

         com.limit.rule.core.TokenBucketLimitServiceImpl

package com.limit.rule.core;

import com.limit.rule.annotation.LimitRule;
import com.limit.rule.exception.LimitException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.Objects;

/**
 * @author shilei
 */
public class TokenBucketLimitServiceImpl implements LimitService {
    @Override
    public void handle(LimitRule limitRule, RedisTemplate<String, Object> redisTemplate, DefaultRedisScript<Long> script) {
        Long count = redisTemplate.execute(script, redisSerializer, redisSerializer, Collections.singletonList(limitRule.resource()),
                limitRule.rate(), limitRule.capacity(), String.valueOf(System.currentTimeMillis()));
        if (Objects.nonNull(count) && count.intValue() == 0) {
            throw new LimitException(limitRule.message());
        }
    }
}
 6、配置自动装配的类
(1)redis自动装配配置

        com.limit.rule.config.RedisConfig

package com.limit.rule.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author shilei
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

}
(2) lua脚本自动装配

        com.limit.rule.config.ScriptAutoConfig

package com.limit.rule.config;

import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;

/**
 * @author shilei
 */
@Configuration
@AutoConfigureBefore(RedisConfig.class)
public class ScriptAutoConfig {
    @Bean
    public DefaultRedisScript<Long> rollingWindowLimitScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        ClassPathResource classPathResource = new ClassPathResource("lua/rollingWindowLimit.lua");
        script.setLocation(classPathResource);
        return script;
    }

    @Bean
    public DefaultRedisScript<Long> counterLimitScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        ClassPathResource classPathResource = new ClassPathResource("lua/counterLimit.lua");
        script.setLocation(classPathResource);
        return script;
    }

    @Bean
    public DefaultRedisScript<Long> leakyBucketLimitScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        ClassPathResource classPathResource = new ClassPathResource("lua/leakyBucketLimit.lua");
        script.setLocation(classPathResource);
        return script;
    }

    @Bean
    public DefaultRedisScript<Long> tokenBucketLimitScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        ClassPathResource classPathResource = new ClassPathResource("lua/tokenBucketLimit.lua");
        script.setLocation(classPathResource);
        return script;
    }
}
 (3)限流策略自动装配

        com.limit.rule.config.LimitAutoConfig

package com.limit.rule.config;

import com.limit.rule.aspect.LimitRuleAspect;
import com.limit.rule.core.*;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author shilei
 */
@Configuration
@AutoConfigureBefore(ScriptAutoConfig.class)
public class LimitAutoConfig {

    @Bean
    public LimitService counterLimitServiceImpl() {
        return new CounterLimitServiceImpl();
    }

    @Bean
    public LimitService leakyBucketLimitServiceImpl() {
        return new LeakyBucketLimitServiceImpl();
    }

    @Bean
    public LimitService rollingWindowLimitServiceImpl() {
        return new RollingWindowLimitServiceImpl();
    }

    @Bean
    public LimitService tokenBucketLimitServiceImpl() {
        return new TokenBucketLimitServiceImpl();
    }

    @Bean
    public LimitRuleAspect limitRuleAspect() {
        return new LimitRuleAspect();
    }
}
 (4)META-INF下配置需要自动装配的类

springBoot3.x版本META-INF下新建spring目录,然后在spring目录下新建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件配置如下

com.limit.rule.config.RedisConfig
com.limit.rule.config.ScriptAutoConfig
com.limit.rule.config.LimitAutoConfig

springBoot2.x版本META-INF下新建spring.factories文件配置如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.limit.rule.config.RedisAutoConfig,\
    com.limit.rule.config.ScriptAutoConfig,\
    com.limit.rule.config.LimitAutoConfig

7、编写测试接口

package com.client.utils.controller.limit;


import com.client.utils.vo.Result;
import com.limit.rule.annotation.LimitRule;
import com.limit.rule.enums.LimitRuleEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author shilei
 */
@RestController
@RequestMapping("/limit/rule")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "限流验证", description = "限流验证")
public class LimitRuleController {
    @GetMapping("/counter")
    @Operation(summary = "固定窗口限流测试")
    @LimitRule(resource = "counter", rule = LimitRuleEnum.COUNTER, limitCount = "2", time = "10", message = "错误")
    public Result<Void> counter() {
        return Result.success();
    }


    @GetMapping("/rolling/window")
    @Operation(summary = "滑动窗口限流测试")
    @LimitRule(resource = "rollingWindow", rule = LimitRuleEnum.ROLLING_WINDOW, limitCount = "2", time = "10")
    public Result<Void> rollingWindow() {
        return Result.success();
    }

    @GetMapping("/leaky/bucket")
    @Operation(summary = "漏桶限流测试")
    @LimitRule(resource = "leakyBucket", rule = LimitRuleEnum.LEAKY_BUCKET, capacity = "10", rate = "2")
    public Result<Void> leakyBucket() {
        return Result.success();
    }

    @GetMapping("/token/bucket")
    @Operation(summary = "令牌桶限流测试")
    @LimitRule(resource = "tokenBucket", rule = LimitRuleEnum.TOKEN_BUCKET, capacity = "10", rate = "1")
    public Result<Void> tokenBucket() {
        return Result.success();
    }
}

  • 47
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实现分布式限流可以使用 RedisLua 脚本来完成。以下是可能的实现方案: 1. 使用 Redis 的 SETNX 命令来实现基于令牌桶算法的限流 令牌桶算法是一种常见限流算法,它可以通过令牌的放置和消耗来控制流量。在 Redis 中,我们可以使用 SETNX 命令来实现令牌桶算法。 具体实现步骤如下: - 在 Redis 中创建一个有序集合,用于存储令牌桶的令牌数量和时间戳。 - 每当一个请求到达时,我们首先获取当前令牌桶中的令牌数量和时间戳。 - 如果当前时间戳与最后一次请求的时间戳之差大于等于令牌桶中每个令牌的发放时间间隔,则将当前时间戳更新为最后一次请求的时间戳,并且将令牌桶中的令牌数量增加相应的数量,同时不超过最大容量。 - 如果当前令牌桶中的令牌数量大于等于请求需要的令牌数量,则返回 true 表示通过限流,将令牌桶中的令牌数量减去请求需要的令牌数量。 - 如果令牌桶中的令牌数量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现令牌桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 令牌桶的容量 local capacity = tonumber(ARGV[1]) -- 令牌的发放速率 local rate = tonumber(ARGV[2]) -- 请求需要的令牌数量 local tokens = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取当前令牌桶中的令牌数量和时间戳 local bucket = redis.call('ZREVRANGEBYSCORE', key, now, 0, 'WITHSCORES', 'LIMIT', 0, 1) -- 如果令牌桶为空,则初始化令牌桶 if not bucket[1] then redis.call('ZADD', key, now, capacity - tokens) return 1 end -- 计算当前令牌桶中的令牌数量和时间戳 local last = tonumber(bucket[2]) local tokensInBucket = tonumber(bucket[1]) -- 计算时间间隔和新的令牌数量 local timePassed = now - last local newTokens = math.floor(timePassed * rate) -- 更新令牌桶 if newTokens > 0 then tokensInBucket = math.min(tokensInBucket + newTokens, capacity) redis.call('ZADD', key, now, tokensInBucket) end -- 检查令牌数量是否足够 if tokensInBucket >= tokens then redis.call('ZREM', key, bucket[1]) return 1 else return 0 end ``` 2. 使用 RedisLua 脚本实现基于漏桶算法的限流 漏桶算法是另一种常见限流算法,它可以通过漏桶的容量和漏水速度来控制流量。在 Redis 中,我们可以使用 Lua 脚本实现漏桶算法。 具体实现步骤如下: - 在 Redis 中创建一个键值对,用于存储漏桶的容量和最后一次请求的时间戳。 - 每当一个请求到达时,我们首先获取当前漏桶的容量和最后一次请求的时间戳。 - 计算漏水速度和漏水的数量,将漏桶中的容量减去漏水的数量。 - 如果漏桶中的容量大于等于请求需要的容量,则返回 true 表示通过限流,将漏桶中的容量减去请求需要的容量。 - 如果漏桶中的容量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现漏桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 漏桶的容量 local capacity = tonumber(ARGV[1]) -- 漏水速度 local rate = tonumber(ARGV[2]) -- 请求需要的容量 local size = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取漏桶中的容量和最后一次请求的时间戳 local bucket = redis.call('HMGET', key, 'capacity', 'last') -- 如果漏桶为空,则初始化漏桶 if not bucket[1] then redis.call('HMSET', key, 'capacity', capacity, 'last', now) return 1 end -- 计算漏水的数量和漏桶中的容量 local last = tonumber(bucket[2]) local capacityInBucket = tonumber(bucket[1]) local leak = math.floor((now - last) * rate) -- 更新漏桶 capacityInBucket = math.min(capacity, capacityInBucket + leak) redis.call('HSET', key, 'capacity', capacityInBucket) redis.call('HSET', key, 'last', now) -- 检查容量是否足够 if capacityInBucket >= size then return 1 else return 0 end ``` 以上是使用 RedisLua 脚本实现分布式限流的两种方案,可以根据实际需求选择适合的方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值