1.令牌桶算法:令牌桶算法是一种常见的限流算法。它基于一个令牌桶,该桶以固定速率生成令牌。每当接口被调用时,需要先获取一个令牌才能执行操作。如果令牌桶中没有足够的令牌,则拒绝进一步的访问。
下面是一个使用Guava库中的RateLimiter实现令牌桶算法的示例代码:
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
private RateLimiter rateLimiter = RateLimiter.create(10.0); // 设置每秒生成的令牌数为10
@GetMapping("/my-api")
public String myApi() {
if (rateLimiter.tryAcquire()) {
// 执行接口逻辑
return "正常响应";
} else {
// 拒绝访问
return "访问频率超过限制";
}
}
}
在上述示例中,我们在MyController中创建了一个RateLimiter实例,并设置每秒生成的令牌数为10。在myApi()方法中,我们使用tryAcquire()方法尝试获取一个令牌。如果成功获取到令牌,则可以执行接口逻辑;如果获取失败,则说明访问频率超过了限制,拒绝进一步的访问。
你可以根据具体需求调整令牌桶的速率和其他参数。此外,请注意令牌桶算法只能限制整体的访问频率,无法对单个用户进行精确的限制。如果需要对每个用户进行个性化的限制,你可能需要结合其他方法,如计数器或缓存
2.计数器算法
当使用计数器方式限制接口访问频率时,你可以通过以下代码示例来实现
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@RestController
public class MyController {
private ConcurrentHashMap<String, Long> lastAccessMap = new ConcurrentHashMap<>();
private int maxRequests = 10; // 设置最大访问次数限制
private long timeWindow = 1; // 设置时间窗口为1分钟
@GetMapping("/my-api")
public String myApi() {
String ipAddress = getClientIpAddress(); // 获取客户端IP地址
// 获取上次访问时间戳
long lastAccessTime = lastAccessMap.getOrDefault(ipAddress, 0L);
// 获取当前时间戳
long currentTime = System.currentTimeMillis();
// 计算时间间隔(分钟)
long timeInterval = TimeUnit.MILLISECONDS.toMinutes(currentTime - lastAccessTime);
// 检查时间间隔是否超过时间窗口
if (timeInterval > timeWindow) {
// 重置计数器和最后访问时间
lastAccessMap.put(ipAddress, currentTime);
lastAccessMap.put(ipAddress + "_count", 1L);
} else {
// 获取当前计数器值,并递增
long currentCount = lastAccessMap.getOrDefault(ipAddress + "_count", 0L);
lastAccessMap.put(ipAddress + "_count", currentCount + 1);
// 检查计数器是否超过最大限制
if (currentCount >= maxRequests) {
return "访问频率超过限制";
}
}
// 处理正常的接口逻辑
return "正常响应";
}
private String getClientIpAddress() {
// 获取客户端IP地址的方法实现,可以根据具体的需求自行实现
// 例如,可以通过HttpServletRequest对象获取IP地址
// HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// return request.getRemoteAddr();
return "127.0.0.1"; // 这里仅作示例,假设IP地址为本地地址
}
}
在上述示例中,MyController是一个Spring Boot的控制器类,包含了一个名为myApi()的接口方法。在该方法中,我们使用ConcurrentHashMap来存储每个客户端的最后访问时间和计数器。首先,从请求中获取客户端IP地址(getClientIpAddress()方法需要根据实际情况实现)。然后,我们获取上次访问时间戳和当前时间戳,并计算时间间隔(分钟)。如果时间间隔超过了设定的时间窗口,我们将重置计数器和最后访问时间。否则,我们获取当前计数器值并递增,然后检查是否超过了设定的最大限制。如果超过,则返回相应的错误消息,否则继续处理正常的接口逻辑。
3.通过注解来实现访问频率控制
3.1使用自定义注解结合拦截器来限制接口的访问频率。以下是一个示例代码:
首先,创建一个自定义注解@RateLimit:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int value() default 10; // 默认最大访问次数
long duration() default 60; // 默认时间窗口(秒)
}
然后,创建一个拦截器RateLimitInterceptor来处理频率限制逻辑:
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class RateLimitInterceptor implements HandlerInterceptor {
private ConcurrentHashMap<String, Long> requestCountMap = new ConcurrentHashMap<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RateLimit rateLimitAnnotation = handlerMethod.getMethod().getAnnotation(RateLimit.class);
if (rateLimitAnnotation != null) {
int maxRequests = rateLimitAnnotation.value();
long duration = rateLimitAnnotation.duration();
String ipAddress = getClientIpAddress(request);
String key = ipAddress + ":" + request.getRequestURI();
// 获取上次访问时间戳
long lastAccessTime = requestCountMap.getOrDefault(key, 0L);
// 获取当前时间戳
long currentTime = System.currentTimeMillis();
// 计算时间间隔(秒)
long timeInterval = TimeUnit.MILLISECONDS.toSeconds(currentTime - lastAccessTime);
// 检查时间间隔是否超过限制
if (timeInterval < duration) {
// 检查访问次数是否超过限制
if (requestCountMap.getOrDefault(key + "_count", 0L) >= maxRequests) {
response.getWriter().write("访问频率超过限制");
response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
return false;
}
} else {
// 重置访问次数和最后访问时间
requestCountMap.put(key, currentTime);
requestCountMap.put(key + "_count", 1L);
}
// 递增访问次数
requestCountMap.put(key + "_count", requestCountMap.getOrDefault(key + "_count", 0L) + 1);
}
}
return true;
}
private String getClientIpAddress(HttpServletRequest request) {
// 获取客户端IP地址的方法实现,可以根据具体的需求自行实现
// 例如,可以通过HttpServletRequest对象获取IP地址
// return request.getRemoteAddr();
return "127.0.0.1"; // 这里仅作示例,假设IP地址为本地地址
}
}
最后,在需要限制频率的接口上加上@RateLimit注解:
@RestController
public class MyController {
@RateLimit(value = 10, duration = 60) // 设置最大访问次数为10,时间窗口为60秒
@GetMapping("/my-api")
public String myApi() {
// 处理正常的接口逻辑
return "正常响应";
}
}
在上述示例中,我们创建了一个自定义注解@RateLimit,用于标记需要限制频率的接口。然后,我们在拦截器RateLimitInterceptor中,通过解析注解获取最大访问次数和时间窗口,并根据客户端IP地址和请求URI构建唯一的键。在拦截器的preHandle方法中,我们检查上次访问时间和当前时间的时间间隔,并根据限制条件判断是否允许继续访问。如果超过了限制,则返回相应的错误消息
3.2使用@Aspect注解和AOP(面向切面编程)来实现访问频率的限制。
以下是一个示例代码:
首先,创建一个自定义注解@RateLimit:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int limit() default 1; // 默认最大访问次数
long duration() default 60; // 默认时间窗口(秒)
}
然后,创建一个切面RateLimitAspect来处理频率限制逻辑:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class RateLimitAspect {
private Map<String, Long> requestCountMap = new HashMap<>();
@Around("@annotation(rateLimit)")
public Object enforceRateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
int limit = rateLimit.limit();
long duration = rateLimit.duration();
String key = joinPoint.getSignature().toLongString();
// 获取上次访问时间戳
long lastAccessTime = requestCountMap.getOrDefault(key, 0L);
// 获取当前时间戳
long currentTime = System.currentTimeMillis();
// 计算时间间隔(秒)
long timeInterval = TimeUnit.MILLISECONDS.toSeconds(currentTime - lastAccessTime);
// 检查时间间隔是否超过限制
if (timeInterval < duration) {
// 检查访问次数是否超过限制
if (requestCountMap.getOrDefault(key, 0L) >= limit) {
throw new RuntimeException("访问频率超过限制");
}
} else {
// 重置访问次数和最后访问时间
requestCountMap.put(key, 0L);
}
// 递增访问次数
requestCountMap.put(key, requestCountMap.getOrDefault(key, 0L) + 1);
return joinPoint.proceed();
}
}
最后,在需要限制频率的接口上加上@RateLimit注解:
@RestController
public class MyController {
@RateLimit(limit = 1, duration = 60) // 设置最大访问次数为1,时间窗口为60秒
@GetMapping("/my-api")
public String myApi() {
// 处理正常的接口逻辑
return "正常响应";
}
}
在上述示例中,我们创建了一个自定义注解@RateLimit,用于标记需要限制频率的接口。然后,我们创建了一个切面RateLimitAspect,在切面中,我们通过解析注解获取最大访问次数和时间窗口,并根据方法的签名构建唯一的键。在切面的enforceRateLimit方法中,我们检查上次访问时间和当前时间的时间间隔,并根据限制条件判断是否允许继续访问。如果超过了限制,则抛出异常。
本文暂时没有评论,来添加一个吧(●'◡'●)