Browse Source

guava限流

pull/2/head
马府强 2 years ago
parent
commit
a3a09d2aeb
  1. 33
      guavalimit/.gitignore
  2. 11
      guavalimit/README.md
  3. 61
      guavalimit/pom.xml
  4. 13
      guavalimit/src/main/java/com/example/demo/DemoRequestScopeApplication.java
  5. 73
      guavalimit/src/main/java/com/example/demo/aop/LimitAop.java
  6. 27
      guavalimit/src/main/java/com/example/demo/controller/LimitController.java
  7. 35
      guavalimit/src/main/java/com/example/demo/limit/Limit.java
  8. 54
      guavalimit/src/main/java/com/example/demo/util/WebUtils.java
  9. 68
      guavalimit/src/main/java/com/example/demo/vo/ResultData.java
  10. 60
      guavalimit/src/main/java/com/example/demo/vo/ReturnCode.java
  11. 1
      guavalimit/src/main/resources/application.properties

33
guavalimit/.gitignore

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

11
guavalimit/README.md

@ -0,0 +1,11 @@
# demo-request-scope
《基于ThreadLocal实现一个上下文管理组件》技术博客对应源码
https://juejin.cn/post/7153287656624324638
本文基于`ThreadLocal`原理,实现了一个上下文状态管理组件`Scope`,通过开启一个自定义的`Scope`,在`Scope`范围内,可以通过`Scope`各个方法读写数据;
通过自定义线程池实现上下文状态数据的线程间传递;
提出了一种基于`Filter`和`Scope`的`Request`粒度的上下文管理方案。

61
guavalimit/pom.xml

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-request-scope</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-request-scope</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

13
guavalimit/src/main/java/com/example/demo/DemoRequestScopeApplication.java

@ -0,0 +1,13 @@
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoRequestScopeApplication {
public static void main(String[] args) {
SpringApplication.run(DemoRequestScopeApplication.class, args);
}
}

73
guavalimit/src/main/java/com/example/demo/aop/LimitAop.java

@ -0,0 +1,73 @@
package com.example.demo.aop;
import com.example.demo.limit.Limit;
import com.example.demo.util.WebUtils;
import com.example.demo.vo.ResultData;
import com.example.demo.vo.ReturnCode;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
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 javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;
@Slf4j
@Aspect
@Component
public class LimitAop {
/**
* 不同的接口不同的流量控制
* map的key为 Limiter.key
*/
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
@Around("@annotation(com.example.demo.limit.Limit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//拿limit的注解
Limit limit = method.getAnnotation(Limit.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, 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) {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
ResultData<Object> resultData = ResultData.fail(ReturnCode.LIMIT_ERROR.getCode(), msg);
WebUtils.writeJson(response, resultData);
}
}

27
guavalimit/src/main/java/com/example/demo/controller/LimitController.java

@ -0,0 +1,27 @@
package com.example.demo.controller;
import com.example.demo.limit.Limit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* SpringBoot 如何进行限流 单机可以这么玩
* Guava限流工具类RateLimiter
*/
@Slf4j
@RestController
@RequestMapping("/limit")
public class LimitController {
@GetMapping("/test")
@Limit(key = "limit3", permitsPerSecond = 1, timeout = 50, timeunit = TimeUnit.MILLISECONDS,msg = "系统繁忙,请稍后再试!")
public String limit3() {
log.info("令牌桶limit3获取令牌成功");
return "ok";
}
}

35
guavalimit/src/main/java/com/example/demo/limit/Limit.java

@ -0,0 +1,35 @@
package com.example.demo.limit;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface Limit {
/**
* 资源的key,唯一
* 作用不同的接口不同的流量控制
*/
String key() default "";
/**
* 最多的访问限制次数
*/
double permitsPerSecond();
/**
* 获取令牌最大等待时间
*/
long timeout();
/**
* 获取令牌最大等待时间,单位(:分钟//毫秒) 默认:毫秒
*/
TimeUnit timeunit() default TimeUnit.MILLISECONDS;
/**
* 得不到令牌的提示语
*/
String msg() default "系统繁忙,请稍后再试.";
}

54
guavalimit/src/main/java/com/example/demo/util/WebUtils.java

@ -0,0 +1,54 @@
package com.example.demo.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* <p>
* <code>WebUtils</code>
* </p>
* Description:
* WEB工具类
*
* @author woniu
* @date 2023/4/12 10:56
*/
@Slf4j
public class WebUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
/**
* 向客户端输出JSON字符串
*
* @param response HttpServletResponse
* @param object 输出的数据
*/
@SneakyThrows
public static void writeJson(HttpServletResponse response, Object object) {
writeData(response, objectMapper.writeValueAsString(object), MediaType.APPLICATION_JSON_VALUE);
}
/**
* 客户端返回字符串
*
* @param response HttpServletResponse
* @param data 需要返回的数据
*/
public static void writeData(HttpServletResponse response, String data, String type) {
try {
response.setContentType(type);
response.setCharacterEncoding("utf-8");
response.getWriter().print(data);
response.getWriter().flush();
response.getWriter().close();
} catch (IOException e) {
log.error("writeData error", e);
}
}
}

68
guavalimit/src/main/java/com/example/demo/vo/ResultData.java

@ -0,0 +1,68 @@
package com.example.demo.vo;
import lombok.Data;
@Data
public class ResultData<T> {
/**
* 结果状态 ,具体状态码参见ResultData.java
*/
private int status;
/**
* 响应消息
**/
private String message;
/**
* 响应数据
**/
private T data;
/**
* 接口请求时间
**/
private long timestamp;
public ResultData() {
this.timestamp = System.currentTimeMillis();
}
public static <T> ResultData<T> success(String message) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(ReturnCode.RC100.getCode());
resultData.setMessage(message);
return resultData;
}
public static <T> ResultData<T> success() {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(ReturnCode.RC100.getCode());
resultData.setMessage(ReturnCode.RC100.getMessage());
return resultData;
}
public static <T> ResultData<T> success(T data) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(ReturnCode.RC100.getCode());
resultData.setMessage(ReturnCode.RC100.getMessage());
resultData.setData(data);
return resultData;
}
public static <T> ResultData<T> fail(String message) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(ReturnCode.RC999.getCode());
resultData.setMessage(message);
return resultData;
}
public static <T> ResultData<T> fail(int code, String message) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(code);
resultData.setMessage(message);
return resultData;
}
}

60
guavalimit/src/main/java/com/example/demo/vo/ReturnCode.java

@ -0,0 +1,60 @@
package com.example.demo.vo;
/**
* 状态码集合
*
* @author woniu
* @date 2023/4/12 14:33
*/
public enum ReturnCode {
/**
* 操作成功
**/
RC100(100, "操作成功"),
/**
* 操作失败
**/
RC999(999, "操作失败"),
/**
* 服务限流
**/
LIMIT_ERROR(2001, "系统繁忙,请稍后再试!"),
/**
* 服务异常
**/
RC500(500, "系统异常,请稍后重试"),
ILLEGAL_ARGUMENT(3001, "非法参数"),
ILLEGAL_HEADER(4001, "非法请求头,请添加合适的签名"),
REPLAY_ERROR(4002, "访问已过期,请重新访问!"),
ARGUMENT_ERROR(4003, "您正在尝试恶意访问,已记录IP备案!");
/**
* 自定义状态码
**/
private final int code;
/**
* 自定义描述
**/
private final String message;
ReturnCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}

1
guavalimit/src/main/resources/application.properties

@ -0,0 +1 @@
Loading…
Cancel
Save