redis+lua實(shí)現(xiàn)限流的項(xiàng)目實(shí)踐
更新時(shí)間:2023年10月30日 15:29:28 作者:Best_Liu~
redis有很多限流的算法(比如:令牌桶,計(jì)數(shù)器,時(shí)間窗口)等,在分布式里面進(jìn)行限流的話,我們則可以使用redis+lua腳本進(jìn)行限流,下面就來介紹一下redis+lua實(shí)現(xiàn)限流
1、需要引入Redis的maven坐標(biāo)
<!--redis和 springboot集成的包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>2、redis配置
spring:
# Redis數(shù)據(jù)庫索引
redis:
database: 0
# Redis服務(wù)器地址
host: 127.0.0.1
# Redis服務(wù)器連接端口
port: 6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
password:
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
jedis:
pool:
max-active: 8
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
max-wait: -1
# 連接池中的最大空閑連接
max-idle: 8
# 連接池中的最小空閑連接
min-idle: 0
# 連接超時(shí)時(shí)間(毫秒)
timeout: 100003、新建腳本放在該項(xiàng)目的 resources 目錄下,新建 limit.lua
local key = KEYS[1] --限流KEY
local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call('get', key) or "0") if current + 1 > limit then
return 0 else redis.call("INCRBY", key,"1") redis.call("expire", key,"2") return current + 1 end
4、自定義限流注解
import java.lang.annotation.*;
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisRateLimiter {
//往令牌桶放入令牌的速率
double value() default Double.MAX_VALUE;
//獲取令牌的超時(shí)時(shí)間
double limit() default Double.MAX_VALUE;
}5、自定義切面類 RedisLimiterAspect 類 ,修改掃描自己controller類
import com.imooc.annotation.RedisRateLimiter;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.assertj.core.util.Lists;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.List;
@Aspect
@Component
public class RedisLimiterAspect {
@Autowired
private HttpServletResponse response;
/**
* 注入redis操作類
*/
@Autowired
private StringRedisTemplate stringRedisTemplate;
private DefaultRedisScript<List> redisScript;
/**
* 初始化 redisScript 類
* 返回值為 List
*/
@PostConstruct
public void init(){
redisScript = new DefaultRedisScript<List>();
redisScript.setResultType(List.class);
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limit.lua")));
}
public final static Logger log = LoggerFactory.getLogger(RedisLimiterAspect.class);
@Pointcut("execution( public * com.zz.controller.*.*(..))")
public void pointcut(){
}
@Around("pointcut()")
public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature)proceedingJoinPoint.getSignature();
//使用Java 反射技術(shù)獲取方法上是否有@RedisRateLimiter 注解類
RedisRateLimiter redisRateLimiter = signature.getMethod().getDeclaredAnnotation(RedisRateLimiter.class);
if(redisRateLimiter == null){
//正常執(zhí)行方法,執(zhí)行正常業(yè)務(wù)邏輯
return proceedingJoinPoint.proceed();
}
//獲取注解上的參數(shù),獲取配置的速率
double value = redisRateLimiter.value();
double time = redisRateLimiter.limit();
//list設(shè)置lua的keys[1]
//取當(dāng)前時(shí)間戳到單位秒
String key = "ip:"+ System.currentTimeMillis() / 1000;
List<String> keyList = Lists.newArrayList(key);
//用戶Mpa設(shè)置Lua 的ARGV[1]
//List<String> argList = Lists.newArrayList(String.valueOf(value));
//調(diào)用腳本并執(zhí)行
List result = stringRedisTemplate.execute(redisScript, keyList, String.valueOf(value),String.valueOf(time));
log.info("限流時(shí)間段內(nèi)訪問第:{} 次", result.toString());
//lua 腳本返回 "0" 表示超出流量大小,返回1表示沒有超出流量大小
if(StringUtils.equals(result.get(0).toString(),"0")){
//服務(wù)降級
fullback();
return null;
}
// 沒有限流,直接放行
return proceedingJoinPoint.proceed();
}
/**
* 服務(wù)降級方法
*/
private void fullback(){
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = null;
try {
writer= response.getWriter();
JSONObject o = new JSONObject();
o.put("status",500);
o.put("msg","Redis限流:請求太頻繁,請稍后重試!");
o.put("data",null);
writer.printf(o.toString()
);
}catch (Exception e){
e.printStackTrace();
}finally {
if(writer != null){
writer.close();
}
}
}
}6、在需要限流的類添加注解
import com.imooc.annotation.RedisRateLimiter;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@Api(value = "限流", tags = {"限流測試接口"})
@RequestMapping("limiter")
public class LimiterController {
@ApiOperation(value = "Redis限流注解測試接口",notes = "Redis限流注解測試接口", httpMethod = "GET")
@RedisRateLimiter(value = 10, limit = 1)
@GetMapping("/redislimit")
public IMOOCJSONResult redislimit(){
System.out.println("Redis限流注解測試接口");
return IMOOCJSONResult.ok();
}
}到此這篇關(guān)于redis+lua實(shí)現(xiàn)限流的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)redis lua限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Redis數(shù)據(jù)結(jié)構(gòu)之跳躍表
這篇文章主要介紹了Redis數(shù)據(jù)結(jié)構(gòu)中的跳躍表的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
Redis概述及l(fā)inux安裝redis的詳細(xì)教程
這篇文章主要介紹了Redis概述及l(fā)inux安裝redis的詳細(xì)教程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
Redis過期Key刪除策略和內(nèi)存淘汰策略的實(shí)現(xiàn)
當(dāng)內(nèi)存使用達(dá)到上限,就無法存儲更多數(shù)據(jù)了,為了解決這個(gè)問題,Redis內(nèi)部會(huì)有兩套內(nèi)存回收的策略,過期Key刪除策略和內(nèi)存淘汰策略,本文就來詳細(xì)的介紹一下這兩種方法,感興趣的可以了解一下2024-02-02
redis存儲空間復(fù)雜度和時(shí)間復(fù)雜度的平衡
本文主要介紹了在獎(jiǎng)品概率計(jì)算中,如何平衡內(nèi)存占用和時(shí)間復(fù)雜度,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-02-02

