創(chuàng)建限流組件項目
pom.xml文件中引入相關(guān)依賴
dependencies> 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>18.0/version> /dependency> /dependencies>
在resources目錄下創(chuàng)建lua腳本 ratelimiter.lua
-- -- Created by IntelliJ IDEA. -- User: 寒夜 -- -- 獲取方法簽名特征 local methodKey = KEYS[1] redis.log(redis.LOG_DEBUG, 'key is', methodKey) -- 調(diào)用腳本傳入的限流大小 local limit = tonumber(ARGV[1]) -- 獲取當(dāng)前流量大小 local count = tonumber(redis.call('get', methodKey) or "0") -- 是否超出限流閾值 if count + 1 > limit then -- 拒絕服務(wù)訪問 return false else -- 沒有超過閾值 -- 設(shè)置當(dāng)前訪問的數(shù)量+1 redis.call("INCRBY", methodKey, 1) -- 設(shè)置過期時間 redis.call("EXPIRE", methodKey, 1) -- 放行 return true end
創(chuàng)建RedisConfiguration 類
package com.imooc.springcloud; import org.springframework.context.annotation.Bean; 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.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; /** * @author 寒夜 */ @Configuration public class RedisConfiguration { @Bean public RedisTemplateString, String> redisTemplate( RedisConnectionFactory factory) { return new StringRedisTemplate(factory); } @Bean public DefaultRedisScript loadRedisScript() { DefaultRedisScript redisScript = new DefaultRedisScript(); redisScript.setLocation(new ClassPathResource("ratelimiter.lua")); redisScript.setResultType(java.lang.Boolean.class); return redisScript; } }
創(chuàng)建一個自定義注解
package com.hy.annotation; import java.lang.annotation.*; /** * @author 寒夜 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AccessLimiter { int limit(); String methodKey() default ""; }
創(chuàng)建一個切入點
package com.hy.annotation; import com.google.common.collect.Lists; 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.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.lang.reflect.Method; import java.util.Arrays; import java.util.stream.Collectors; /** * @author 寒夜 */ @Slf4j @Aspect @Component public class AccessLimiterAspect { private final StringRedisTemplate stringRedisTemplate; private final RedisScriptBoolean> rateLimitLua; public AccessLimiterAspect(StringRedisTemplate stringRedisTemplate, RedisScriptBoolean> rateLimitLua) { this.stringRedisTemplate = stringRedisTemplate; this.rateLimitLua = rateLimitLua; } @Pointcut(value = "@annotation(com.hy.annotation.AccessLimiter)") public void cut() { log.info("cut"); } @Before("cut()") public void before(JoinPoint joinPoint) { // 1. 獲得方法簽名,作為method Key MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); AccessLimiter annotation = method.getAnnotation(AccessLimiter.class); if (annotation == null) { return; } String key = annotation.methodKey(); int limit = annotation.limit(); // 如果沒設(shè)置methodkey, 從調(diào)用方法簽名生成自動一個key if (StringUtils.isEmpty(key)) { Class[] type = method.getParameterTypes(); key = method.getClass() + method.getName(); if (type != null) { String paramTypes = Arrays.stream(type) .map(Class::getName) .collect(Collectors.joining(",")); log.info("param types: " + paramTypes); key += "#" + paramTypes; } } // 2. 調(diào)用Redis boolean acquired = stringRedisTemplate.execute( rateLimitLua, // Lua script的真身 Lists.newArrayList(key), // Lua腳本中的Key列表 Integer.toString(limit) // Lua腳本Value列表 ); if (!acquired) { log.error("your access is blocked, key={}", key); throw new RuntimeException("Your access is blocked"); } } }
創(chuàng)建測試項目
pom.xml中引入組件
application.yml配置
spring: redis: host: 192.168.0.218 port: 6379 password: 123456 database: 0 application: name: ratelimiter-test server: port: 10087
創(chuàng)建controller
package com.hy; import com.hy.annotation.AccessLimiter; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author 寒夜 */ @RestController @Slf4j public class Controller { private final com.hy.AccessLimiter accessLimiter; public Controller(com.hy.AccessLimiter accessLimiter) { this.accessLimiter = accessLimiter; } @GetMapping("test") public String test() { accessLimiter.limitAccess("ratelimiter-test", 3); return "success"; } // 提醒! 注意配置掃包路徑(com.hy路徑不同) @GetMapping("test-annotation") @AccessLimiter(limit = 1) public String testAnnotation() { return "success"; } }
開始測試,快速點擊結(jié)果如下
到此這篇關(guān)于基于Redis+Lua腳本實現(xiàn)分布式限流組件封裝的方法的文章就介紹到這了,更多相關(guān)Redis+Lua腳本實現(xiàn)分布式限流組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
標(biāo)簽:畢節(jié) 河源 南寧 伊春 甘南 定州 拉薩 泰州
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《基于Redis+Lua腳本實現(xiàn)分布式限流組件封裝的方法》,本文關(guān)鍵詞 基于,Redis+Lua,腳本,實現(xiàn),;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。