1.背景
最近负责了两个接口层的项目,一个对接的是公司内部的爬虫,另一个对接的是公司外部的中航信。虽然接口层不涉及过多的业务,但是想把接口层做好也并不是那么容易的。遇到的一个很大的问题就是限流。数据组的同事经常反馈说我们的调用量在某一时间内过于频繁,导致抓取服务压力过大,而且频繁的请求有IP被封的危险。因此限流对那些下游是抓取的(或者下游有限流策略的)服务来说十分重要。
在谈限流策略前,我们首先明确一下什么是限流。这里说的限流并不具有一般性,只是针对面临到的业务场景。我们约定,本文中的限流就是限制一段时间内(多为秒级,这个时间在下文中也会被称为时间窗)的请求次数。例如,我们下游服务能承受的最大TPS为2,因此我们希望控制请求量每秒不超过两个。
(还有一点需要说明的就是TPS和QPS的区别,这一点在网上查了很多但是一直没有找到满意的回答,大多是针对浏览器端访问页面的,不适用我们的接口层。我个人认为,TPS是每秒事务数,可以认为是服务器每秒处理的请求数,QPS呢是每秒查询数。乍一看好像这两个一样,但是如果对方服务端也有限流策略就不同了。假设我们下游服务给我们提供的TPS是10,我们一秒发了20个请求过去,结果对方这1秒把所有请求都返回了,但是只处理了10个,多出来的10个快速失败。那么我们从流量统计上看QPS是20,但是实际处理的事务数(TPS)是10。以上均为个人观点,如有不正确指出还请指出)
下面介绍三种限流方式(这里只考虑限流,至于被限流后如何处理,是简单的丢弃还是将其加入重试队列,需要根据业务自行决定,这些暂不列入讨论范围内):
2.限流策略
2.1 信号量
信号量是一种锁,严格意义上说它并不能够做到限流,只能限制并发数。适用于对上游服务的请求限流,但是无法对下游服务限流,尤其是下游服务已经有了限流策略的时候。
// 初始化
Semaphore semaphore = new Semaphore(limit);
// 获取执行权
public boolean acquire() {
return semaphore.tryAcquire();
}
// 释放执行权
public void release() {
return semaphore.release();
}
在对接中航信的服务时,对方给我们提供的最大TPS是10,我们一开始使用的是信号量的方式进行限流。但是发现有大量的请求被航信限流,后来降低信号量阈值,一直降低到6还是有大量