apache连接池机制讨论

发布于:2025-03-30 ⋅ 阅读:(24) ⋅ 点赞:(0)

apache连接池的连接有效性

server一般会配置keep-alive超时时间,过了这个时间还没新请求到来,则关闭连接。客户端从连接池里拿出连接时,会检查一下连接是否已关闭,如已关闭,会丢弃掉该连接,并尝试从连接池再拿一个新的连接,代码机制在AbstractConnPool.lease方法里:

public Future<E> lease(final T route, final Object state, final FutureCallback<E> callback) {
        Args.notNull(route, "Route");
        Asserts.check(!this.isShutDown, "Connection pool shut down");
        return new Future<E>() {
            ...
            public E get(long timeout, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
                while(true) {
                    synchronized(this) {
                        PoolEntry var10000;
                        try {
                            E entry = (PoolEntry)this.entryRef.get();
                            if (entry != null) {
                                var10000 = entry;
                            } else {
                                ...
                               // 从连接池租借一个连接
                                E leasedEntry = AbstractConnPool.this.getPoolEntryBlocking(route, state, timeout, timeUnit, this);
                                // 如果该连接满足下面条件之一:
                                // 1. 未配置validateAfterInactivity
                                // 2. 配置了validateAfterInactivity,且自上次使用还未达到该超时时间
                                // 3. 已经达到validateAfterInactivity超时时间,且对连接做测试后依然有效
                                // 则使用该连接
                                if (AbstractConnPool.this.validateAfterInactivity <= 0 || leasedEntry.getUpdated() + (long)AbstractConnPool.this.validateAfterInactivity > System.currentTimeMillis() || AbstractConnPool.this.validate(leasedEntry)) {
                                    if (!this.done.compareAndSet(false, true)) {
                                        AbstractConnPool.this.release(leasedEntry, true);
                                        throw new ExecutionException(AbstractConnPool.operationAborted());
                                    }

                                    this.entryRef.set(leasedEntry);
                                    this.done.set(true);
                                    AbstractConnPool.this.onLease(leasedEntry);
                                    if (callback != null) {
                                        callback.completed(leasedEntry);
                                    }

                                    var10000 = leasedEntry;
                                    return var10000;
                                }
							// 租借的连接经validateAfterInactivity校验发现已关闭,丢弃该连接,并回到while循环开始,继续调用getPoolEntryBlocking获得新的连接,若池子里没有连接,创建一个新连接。
                                leasedEntry.close();
                                AbstractConnPool.this.release(leasedEntry, false);
                                continue;
                            }
                        } catch (IOException var8) {
                           ...
                        }

                        return var10000;
                    }
                }
            }
        };
    }

validateAfterInactivity默认值是2s。一般情况下,足以保证获得的连接是有效的。我曾强制将其设为0,同时上层未设置重试,确实很容易出现java.net.SocketException: Software caused connection abort: recv failed的错误,这恰恰是对面已关闭socket后的行为。当然,如果上层设了重试,重试会再取一个连接,一般都能恢复正常。
AbstractConnPool.this.validate会调用connection的isStale方法:

//CPool.java
protected boolean validate(CPoolEntry entry) {
        return !((ManagedHttpClientConnection)entry.getConnection()).isStale();
    }

那么一个连接是如何判定不新鲜(stale)的呢?逻辑如下:

//BHttpConnectionBase.java
public boolean isStale() {
        if (!this.isOpen()) {
            return true;
        } else {
            try {
                //对面FIN关闭,返回-1
                int bytesRead = this.fillInputBuffer(1);
                return bytesRead < 0;
            } catch (SocketTimeoutException var2) {
                // 对面连接完好,走到这里
                return false;
            } catch (IOException var3) {
                // 对面被kill掉,走到这里,具体异常信息:java.net.SocketException: Connection reset
                // 应该是进程消亡后TCP协议栈发送的RST
                // 另外,SSLException也会走到这里
                return true;
            }
        }
    }

fillInputBuffer方法会尝试从socket里读取字节,返回值为读取的字节数,若返回-1,说明连接已关闭。

顺带说一下,实测发现,isStale的判定对于server端正常或异常关闭连接的情况,都能检测到

各web服务器的keep-alive策略配置

很显然,一个用于生产的web服务器是要配置keep-alive超时的,毕竟机器的IO连接资源有限,万一大量的长连接被占用,新来的请求将得不到服务。

fastAPI可以在启动时指定keepalive的超时时间,像这样:

app = FastAPI()

@app.get("/test")
async def root():
    return "Hello fastapi"

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8002, timeout_keep_alive=600)

这里我们指定600s,默认keepalive超时是5s,即5s没有请求则关闭连接

tomcat的keep-alive策略配置在server.xml里,除了keepAliveTimeout之外,还有maxKeepAliveRequests选项,意思是服务多少个请求后就关闭连接,例如下面的例子,在服务5个请求后关闭连接:

<Connector port="8080" protocol="HTTP/1.1"

               connectionTimeout="20000"

               maxThreads="1000"

               acceptCount="100"

               redirectPort="8443"

               URIEncoding="UTF-8"

               maxKeepAliveRequests="5"/>

两个参数的含义如下:

keepAliveTimeout:
The number of milliseconds Tomcat will wait for a subsequent request before closing the connection

maxKeepAliveRequests:
Maximum number of Keep-Alive requests to honor per connection

FIN和RST的区别

FIN只是“半关闭”。A向B发送FIN,表示A不会再向B发送数据了,接下来,如果B调用recv(无论调几次)只会得到-1(代表EOF)。但是B仍然可以向A继续发送数据,只有B向A发送了FIN,整个连接才算全关闭。

RST则表示整个连接被异常关闭。A向B发送RST,接下来,B调用recv会报connection reset错误(java下是java.net.SocketException: Connection reset异常),注意:不是返回-1哦。且B不能再向A继续发送数据,发送数据也会报connection reset错(java下是java.net.SocketException: Connection reset by peer: socket write error异常)。若A被kill掉,TCP协议栈也会发送RST给B,这时B调用recv会报connection reset错。

两者的区别参考这里:

https://docs.oracle.com/javase/8/docs/technotes/guides/net/articles/connection_release.html

原文:

An orderly release occurs in two stages. First one side (say A) decides to stop sending data and sends a FIN message across to B. When the TCP stack at B's side receives the FIN it knows that no more data is coming from A, and whenever B reads all preceding data off the socket, further reads will return the value -1 to indicate end-of-file. This procedure is known as the TCP half-close, because only one half of the connection is closed. 
By contrast, an abortive close uses the RST (Reset) message. If either side issues an RST, this means the entire connection is aborted and the TCP stack can throw away any queued data which has not been sent or received by either application.

文章里提到了socket.close和发送FIN的区别,前者表示“我不再发送数据,也不想接收数据”,后者仅表示“我不再发送数据”。所以,如果A调用socket.close,B继续向A发送数据,虽然能发送成功,但接下来的recv操作会报错:

java.net.SocketException: Software caused connection abort: recv failed

因为B的发送操作,A是无法接收到的,TCP协议栈会发送RST信号关闭整个连接:

But since A's socket is closed there is nobody to read data if B should continue sending. In this situation A's TCP stack must send an RST to forcibly terminate the connection.