创建限流组件项目
pom.xml文件中引入相关依赖
<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目录下创建lua脚本 ratelimiter.lua
-- -- Created by IntelliJ IDEA. -- User: 寒夜 -- -- 获取方法签名特征 local methodKey = KEYS[1] redis.log(redis.LOG_DEBUG, 'key is', methodKey) -- 调用脚本传入的限流大小 local limit = tonumber(ARGV[1]) -- 获取当前流量大小 local count = tonumber(redis.call('get', methodKey) or "0") -- 是否超出限流阈值 if count + 1 > limit then -- 拒绝服务访问 return false else -- 没有超过阈值 -- 设置当前访问的数量+1 redis.call("INCRBY", methodKey, 1) -- 设置过期时间 redis.call("EXPIRE", methodKey, 1) -- 放行 return true end
创建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 RedisTemplate<String, 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; } }
创建一个自定义注解
package com.hy.annotation; import java.lang.annotation.*; /** * @author 寒夜 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AccessLimiter { int limit(); String methodKey() default ""; }
创建一个切入点
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 RedisScript<Boolean> rateLimitLua; public AccessLimiterAspect(StringRedisTemplate stringRedisTemplate, RedisScript<Boolean> 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(); // 如果没设置methodkey, 从调用方法签名生成自动一个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. 调用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"); } } }
创建测试项目
pom.xml中引入组件
application.yml配置
spring: redis: host: 192.168.0.218 port: 6379 password: 123456 database: 0 application: name: ratelimiter-test server: port: 10087
创建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"; } }
开始测试,快速点击结果如下
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件!
如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
白云城资源网 Copyright www.dyhadc.com
暂无“基于Redis+Lua脚本实现分布式限流组件封装的方法”评论...
稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!
昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。
这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。
而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?
更新日志
2024年11月05日
2024年11月05日
- 雨林唱片《赏》新曲+精选集SACD版[ISO][2.3G]
- 罗大佑与OK男女合唱团.1995-再会吧!素兰【音乐工厂】【WAV+CUE】
- 草蜢.1993-宝贝对不起(国)【宝丽金】【WAV+CUE】
- 杨培安.2009-抒·情(EP)【擎天娱乐】【WAV+CUE】
- 周慧敏《EndlessDream》[WAV+CUE]
- 彭芳《纯色角3》2007[WAV+CUE]
- 江志丰2008-今生为你[豪记][WAV+CUE]
- 罗大佑1994《恋曲2000》音乐工厂[WAV+CUE][1G]
- 群星《一首歌一个故事》赵英俊某些作品重唱企划[FLAC分轨][1G]
- 群星《网易云英文歌曲播放量TOP100》[MP3][1G]
- 方大同.2024-梦想家TheDreamer【赋音乐】【FLAC分轨】
- 李慧珍.2007-爱死了【华谊兄弟】【WAV+CUE】
- 王大文.2019-国际太空站【环球】【FLAC分轨】
- 群星《2022超好听的十倍音质网络歌曲(163)》U盘音乐[WAV分轨][1.1G]
- 童丽《啼笑姻缘》头版限量编号24K金碟[低速原抓WAV+CUE][1.1G]