• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

分布式限流(三)之 redis + lua 限流

互联网 diligentman 7天前 6次浏览

redis + lua 脚本实现分布式限流

redis环境搭建,请自行百度。
windows搭建lua环境,参考链接:https://blog.csdn.net/weixin_41725792/article/details/113827606

redis内置了lua解释器,我们在redis中可以使用redis 关键字 eval 运行Lua代码,如下:

# eval 关键字
# 脚本" return { KEYS[1],ARGV[1]} " 
# 2 参数个数
# 参数 K1 K2 ,值 V1 V2
eval " return { KEYS[1],ARGV[1]} " 2 K1 K2 V1 V2

分布式限流(三)之 redis + lua 限流

redis预加载:

script load "return 'aaa'"

分布式限流(三)之 redis + lua 限流

evalsha "e20bf1be2a7b6dc909965de4a871a2b34a6d900d" 0

分布式限流(三)之 redis + lua 限流
带参数:

# lua 连接符是  ..
script load "return 'hello '..KEYS[1]"
evalsha "c42984269dae236a5a42bea45409b728a873605e" 1 jiabengshi

分布式限流(三)之 redis + lua 限流

# 查看脚本是否存在 存在返回1 不存在返回0
script exists "c42984269dae236a5a42bea45409b728a873605e"
# 清空脚本
script flush

分布式限流(三)之 redis + lua 限流
接下来进入正题:使用java操作redis,实现分布式限流。

1. 编写lua限流脚本

-- 声明变量
local methodKey = KEYS[1]
-- 打印在控制台
print("key is "..methodKey)
-- redis控制台打印日志 注意配置文件配置等级 可去配置文件中logfile="" 路劲下查看
redis.log(redis.LOG_DEBUG, "key is ", methodKey)

-- 传入的限流值
print("value is "..ARGV[1])
local limit = tonumber(ARGV[1])
print("convert is "..limit)

-- 获取当前限流个数 可能为nil(key不存在)
local count = tonumber(redis.call("get", methodKey) or "0")
print("count is "..count)

if count + 1 > limit then
    -- 超出限流数  拒绝
    redis.log(redis.LOG_INFO, "limit result is ", false)
    return false
end

-- 自增1
redis.call('INCRBY',methodKey,1)
-- 设置过期时间 一秒
redis.call('EXPIRE',methodKey,1)

return true

2. 编写java程序(插件式)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>

META-INF

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.redis.lua.config.RedisLuaConfiguration
package com.redis.lua.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

@Configuration
@ComponentScan("com.redis.lua")
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
public class RedisLuaConfiguration {
    @Bean
    public DefaultRedisScript defaultRedisScript() {
        DefaultRedisScript<Boolean> booleanDefaultRedisScript = new DefaultRedisScript<>();
        booleanDefaultRedisScript.setLocation(new ClassPathResource("ratelimiter.lua"));
        booleanDefaultRedisScript.setResultType(Boolean.class);
        return booleanDefaultRedisScript;
    }
}
package com.redis.lua.service;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class RedisLuaService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisScript<Boolean> redisScript;

    public Boolean limit(String methodKey, Integer limitCount) {
        log.info("key {} limitCount {}", methodKey, limitCount);
        Boolean result = stringRedisTemplate.execute(redisScript, Lists.newArrayList(methodKey), limitCount.toString());
        if (!result) {
            throw new RuntimeException("more than limit" + limitCount);
        }
        return result;
    }
}
package com.redis.lua.aop;

import com.redis.lua.anno.LuaLimit;
import com.redis.lua.service.RedisLuaService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
@Aspect
@Slf4j
public class RedisLuaAop {

    @Autowired
    private RedisLuaService redisLuaService;

    @Value("${lua.limit:1}")
    private Integer limitCount;

    @Pointcut("@annotation(com.redis.lua.anno.LuaLimit)")
    public void pointCut() {

    }

    @Before("pointCut()")
    public void beforePointCut(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LuaLimit annotation = method.getAnnotation(LuaLimit.class);
        redisLuaService.limit(method.getName(), annotation.limitCount() == 0 ? 0 : limitCount);
    }
}

package com.redis.lua.anno;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface LuaLimit {
    int limitCount() default 1;
}

3. 验证

新建项目

<dependency>
    <groupId>com.huazhi</groupId>
    <artifactId>redis-lua</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
spring:
  application:
    name: redis
  redis:
    host: localhost
    port: 6379
@GetMapping("/testLua")
@LuaLimit(limitCount = 1)
public String testLua() {
    return "success";
}

一秒点击一次
分布式限流(三)之 redis + lua 限流
一秒点击多次
分布式限流(三)之 redis + lua 限流


程序员灯塔
转载请注明原文链接:分布式限流(三)之 redis + lua 限流
喜欢 (0)