SpringBoot三十:使用Guava的RateLimiter限制IP访问接口频率

pom文件引入以下依赖


	com.google.guava</groupId>
	guava</artifactId>
	28.1-jre</version>
</dependency>

RateLimiter简单使用

@Test
public void test1() {
	long start = System.currentTimeMillis();
	RateLimiter limiter = RateLimiter.create(1.0); // 这里的1表示每秒允许处理的量为1个
	for (int i = 1; i <= 10; i++) {
		limiter.acquire();// 请求RateLimiter, 超过permits会被阻塞
		System.out.println("call execute:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
	}
	long end = System.currentTimeMillis();
	System.out.println("time:" + (end - start) + "'ms");
}

结果

call execute:2019-11-07 10:52:20
call execute:2019-11-07 10:52:21
call execute:2019-11-07 10:52:22
call execute:2019-11-07 10:52:23
call execute:2019-11-07 10:52:24
call execute:2019-11-07 10:52:25
call execute:2019-11-07 10:52:26
call execute:2019-11-07 10:52:27
call execute:2019-11-07 10:52:28
call execute:2019-11-07 10:52:29
time:9000'ms

控制每秒N个许可(不限制IP)

Controller

@RequestMapping("/ratelimiter")
@ResponseBody
public String ratelimiter() {
	if (loadingCacheService.tryAcquire()) {
		// 获得令牌(不限制访问)
		return "success";
	} else {
		// 未获得令牌(限制访问)
		int a = 1 / 0;// 方便看测试结果
		System.out.println(a);
		return "error";
	}
}

Service

@Override
public boolean tryAcquire() {
	return rateLimiter.tryAcquire();

}

// 每秒控制5个许可
RateLimiter rateLimiter = RateLimiter.create(5.0);

jmeter10个线程测试结果
SpringBoot三十:使用Guava的RateLimiter限制IP访问接口频率_第1张图片
10个并发请求有6个能获得令牌,结论:RateLimiter.create(n)能获取到n+1个令牌

控制每个IP的每秒N个许可(限制IP访问接口频率)

Controller

@RequestMapping("/iplimiter")
@ResponseBody
public String ipLimiter(HttpServletRequest request) {
	String ipAddr = IPUtil.getIpAddr(request);
	try {
		RateLimiter limiter = loadingCacheService.getIPLimiter(ipAddr);
		if (limiter.tryAcquire()) {
			// 获得令牌(不限制访问)
			return "success";
		} else {
			// 未获得令牌(限制访问)
			int a = 1 / 0;
			System.out.println(a);
			return "error";
		}
	} catch (ExecutionException e) {
		e.printStackTrace();
	}
	return ipAddr;
}

Service

@Override
public RateLimiter getIPLimiter(String ipAddr) throws ExecutionException {
	return ipRequestCaches.get(ipAddr);
}

LoadingCache, RateLimiter> ipRequestCaches = CacheBuilder.newBuilder()
		.maximumSize(1000)// 设置缓存个数
		.expireAfterWrite(1, TimeUnit.MINUTES)
		.build(new CacheLoader, RateLimiter>() {
			@Override
			public RateLimiter load(String s) throws Exception {
				return RateLimiter.create(0.1);// 新的IP初始化 (限流每秒0.1个令牌响应,即10s一个令牌)
			}
		});

注:主要就是把每一个用户IP的令牌桶RateLimiter给缓存起来,然后每次用户请求接口的时候就去拿一次令牌,拿到令牌则不限制访问,没拿到则限制访问
IPUtil

/**
 * 获取客户端IP地址
 */
public class IPUtil {

	/**
	 * 客户端真实IP地址的方法一:
	 */
	public static String getRemortIP(HttpServletRequest request) {
		if (request.getHeader("x-forwarded-for") == null) {
			return request.getRemoteAddr();
		}
		return request.getHeader("x-forwarded-for");
	}

	/**
	 * 客户端真实IP地址的方法二:
	 */
	public static String getIpAddr(HttpServletRequest request) {
		String ip = "";
		if (request != null) {
			ip = request.getHeader("x-forwarded-for");
			if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
				ip = request.getHeader("Proxy-Client-IP");
			}
			if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
				ip = request.getHeader("WL-Proxy-Client-IP");
			}
			if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
				ip = request.getRemoteAddr();
			}
		}
		return ip;
	}

}

jmeter10个线程测试结果
SpringBoot三十:使用Guava的RateLimiter限制IP访问接口频率_第2张图片
同一IP10s才能访问一次接口

基于 自定义注解+切面 的方式实现

pom.xml


	org.springframework.boot</groupId>
	spring-boot-starter-aop</artifactId>
</dependency>

自定义注解

/**
 * 自定义注解可以不包含属性,成为一个标识注解
 */
@Inherited
@Documented
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface IPLimit {

}

切面

@Component
@Scope
@Aspect
public class IPLimitAspect {

	@Autowired
	private HttpServletRequest request;
	@Autowired
	private LoadingCacheService loadingCacheService;

	@Pointcut("@annotation(cn.com.javakf.ratelimiter.annotation.IPLimit)")
	public void ipLimit() {

	}

	@Around("ipLimit()")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
		Object obj = null;
		String ipAddr = IPUtil.getIpAddr(request);
		RateLimiter limiter = loadingCacheService.getIPLimiter(ipAddr);
		if (limiter.tryAcquire()) {
			// 获得令牌(不限制访问)
			obj = joinPoint.proceed();
		} else {
			// 未获得令牌(限制访问)
			obj = new Result(true, StatusCode.OK, "error");
		}
		return obj;
	}

}

测试

/**
 * 基于 自定义注解+切面 的方式实现
 * 
 * @return
 */
@ResponseBody
@IPLimit // 可以非常方便的通过这个注解来实现限流
@RequestMapping("/annotation")
public Result test() {
	return new Result(true, StatusCode.OK, "success");
}

参考:
限流算法之漏桶算法、令牌桶算法
Guava RateLimiter实现接口API限流
guava 限流的两种方式
SpringBoot基于RateLimiter+AOP动态的为不同接口限流

代码托管:springboot_ratelimiter

你可能感兴趣的:(#,SpringBoot,Spring全家桶)