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預加載:
script load "return 'aaa'"

evalsha "e20bf1be2a7b6dc909965de4a871a2b34a6d900d" 0

帶引數:
# lua 連接符是 ..
script load "return 'hello '..KEYS[1]"
evalsha "c42984269dae236a5a42bea45409b728a873605e" 1 jiabengshi

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

接下來進入正題:使用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";
}
一秒點擊一次

一秒點擊多次

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/260561.html
標籤:java
上一篇:關于Java字串
