上一篇文章《微服务实战spring cloud alibaba(二十)利用guava框架实现单个接口的令牌桶限流》我们介绍了使用guava框架实现单个接口的令牌桶限流。但是大家想象一下,真实的生产业务,有直接在controller里面编写令牌筒的判断逻辑的吗?
所以说这肯定是行不通的,那真实环境是怎么做的呢? 其实这里肯定是使用注解的啦,spring里面的aop利用起来非常的方便。下面我们来实战一下:
一、创建一个maven项目,引入如下的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<!-- log start -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- OKHttp3依赖 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>二、定义一个注解RateLimitAnnotation
使用注解实现限流,那么首先我们需要自定义一个注解,完整代码示例如下:
package com.demo.api.aop;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 自定义限流的注解
* @author Administrator
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Documented
public @interface RateLimitAnnotation {
/**
* 资源的key,唯一 作用:不同的接口,不同的流量控制
*/
String key() default "";
/**
* 最多的访问限制次数
*/
double permitsPerSecond();
/**
* 获取令牌最大等待时间
*/
long timeout();
/**
* 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒
*/
TimeUnit timeunit() default TimeUnit.MILLISECONDS;
/**
* 得不到令牌的提示语
*/
String msg() default "系统繁忙,请稍后再试.";
}三、实现自定义注解RateLimitAspect
自定义好了注解之后,我们需要实现这个注解里面的逻辑,完整示例代码如下
package com.demo.api.aop;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSON;
import com.demo.api.model.BaseResponse;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
/**
* 实现自定义限流注解的代码逻辑
*
* @author Administrator
*
*/
@Slf4j
@Aspect
@Component
public class RateLimitAspect {
/**
* 不同的接口,不同的流量控制 map的key为 Limiter.key
*/
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
@Around("@annotation(com.demo.api.aop.RateLimitAnnotation)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 拿limit的注解
RateLimitAnnotation limit = method.getAnnotation(RateLimitAnnotation.class);
if (limit != null) {
// key作用:不同的接口,不同的流量控制
String key = limit.key();
RateLimiter rateLimiter = null;
// 验证缓存是否有命中key
if (!limitMap.containsKey(key)) {
// 创建令牌桶
rateLimiter = RateLimiter.create(limit.permitsPerSecond());
limitMap.put(key, rateLimiter);
log.info("当前key:{} 的令牌桶找不到,所以在这里新创建一个令牌筒:{} 每秒钟的容量是:{}",
new Object[] { key, key, limit.permitsPerSecond() });
}
rateLimiter = limitMap.get(key);
// 拿令牌
boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit());
// 拿不到命令,直接返回异常提示
if (!acquire) {
log.debug("当前令牌桶:{},获取令牌失败,准备返回错误信息", key);
this.responseFail(limit.msg());
return null;
}
}
return joinPoint.proceed();
}
/**
* 直接向前端抛出异常
*
* @param msg 提示信息
*/
private void responseFail(String msg) {
try {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getResponse();
// 使用setCharacterEncoding方法设置输出内容使用UTF-8进行编码
response.setCharacterEncoding("UTF-8");
BaseResponse rs = BaseResponse.fail(msg);
String jsonString = JSON.toJSONString(rs);
log.info("注解信息是:{}",jsonString);
response.getWriter().write(jsonString);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}四、编写一个接口,添加刚才自定义的注解
@GetMapping("/testLimiter1")
@RateLimitAnnotation(key = "testLimiter1", permitsPerSecond = 2, timeout = 500, timeunit = TimeUnit.MILLISECONDS, msg = "当前排队人数较多,请稍后再试!")
public BaseResponse testLimiter1() {
return BaseResponse.success("请求成功,获取令牌成功,时间:" + LocalDateTime.now().format(dtf));
}这里我们定义的是每秒钟放2个令牌。
五、测试
这里我们还是使用上一篇文章的okhttp测试类,修改下url即可
运行后我们看下效果:
可以发现在同一秒里面,只有2个请求拿到了令牌筒,实现了接口的限流。
以上就是利用spring aop实现使用注解的方式为单个接口进行限流的案例,最后附上本案例的源码,登录后即可下载。












还没有评论,来说两句吧...