当前位置: 移动技术网 > IT编程>数据库>Redis > 基于Redis+Lua脚本实现分布式限流组件封装的方法

基于Redis+Lua脚本实现分布式限流组件封装的方法

2020年10月31日  | 移动技术网IT编程  | 我要评论
创建限流组件项目pom.xml文件中引入相关依赖 <dependencies> <dependency> <groupid>org.springframework

创建限流组件项目

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";
 }
 
}

开始测试,快速点击结果如下

到此这篇关于基于redis+lua脚本实现分布式限流组件封装的方法的文章就介绍到这了,更多相关redis+lua脚本实现分布式限流组件内容请搜索移动技术网以前的文章或继续浏览下面的相关文章希望大家以后多多支持移动技术网!

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网