其他分享
首页 > 其他分享> > 高可用之——应用级限流

高可用之——应用级限流

作者:互联网

转载请注明出处:https://blog.csdn.net/l1028386804/article/details/100743565

1.限流总并发/连接/请求数

对于一个应用系统来说,一定会有极限并发/请求数,即总有一个TPS/QPS阈值,如果超过了阈值,则系统就会不响应用户请求或响应的非常慢,所以,需要进行过载保护,防止大量请求涌入击垮系统。
例如Tomcat,Connector其中一种配置中有如下几个参数。

2.限流总资源数

稀缺资源(数据库连接池、线程)可以使用池化技术限制总资源数,如:连接池、线程池。如果分配给每个应用的数据库连接是100,那么本应用最多可以使用100个资源,超出则可以等待或抛出异常。

3.限制某个接口的总并发/请求数

限制某个接口的总并发/请求数粒度比较细,可以为每个接口设置相应的阈值,可以使用Java中的AtomicLong或者Semaphore进行限流。模型代码如下:

try{
	if(atomic.incrementAndGet() > 限流数){
		//拒绝处理
	}
	//处理请求
}finally{
	atomic.decrementAndGet();
}

这种方式适合对可降级业务或者需要过载保护的服务进行限流,比如抢购业务,超出限额,要么让用户排队,要么告诉用户没货了,这对用户来说是可以接受的。一些开放平台也会限制用户调用某个接口的试用请求量,这时也可以用这种计数器方式实现。

4.限流某个接口的时间窗请求数

如果限制某个接口/服务每秒/每分钟/每天的请求数/调用量。比如一些基础服务会被很多其他系统调用,可以对每秒/每分钟的调用量进行限速,一种实现方式如下所示。

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);
			}
		});
long limit = 1000;
while(true){
	//得到当前秒
	long currentSeconds = System.currentTimeMillis() / 1000;
	if(counter.get(currentSeconds).incrementAndGet() > limit){
		System.out.println("系统限流了:" + currentSeconds);
		continue;
	}
	//业务处理
}

使用Guava的Cache来存储计数器,过期时间设置为2秒(保证能记录1秒内的计数)。然后,获取当前时间戳,取秒数作为Key进行计数统计和限流。

5.平滑限流某个接口的请求数

之前的限流方式都不能很好的应对突发请求,即瞬间请求可能都被允许,从而导致一些问题。因此,在一些场景中需要对突发请求进行整形,整形为平均速率请求处理(比如5r/s,即每隔200毫秒处理一个请求,平滑了速率)。有两种算法满足场景:令牌桶和漏桶算法。Guava框架提供了令牌桶算法,可以直接使用。
Guava RateLimiter提供的令牌桶算法可用于平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

(1)SmoothBursty

@Test
public void testSmoothBrusty01(){
	RateLimiter limiter = RateLimiter.create(5);
	System.out.println(limiter.acquire());
	System.out.println(limiter.acquire());
	System.out.println(limiter.acquire());
	System.out.println(limiter.acquire());

	System.out.println(limiter.acquire());
	System.out.println(limiter.acquire());
}

将得到类似如下输出:

0.0
0.197944
0.197721
0.199604
0.199207
0.199725

突发请求示例一

@Test
public void testSmoothBrusty02(){
	RateLimiter limiter = RateLimiter.create(5);
	System.out.println(limiter.acquire(5));
	System.out.println(limiter.acquire(1));
	System.out.println(limiter.acquire(1));
	System.out.println(limiter.acquire(1));
}

将得到类似如下输出:

0.0
0.998038
0.197305
0.196624

接下来的请求就被整形为固定速率了。

突发请求示例二

@Test
public void testSmoothBrusty03(){
	RateLimiter limiter = RateLimiter.create(5);
	System.out.println(limiter.acquire(10));
	System.out.println(limiter.acquire(1));
	System.out.println(limiter.acquire(1));
	System.out.println(limiter.acquire(1));
}

将得到类似如下输出:

0.0
1.998626
0.196534
0.199605

突发请求示例三

@Test
public void testSmoothBrusty04() throws Exception{
	RateLimiter limiter = RateLimiter.create(2);
	System.out.println(limiter.acquire());
	Thread.sleep(2000);

	System.out.println(limiter.acquire());
	System.out.println(limiter.acquire());
	System.out.println(limiter.acquire());

	System.out.println(limiter.acquire());
	System.out.println(limiter.acquire());
}

将得到类似如下输出:

0.0
0.0
0.0
0.0
0.499875
0.496976

此处可以看到桶容量为2(允许的突发量),这是因为SmoothBursty中有一个参数:最大突发秒数(maxBurstSeconds),默认1s。突发量/桶容量 = 速率 * maxBurstSeconds,所以本示例中桶容量/突发量为2(本示例中速率为0.5秒,maxBurstSeconds为1秒)。
SmoothBursty通过平均速率和最后一次新增令牌的时间计算出下次新增令牌的时间。另外,需要一个桶暂存一段时间内没有使用的令牌(即可以突发的令牌数)。另外,RateLimiter还提供了tryAcquire()方法来进行无阻塞或可超时的令牌消费。

(2)SmoothWarmingUp

Guava的SmoothWarmingUp能够实现在系统冷启动后慢慢趋于平均速率(即刚开始速率小一些,然后慢慢趋于设置的固定速率)。
创建方式为:

RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)

示例如下:

@Test
public void testSmoothWarmingUp() throws  Exception{
	RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);
	for(int i = 1; i < 5; i++){
		System.out.println(limiter.acquire());
	}
	Thread.sleep(1000);
	for(int i = 1; i < 5; i++){
		System.out.println(limiter.acquire());
	}
}

将得到类似如下输出:

0.0
0.518306
0.357487
0.220917
0.0
0.519406
0.360388
0.220326

速率是梯形上升速率,也就是说冷启动时会以一个比较大的速率慢慢达到平均速率。然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率。

注意:应用级限流只是单应用内的请求限流,如果将应用部署到多台服务器上,应用级限流不能进行全局限流。因此,需要用分布式限流和接入层限流来解决这个问题。

——总结自涛哥的《亿级流量网站架构核心技术》

标签:令牌,可用,acquire,System,限流,应用,limiter,println,out
来源: https://blog.csdn.net/l1028386804/article/details/100743565