系统在设计之初就会有一个预估容量,长时间超过系统能承受的TPS/QPS阈值,(其中TPS是每秒内的事务数,比如执行了dml操作,那么相应的tps会增加;QPS是指每秒内查询次数,比如执行了select操作,相应的qps会增加)系统可能会被压垮,最终导致整个服务不够用。为了避免这种情况,我们就需要对接口请求进行限流。
限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待。
常见的限流模式有控制并发和控制速率,一个是限制并发的数量,一个是限制并发访问的速率,另外还可以限制单位时间窗口内的请求数量。
1)控制并发数量
属于一种较常见的限流手段,在实际应用中可以通过信号量机制(如Java中的Semaphore)来实现。
举个例子,我们对外提供一个服务接口,允许最大并发数为10,代码实现如下:
public class DubboService {
private final Semaphore permit = new Semaphore(10, true);
public void process(){
try{
permit.acquire();
//业务逻辑处理
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
permit.release();
}
}
}
在代码中,虽然有30个线程在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证,还可以用tryAcquire()方法尝试获取许可证。
2)控制访问速率
//速率是每秒两个许可
final RateLimiter rateLimiter = RateLimiter.create(2.0);
void submitTasks(List tasks, Executor executor) {
for (Runnable task : tasks) {
rateLimiter.acquire(); // 也许需要等待
executor.execute(task);
}
}
3)控制单位时间窗口内请求数
某些场景下,我们想限制某个接口或服务 每秒/每分钟/每天 的请求次数或调用次数。例如限制服务每秒的调用次数为50,实现如下:
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
private LoadingCache<Long, AtomicLong> counter =
CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(new CacheLoader<Long, AtomicLong>() {
@Override
public AtomicLong load(Long seconds) throws Exception {
return new AtomicLong(0);
}
});
public static long permit = 50;
public ResponseEntity getData() throws ExecutionException {
//得到当前秒
long currentSeconds = System.currentTimeMillis() / 1000;
if(counter.get(currentSeconds).incrementAndGet() > permit) {
return ResponseEntity.builder().code(404).msg("访问速率过快").build();
}
//业务处理
}
关于限流部分到此就结束了。