测试类:
//package com.xxx.fxserver.service.utils.limiters;
//
//import com.xxx.fxserver.vo.LimiterEnum;
//
//import java.text.SimpleDateFormat;
//import java.util.Date;
//import java.util.concurrent.CountDownLatch;
//import java.util.concurrent.ExecutorService;
//import java.util.concurrent.Executors;
//import java.util.concurrent.atomic.AtomicInteger;
//
///**
// * @author jangwenhua
// * @description: 测试计数器限流算法
// * @date 2023-07-14 15:10
// */
//public class BaseLimitTest {
//
// private MbLimiter limiter;
// private int maxQPS;//填充速率
//
// public BaseLimitTest(LimiterEnum type, int qps) {
// this.limiter = MbLimiterFactory.getCountLimiter(type, qps);
// this.maxQPS = qps;
// }
//
// public void test(boolean showLog) throws Exception {
//
// SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
//
// final CountDownLatch countDownLatch1 = new CountDownLatch(100);
// final CountDownLatch countDownLatch2 = new CountDownLatch(100);
// final CountDownLatch countDownLatch3 = new CountDownLatch(100);
//
// // 用于统计成功请求个数
// AtomicInteger count1 = new AtomicInteger(0);
// AtomicInteger count2 = new AtomicInteger(0);
// AtomicInteger count3 = new AtomicInteger(0);
//
// // 固定大小线程池
// ExecutorService executorService = Executors.newFixedThreadPool(100);
//
// System.out.println("====== Test 1 100 并发 ======");
//
// // 获取开始时间
// long startTime = System.currentTimeMillis();
//
// // 前 1s 平均 50 次,随后不到 1s 内发起 50 次请求
// for (int i = 0; i < 100; i++) {
// executorService.execute(() -> {
// if (limiter.tryAcquire()) {
// if (showLog)
// System.out.println("success " + sf.format(new Date()));
// count1.incrementAndGet();
// } else {
// if (showLog)
// System.out.println("failed " + sf.format(new Date()));
// }
// countDownLatch1.countDown();
// });
// // 计数器算法最多请求20次,出现突刺现象
// if (i < 50 || i > 80) {
// Thread.sleep(20);
// }
// }
//
// countDownLatch1.await();
// System.out.println("Time Used: " + (System.currentTimeMillis() - startTime) / 1000.0 + " success: " + count1);
//
// Thread.sleep(1000);
// System.out.println("====== Test 2 每秒钟请求 10 次 ======");
//
// startTime = System.currentTimeMillis();
//
// // 功能性测试,每秒平均 10 次请求
// for (int i = 0; i < 100; i++) {
// executorService.execute(() -> {
// if (limiter.tryAcquire()) {
// if (showLog)
// System.out.println("success " + sf.format(new Date()));
// count2.incrementAndGet();
// } else {
// if (showLog)
// System.out.println("failed " + sf.format(new Date()));
// }
// countDownLatch2.countDown();
// });
// // 每 10 次请求延时 1 秒
if ((i + 1) % 10 == 0) {
Thread.sleep(1000);
}
// Thread.sleep(100);
// }
//
// countDownLatch2.await();
// System.out.println("Time Used: " + (System.currentTimeMillis() - startTime) / 1000.0 + " success: " + count2);
//
// Thread.sleep(1000);
// System.out.println("====== Test 3 每秒钟请求 20 次 ======");
//
// startTime = System.currentTimeMillis();
//
// // 前 4.5s 平均每秒 20 次请求,4.5s 后并发请求 10 次
// for (int i = 0; i < 100; i++) {
// executorService.execute(() -> {
// if (limiter.tryAcquire()) {
// if (showLog)
// System.out.println("success " + sf.format(new Date()));
// count3.incrementAndGet();
// } else {
// if (showLog)
// System.out.println("failed " + sf.format(new Date()));
// }
// countDownLatch3.countDown();
// });
// 每 10 次请求延时 1 秒
if ((i + 1) % 20 == 0) {
Thread.sleep(1000);
}
// // 测试下令牌桶算法瞬时处理一定量的请求
// if (i < 90) {
// Thread.sleep(50);
// }
// }
//
// countDownLatch3.await();
// System.out.println("Time Used: " + (System.currentTimeMillis() - startTime) / 1000.0 + " success: " + count3);
// executorService.shutdown();
//
// }
//}
枚举算法类型:
package com.xxx.fxserver.vo;
public enum LimiterEnum {
COUNT_LIMITER,//计数器限流算法
LEAKY_BUCKET_LIMITER,//可重入锁 漏桶限流算法
TOKEN_BUCKET_LIMITER//令牌桶限流算法
}
计数器:
package com.xxx.fxserver.service.utils.limiters;
/**
* @author jangwenhua
* @description: 计数器限流算法
* @date 2023-07-14 15:10
*/
public class CountLimiter extends MbLimiter {
private int count; // 计数器
private long lastTime; // 时间戳
public CountLimiter(int qps) {
super(qps);
count = 0;
lastTime = 0;
}
@Override
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
if (now - lastTime > 1000) {
lastTime = now>>3<<3; // 保证时间戳后三位都是0, 更精确
count = 1;
return true;
} else if (count < qps) {
count++;
return true;
} else {
return false;
}
}
}
漏桶算法:
package com.xxx.fxserver.service.utils.limiters;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author jangwenhua
* @description: 漏桶限流算法
* @date 2023-07-14 15:10
*/
public class LeakyBucketLimiter extends MbLimiter {
private final long capacity; // 水桶容量, 一秒流光
private double remainWater; // 目前水桶剩下的水量
private long lastTime; // 时间戳
private ReentrantLock lock = new ReentrantLock(); // 可重入锁
LeakyBucketLimiter(int qps) {
super(qps);
capacity = qps;
remainWater = capacity;
lastTime = 0;
}
@Override
public boolean tryAcquire() {
lock.lock();
try {
long now = System.currentTimeMillis();
double outWater = ((now - lastTime)/1000.0) * capacity; // 计算这段时间匀速流出的水
lastTime = now;
remainWater = Math.max(0, remainWater - outWater);
if (remainWater + 1 <= capacity) {
remainWater += 1;
long waitingMs = (long)((remainWater / capacity) * 1000); // 计算刚加入的水滴完全滴出漏桶需要的时间(毫秒)
lock.unlock();
try {
Thread.sleep(waitingMs); // 为实现匀速处理请求,需要阻塞一段时间后再return
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
} else return false;
} finally {
if (lock.isLocked()) lock.unlock();
}
}
// @Override
// public synchronized boolean tryAcquire() {
// long now = System.currentTimeMillis();
// double outWater = ((now - lastTime)/1000.0)*capacity; // 计算这段时间匀速流出的水
// lastTime = now;
// if (outWater > remainWater) {
// // 请求已全部处理完毕
// remainWater = 1;
// return true;
// } else {
// // 还有未处理的请求
// remainWater -= outWater;
// if (remainWater + 1 <= capacity) {
// remainWater += 1;
// return true;
// } else return false;
// }
// }
//}
}
令牌桶算法:
package com.xxx.fxserver.service.utils.limiters;
/**
* @author jangwenhua
* @description: 令牌桶限流算法
* @date 2023-07-14 15:10
*/
public class TokenBucketLimiter extends MbLimiter {
private final int capacity; // 桶内能装多少令牌
private double curTokenNum; // 现在桶内令牌数量(用double存)
private long lastTime; // 时间戳
TokenBucketLimiter(int qps) {
super(qps);
capacity = qps;
curTokenNum = 0;
lastTime = 0;
}
@Override
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
double intoToken = (now - lastTime)/1000.0 * capacity;
lastTime = now;
if (intoToken + curTokenNum > capacity) {
// 令牌已放满
curTokenNum = capacity - 1;
return true;
} else if (intoToken + curTokenNum >= 1) {
// 还有令牌
curTokenNum += intoToken - 1;
return true;
} else {
curTokenNum += intoToken;
return false;
}
}
}
限流器抽象父类:
package com.xxx.fxserver.service.utils.limiters;
/**
* @author jangwenhua
* @description: 限流器抽象父类
* @date 2023-07-14 15:10
*/
public abstract class MbLimiter {
final int qps;//填充速率
MbLimiter(int qps) {
this.qps = qps;
}
// 获取继续执行的资格(非阻塞)立刻返回成功或失败
public abstract boolean tryAcquire();
}
限流器工厂类:
package com.xxx.fxserver.service.utils.limiters;
import com.xxx.fxserver.vo.LimiterEnum;
/**
* @author jangwenhua
* @description: 限流器工厂类
* @date 2023-07-14 15:10
*/
public class MbLimiterFactory {
public static MbLimiter getCountLimiter(LimiterEnum limiterEnum, int qps) {
switch (limiterEnum) {
case COUNT_LIMITER:
return new CountLimiter(qps);
case LEAKY_BUCKET_LIMITER:
return new LeakyBucketLimiter(qps);
case TOKEN_BUCKET_LIMITER:
return new TokenBucketLimiter(qps);
default:
return null;
}
}
}
定义注解调用参数:
package com.xxx.fxserver.service.utils.limiters;
import com.xxx.fxserver.vo.LimiterEnum;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
@Target(ElementType.METHOD)//目标是方法
@Documented//文档生成时,该注解将被包含在javadoc中,可去掉
public @interface MbLimiters {
/**
* 类型
*/
LimiterEnum type() default LimiterEnum.COUNT_LIMITER;
/**
* 一秒流数
*/
int qps() default 10;
}
注解切面核心代码:
package com.xxx.fxserver.service.utils.limiters;
import com.xxx.fxserver.common.exception.BusinessException;
import com.xxx.fxserver.vo.LimiterEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
@Aspect
@Component
@Slf4j
public class MbLimitersAspect {
@Pointcut("@annotation(com.xxx.fxserver.service.utils.limiters.MbLimiters)")
public void controllerAspect() { }
@Around("controllerAspect()")
public Object aroundMethod(ProceedingJoinPoint jp) throws Throwable {
boolean showLog =true;
MbLimiter limiter = giveController(jp);
//执行方法
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
if (limiter.tryAcquire()) {
if (showLog)
log.info("success " + sf.format(new Date()));
} else {
if (showLog)
log.info("failed " + sf.format(new Date()));
throw new BusinessException("请重新尝试!");
}
return jp.proceed();
}
private MbLimiter giveController(ProceedingJoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
MbLimiters limiters = method.getAnnotation(MbLimiters.class);
MbLimiter limiter = this.getCountLimiter(limiters.type(),limiters.qps());
return limiter;
}
return null;
}
public static MbLimiter getCountLimiter(LimiterEnum limiterEnum, int qps) {
switch (limiterEnum) {
case COUNT_LIMITER:
return new CountLimiter(qps);
case LEAKY_BUCKET_LIMITER:
return new LeakyBucketLimiter(qps);
case TOKEN_BUCKET_LIMITER:
return new TokenBucketLimiter(qps);
default:
return null;
}
}
}
接口调用注解方式:
@PostMapping(value = "/getQrcode")
@MbLimiters(type = LimiterEnum.TOKEN_BUCKET_LIMITER,qps = 80)
public ResultVo getQrcode(@RequestBody Map<String, Object> params) throws IOException {
return null;
}