code show: 网络访问超时优化

在这篇blog中,我们将来探讨一个优化的案例,这个案例的背景为:在网络通信中,通常会有需求是从A发送了消息给B,然后同步或异步等待B的响应,但不可能无限期的等下去,因此会需要一个超时机制,通常在基于NIO这样的机制中,发送和接收会是异步方式,通常实现时会采用类似如下的方法,代码简单示例如下。

在这篇blog中,我们将来探讨一个优化的案例,这个案例的背景为:在网络通信中,通常会有需求是从A发送了消息给B,然后同步或异步等待B的响应,但不可能无限期的等下去,因此会需要一个超时机制,通常在基于NIO这样的机制中,发送和接收会是异步方式,通常实现时会采用类似如下的方法,代码简单示例如下:

invoke(){

send(request);

}

onMessageReceived(){

// 接收到响应后的处理

}

在不需要超时的情况下,这样就OK了,在有超时的情况下,则需要启动一个线程来扫描所有连接上(连接太多,所以不太可能每个连接就启一个线程来扫)的所有请求(每个请求的超时时间可能都不一样,有些是1s、有些是3s),这里就出现了一个问题,到底是多久扫描一次呢?简单的实现的话就可以是固定时间间隔扫描一次,这样扫描就显得有些傻了,因为其实很多请求根本就还没到超时时间,而随着连接数、请求数的增加,这里的效率就成问题了,高级点的话就演变为将最近超时的放到最顶端,类似Timer或DelayedQueue的实现,但这样的方式都存在一个问题,就是由于需要排序,所以在往queue里插入和删除对象的时候都需要加锁,这无疑会产生很大的影响,so无法接受,期望能做到的是:

1、单线程扫描请求是否超时;

2、在加入需要扫描的请求时无需加锁;

3、超时扫描等到达了最近有超时的请求后才启动,否则沉默;

4、当请求响应已回来后,可删除需要扫描的相应请求,避免在超时时间段内堆积太多扫描的请求,消耗内存。

我们之前想到的一个方法为

invoke(){

addToScanQueue(request.getId(),request.getTimeout());

send(request);

}

addToScanQueue{

// 判断是否有对应request.getTimeout的queue,如果没有则创建,并往queue里塞入这次需要扫描的请求的id,超时时间,塞入后检查其超时时间和扫描线程的睡眠时间,如超时时间小于睡眠时间,则唤醒扫描线程

}

扫描线程{

// 扫描所有的queue,并取出第一个对象,看看有没有超时,直到取到一个没超时的,比较所有queue中没超时的,看谁时间短,以短的为下次触发扫描的时间点

}

上面这个方法有效的借助了request.getTimeout种类不多的场景以及FIFO的特性,基本上可以做到插入无锁,而且扫描基本上可以等到有需要扫描的才扫描,但有个问题…就是已经有了响应的请求怎么从这里删除呢,一方面queue不好删,另一方面要删多数就要加锁了,如果转为用mark机制的话,由于大部分请求其实都不会超时的,这里会堆积很多的对象,消耗内存,纠结呀…

有同学有兴趣尝试下改造上面的方式吗,既然是code show,感兴趣的同学可直接在回复中贴上代码,:)