ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

for (int i = 1; i < 100; i++) {

final int tempI = i;

newCachedThreadPool.execute(new Runnable() {

public void run() {

if (limitService.acquire()) {

System.out.println(“你没有被限流,可以正常访问逻辑 i:” + tempI);

} else {

System.out.println(“你已经被限流呢 i:” + tempI);

}

}

});

}

}

}

这个计数器的限流方式很简单吧,但这样问题吗?好好想想……

还是以60运行访问10次请求为例,在第一次0-58秒之内,没有访问请求,在59秒之内突然来了10次请求,这个时候会做什么,由于已经到了1分钟计数器会重置。

这个时候第二次的1秒内(1分0秒)又了10请求,这个时候是不是就在2秒之内有20个请求被放行了呢?(59秒,1分0秒),如果某个服务器的访问量只能是10次请求,那这种限流方式已经导致服务器挂了;

4. 滑动窗口计数器

前面已经知道简单的计数器的实现方式,也知道他会出现的一些问题,虽然这些问题举得有些极端,但还是有更好得解决方案,这方案就是使用滑动窗口计数器

滑动窗口计数器得原理是在没错请求过来得时候,先判断前面N个单位内得总访问量是否操过得阈值,并且在当前得时间单位得请求数上+1

举例来说,要求1分钟的访问量不能超过10次

可以把1分钟看成是6个10秒钟的时间,0-9秒的访问数记录到第一个格子,10-19秒的访问数记录数记录到第二个格子以此内推,每次统计将6个格子里面的数据求和,如果超过了10次就不允许访问。

import java.util.concurrent.atomic.AtomicInteger;

public class EnjoySlidingWindow {

private AtomicInteger[] timeSlices;

/* 队列的总长度 */

private final int timeSliceSize;

/* 每个时间片的时长 */

private final long timeMillisPerSlice;

/* 窗口长度 */

private final int windowSize;

/* 当前所使用的时间片位置 */

private AtomicInteger cursor = new AtomicInteger(0);

public static enum Time {

MILLISECONDS(1),

SECONDS(1000),

MINUTES(SECONDS.getMillis() * 60),

HOURS(MINUTES.getMillis() * 60),

DAYS(HOURS.getMillis() * 24),

WEEKS(DAYS.getMillis() * 7);

private long millis;

Time(long millis) {

this.millis = millis;

}

public long getMillis() {

return millis;

}

}

public EnjoySlidingWindow(int windowSize, Time timeSlice) {

this.timeMillisPerSlice = timeSlice.millis;

this.windowSize = windowSize;

// 保证存储在至少两个window

this.timeSliceSize = windowSize * 2 + 1;

init();

}

/**

初始化

*/

private void init() {

AtomicInteger[] localTimeSlices = new AtomicInteger[timeSliceSize];

for (int i = 0; i < timeSliceSize; i++) {

localTimeSlices[i] = new AtomicInteger(0);

}

timeSlices = localTimeSlices;

}

private int locationIndex() {

long time = System.currentTimeMillis();

return (int) ((time / timeMillisPerSlice) % timeSliceSize);

}

/**

对时间片计数+1,并返回窗口中所有的计数总和 该方法只要调用就一定会对某个时间片进行+1 @return

*/

public int incrementAndSum() {

int index = locationIndex();

int sum = 0;

// cursor等于index,返回true

// cursor不等于index,返回false,并会将cursor设置为index

int oldCursor = cursor.getAndSet(index);

if (oldCursor == index) {

// 在当前时间片里继续+1

sum += timeSlices[index].incrementAndGet();

} else {

//轮到新的时间片,置0,可能有其它线程也置了该值,容许

timeSlices[index].set(0);

// 清零,访问量不大时会有时间片跳跃的情况

clearBetween(oldCursor, index);

sum += timeSlices[index].incrementAndGet();

}

for (int i = 1; i < windowSize; i++) {

sum += timeSlices[(index - i + timeSliceSize) % timeSliceSize].get();

}

return sum;

}

/**

判断是否允许进行访问,未超过阈值的话才会对某个时间片+1 @param threshold @return

*/

public boolean allow(int threshold) {

int index = locationIndex();

int sum = 0;

int oldCursor = cursor.getAndSet(index);

if (oldCursor != index) {

timeSlices[index].set(0);

clearBetween(oldCursor, index);

}

for (int i = 0; i < windowSize; i++) {

sum += timeSlices[(index - i + timeSliceSize) % timeSliceSize].get();

}

// 阈值判断

if (sum < threshold) {

// 未超过阈值才+1

timeSlices[index].incrementAndGet();

return true;

}

return false;

}

/**

将fromIndex~toIndex之间的时间片计数都清零 极端情况下,当循环队列已经走了超过1个timeSliceSize以上,这里的清零并不能如期望的进行 @param fromIndex 不包含 @param toIndex 不包含

*/

private void clearBetween(int fromIndex, int toIndex) { 自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

在此为大家准备了四节优质的Android高级进阶视频:

架构师项目实战——全球首批Android开发者对Android架构的见解

附相关架构及资料

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

架构师项目实战——全球首批Android开发者对Android架构的见解

附相关架构及资料

[外链图片转存中…(img-uAs72VZQ-1712612407288)]

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

好文阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。