redis+lua實現(xiàn)限流的項目實踐
更新時間:2023年10月30日 15:29:28 作者:Best_Liu~
redis有很多限流的算法(比如:令牌桶,計數(shù)器,時間窗口)等,在分布式里面進行限流的話,我們則可以使用redis+lua腳本進行限流,下面就來介紹一下redis+lua實現(xiàn)限流
1、需要引入Redis的maven坐標
<!--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ù)器連接密碼(默認為空) password: # 連接池最大連接數(shù)(使用負值表示沒有限制) jedis: pool: max-active: 8 # 連接池最大阻塞等待時間(使用負值表示沒有限制) max-wait: -1 # 連接池中的最大空閑連接 max-idle: 8 # 連接池中的最小空閑連接 min-idle: 0 # 連接超時時間(毫秒) timeout: 10000
3、新建腳本放在該項目的 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; //獲取令牌的超時時間 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] //取當前時間戳到單位秒 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("限流時間段內(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實現(xiàn)限流的項目實踐的文章就介紹到這了,更多相關(guān)redis lua限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis實現(xiàn)主從復(fù)制方式(Master&Slave)
這篇文章主要介紹了Redis實現(xiàn)主從復(fù)制方式(Master&Slave),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06spring?boot集成redis基礎(chǔ)入門實例詳解
redis在spring?boot項目開發(fā)中是常用的緩存套件,常見使用的是spring-boot-starter-data-redis,這篇文章主要介紹了spring?boot集成redis基礎(chǔ)入門,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-10-10Redis簡單動態(tài)字符串SDS的實現(xiàn)示例
Redis沒有直接復(fù)用C語言的字符串,而是新建了SDS,本文主要介紹了Redis簡單動態(tài)字符串SDS的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2023-08-08Redis中Bloom filter布隆過濾器的學(xué)習
布隆過濾器是一個非常長的二進制向量和一系列隨機哈希函數(shù)的組合,可用于檢索一個元素是否存在,本文就詳細的介紹一下Bloom filter布隆過濾器,具有一定的參考價值,感興趣的可以了解一下2022-12-12