🙏废话不多说系列,直接开干🙏
一、Guava RateLimiter 限流简介
Guava 提供的 RateLimiter 可以限制 物理 或逻辑资源的被访问速率。(有点类似 Samephore,但是有不同,RateLimiter 控制的是速率,Samphore 控制的是 并发量。)
RateLimiter 的原理类似于 令牌桶,它主要由 许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑的分发,若请求超过 permitsPerSecond 则 RateLimiter 按照每秒 1/permitsPerSecond 的速率释放许可。
二、使用 RateLimiter
(1)引入 maven 依赖
<!-- 引入 google Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
(2)简单测试示例
import com.google.common.util.concurrent.RateLimiter;
/**
* guava 限流组件
*/
public class RateLimiterTest {
public static void main(String[] args) {
long startTimeStamp = System.currentTimeMillis();
// 这里的1 表示每秒允许处理的量为1个
RateLimiter limiter = RateLimiter.create(1.0);
for (int i = 0; i < 10; i++) {
// 请求 RateLimiter ,超过 permits 会被阻塞
limiter.acquire();
System.out.println("call permits ..." + i);
}
long endTimestamp = System.currentTimeMillis();
System.out.println(endTimestamp - startTimeStamp);
}
}
(3)实际项目使用演示
① 新建 ServiceImpl 提供API限流
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Service;
@Service
public class GuavaRateLimiterService {
/**
* 每秒控制 5 个许可
*/
RateLimiter rateLimiter = RateLimiter.create(5.0);
/**
* 获取令牌
*
* @return 是否成功获取
*/
public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
}
② controller 层
import edu.study.module.up.service.GuavaRateLimiterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* guava ratelimiter API限流
*/
@RestController
@RequestMapping(path = {"/guava"})
public class GuavaRateLimiterController {
@Autowired
private GuavaRateLimiterService rateLimiterService;
Logger logger = LoggerFactory.getLogger(GuavaRateLimiterController.class);
@ResponseBody
@RequestMapping("/ratelimiter")
public Result testRateLimiter() {
if (rateLimiterService.tryAcquire()) {
logger.info("成功获取许可");
return new Result().success(1001, "成功获取许可");
}
logger.info("未获取许可");
return new Result().success(1002, "未获取许可");
}
}
class Result {
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Result() {
}
private Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Result success(Integer code, String msg) {
return new Result(code, msg);
}
}
(4)使用切面和注解改造
使用上述方式使用RateLimiter的方式不够优雅,尽管我们可以把RateLimiter的逻辑包在service里面,controller直接调用即可,但是如果我们换成:自定义注解+切面 的方式实现的话,会优雅的多,
① 自定义注解类
package edu.study.module.up.annotation;
import java.lang.annotation.*;
// 自定义注解可以不包含任何属性,成为一个表示注解
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAspect {
}
② 自定义切面类
package edu.study.module.up.annotation;
import com.alibaba.fastjson.JSONObject; // 引入 fastjson 依赖包即可
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.RateLimiter;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 切面:对@RateLimitAspect拦截点进行限流处理
*/
@Component
@Scope
@Aspect
public class RateLimitAop {
@Autowired
private HttpServletResponse response;
/**
* 比如说,我这里设置“并发数”为 5
*/
private RateLimiter rateLimiter = RateLimiter.create(5.0);
@Pointcut("@annotation(edu.study.module.up.annotation.RateLimitAspect)")
public void serviceLimit() {
}
@Around("serviceLimit()")
public Object around(ProceedingJoinPoint joinPoint) {
Boolean flag = rateLimiter.tryAcquire();
Object obj = null;
try {
if (flag) {
obj = joinPoint.proceed();
} else {
String result = JSONObject.toJSONString(ImmutableMap.of(100, "failure"));
output(response, result);
}
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("flag=" + flag + ",obj=" + obj);
return obj;
}
private void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json; charset=UTF-8");
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.write(msg.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
}
}
}
③ 测试 Controller 类
package edu.study.module.up.controller;
import edu.study.module.up.annotation.RateLimitAspect;
import edu.study.module.up.service.GuavaRateLimiterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* guava ratelimiter API限流
*/
@RestController
@RequestMapping(path = {"/guava"})
public class GuavaRateLimiterController {
@Autowired
private GuavaRateLimiterService rateLimiterService;
Logger logger = LoggerFactory.getLogger(GuavaRateLimiterController.class);
@ResponseBody
@RequestMapping("/ratelimiter")
public Result testRateLimiter() {
if (rateLimiterService.tryAcquire()) {
logger.info("成功获取许可");
return new Result().success(1001, "成功获取许可");
}
logger.info("未获取许可");
return new Result().success(1002, "未获取许可");
}
@RateLimitAspect // 如果需要对某个接口进行限流,则使用此注解标记即可
@ResponseBody
@RequestMapping("/test/ratelimiter")
public String test() {
return new Result().success(1001, "success").toString();
}
}
class Result {
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Result() {
}
private Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Result success(Integer code, String msg) {
return new Result(code, msg);
}
}
④ 测试结果演示
动态测试演示图:
OVER
🙏至此,非常感谢阅读🙏