目录
设计原因
设计背景
有一个业务需求,是需要获取部门成员总数展示。
当时想到了两种方案:
一是定时计算每个部门的数据再进行更新。
二是监听成员表,部门成员数据每次出现变更的时候对部门数据进行计算更新。
对于一来说,部门组织架构不是一个会非常平凡变更的数据,因此每次全表计算会造成一些性能损耗。所以,选择方案二。
方案二存在的问题:比如在导入组织架构的时候,一个部门数据频繁变更,部门数据大量重复计算更新,可能导致系统雪崩。
因此,需要控制同一部门数据频繁请求,达到一段时间同一部门更新限量次数的目的。
现有限流算法不适用
行业使用的限流算法会把超过限制的请求过滤掉,以至于丢失掉之后的更新,这种只适合幂等更新的限制,不适用于汇总计算更新。
设计思路
理想设计是,如果能够知道这段时间对于这个部门最后一次变更的时间,再在这个时间后调用接口更新计算部门总数。
但是目前没有想到一个好的办法知道这个请求是不是最后一个请求。
所以使用一个折中的思路,对限流算法进行优化,我们预设某一个请求是最后一个请求,并进行执行。
怎么预设呢。
如果该时间段限流了,就添加一个执行任务到下一个限流时间段,预设被放到下一个时间段的请求是最后一个请求。这样就能保证上一个时间段的数据一定是最新的,但是会存在一个时间窗口的数据延迟。(eg:12:00:10有请求超过了一分钟的限制次数,就把这个请求延迟到12:01:10执行)
图解
限流次数:n
时间窗口: t秒
单位时间t1内请求限流n次,大于n次之后,将第n+1请求延迟到下一个时间窗口t2发送,剩余请求丢弃。
缺点
上面也提到了,延迟限制后的更新会在下一个时间窗口执行,所以会有一个时间窗口的误差。
实现方式
固定窗口 + 延迟请求
实现思路
限流算法的优化
常用的4种限流算法有:固定窗口算法、滑动窗口算法(每次都以当前时间为开始,往后t的长度为窗口)、漏桶算法(请求加一,消费减一,当count>limit开始限制)、令牌桶算法(系统每恒定的时间往令牌通放令牌.如果令牌被用完了,就限制)。
4种算法中固定窗口算法实现最为简单,并且已经满足了我们单位时间内限制的需要,所以选择用固定窗口算法。
如果想要更准确限流,也可以改用滑动窗口。但是实现就更复杂了,感觉也没那么必要。
窗口坐标的计算
默认坐标的简单实现,用当前时间的秒数/窗口大小。
这只是一种简单的实现,为了防止坐标的碰撞,用这种算法的话需要窗口大小需要时60的因子
如果有特殊需要,也可以实现WindowPointSecond接口,自定义时间窗口算法。
/**
* 限流窗口坐标计算接口
*/
public interface WindowPointService {
/**
* 区间坐标
* @return
*/
Integer getPoint();
/**
* 区间时间(单位:s)
* @return
*/
Integer getSectionTime();
}
Footer
时间坐标枚举
定时窗口实现