当前位置: 首页 > 科技观察

信号量限流,高并发场景不得不说的秘密

时间:2023-03-15 13:16:50 科技观察

限流可以认为是降级,一般是根据后台负载提前预估的一个阈值(也可以动态调整)。高于此值,将执行一些旁路处理。根据业务形式,会有直接拒绝、延迟处理、搁置等待、部分穿透、默认退回等应对方式。concurrent包中的信号量因为使用简单,易于理解而被广泛使用。但是,如果你不认真测试就直接使用网友分享的简单代码,那我可以给你看个电影:《当故障来敲门》。看看下面的简单代码。获取和释放是一对命运相同的鸳鸯。我们把release放在finally块里,一切看起来都很和谐。1)模拟的业务请求大约需要100毫秒2)acquire的参数5表示允许5个线程同时处理3)每次执行完,输出本次执行的具体耗时,加上我们等待的时间启动1000个线程执行req方法。SemaphoreLimiterBadCheckerlimiter=newSemaphoreLimiterBadChecker();ExecutorServiceexecutor=Executors.newCachedThreadPool();for(inti=0;i<1000;i++){executor.submit(()->{while(true){System.out.println(limiter.req)());}});}下面是执行结果。可以看到虽然我们的接口只用了100ms,但是实际执行的时间要长很多,而且没有fail。运行久一点后,可以发现有大量线程被饿死了。更改为公平锁并不能改善这种情况。这就是故障。原因是这样的。web端的资源(比如tomcat)也是有限的。当我们的限流器起作用,实际并发请求高于处理能力时,这种线程阻塞的情况就会逐步传递下去。服务器的响应可能有以下过程:1)压力正常,服务正常,时间正常。2)压力上升,服务开始大面积超时。由于使用了非公平锁竞争,偶尔会出现正常的耗时请求。3)压力不断增大,服务器开始进入假死状态,几乎无法接受新的请求。在client端,既不能提示服务无法处理,也不能中断请求,所有的请求都在循环。继续增加tomcat的连接数和线程数,影响不大。将acquire更改为tryAcquire?仍然没有解决问题。tryAcquire返回的是bool类型,失败了还是可以执行的,包括finally块。有毛用的?if(!tryAcquire()){returnTOO_MANY_REQUESTS;}在上面加个判断,这个才是正道。tryAcquire还可以加一个timeout参数,这样不会立即返回失败,也不会让调用者无限等待,而是控制在合理的响应时间内请求成功。响应时间=超时时间+业务处理时间。以spring为例,可以在preHandle中获取这个权限,然后在postHandle中释放;您还可以使用计时器以特定频率重新制作信号量。当然你要区别对待。1、像上面提到的web服务,可以直接拒绝服务。快速反应是最重要的。2.像秒杀,排序等,可以通过排队或者等待来解决(部分)3.像消息消费,如果没有顺序需求,我觉得无限等待可能是个好办法4,对于大部分可有可无的业务结果,使用一些默认值直接返回,效果会好很多。虽然是限流,但是做熔断工作的用户一定要注意区分。End很奇怪java抽象出CyclicBarrier,在使用场景上不是很高(相对),但是没有通用的限流方法。信号量虽然可以模拟这个过程,但是不够友好,太容易出错。限流(非分布式)最好使用guava组件,我们会在后面的文章中讨论。