Link pool usage:
As we all know, HTTP is based on TCP, and each communication requires a three-way handshake to establish a connection. However, in the case of high concurrency, the frequent establishment and release of connections will lead to poor performance. Therefore, the keep-alive mechanism appears, which means that the connection is kept alive for a certain timeout period after the data transmission is completed.
- Objective: To reduce the number of TCP links created,
- Method: In a certain period of time (timeout), ensure that the link keepalive, thus reuse
- Important structural analysis
- Connections: Connection cache pool, similar to the thread pool in the previous post. Implemented in a Deque, is a two-ended list that supports inserting elements at the beginning and end,
- Excutor: thread pool used to detect and clean up idle sockets.
- RealConnection: The Connection implementation class represents a three-way handshake link (containing multiple streams)
- StreamAllocation: Finds a “connection” and establishes a “stream” for a “request”
- Request: that is, Call when we use it
- Link: RealConnection, also known as a socket
- Stream: A multiplexing technique used in the development of HTTP. If multiple streams are connected to the same host and port, they can use the same socket to connect. The advantage of this method is to reduce the TCP three-way handshake time
- analogy
The connection pool is the reuse of links, which can be likened to the chute of Frozone in the Incredibles.
! [Mr. Fronzone](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4cc2f8ecac442cb820e65a502fdc4a9~tplv-k3u1fbpfcp-zoom-1.imag e)Copy the code
When he wanted to get from the start to the finish, he built a RealConnection. The slide would melt because of the temperature difference, but it was clear that creating it from scratch each time would consume a lot of resources, so he updated the technology to keep the slide going for a while. Each slide is divided into a different road, similar to our road. Many cars can run in parallel on a road, which is called a flow in a link.
Source code analysis:
Source code is located in RealConnectionPool, you can enter it by CTRL + Shift + F. The current version uses Kotlin implementation, also download 3.8.0
-
Put: a thread that puts a new connection into a list and performs cleanup of idle connections (Part 3 details)
fun put(connection: RealConnection) { connection.assertThreadHoldsLock() connections.add(connection) Cleanupqueue.schedule (cleanupTask)Copy the code
-
Get, creates a connection object and iterates through the cache pool. If the conditions are met, the connection is retrieved from the cache pool and returned to Request.
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (connection.isEligible(address, route)) { streamAllocation.acquire(connection); returnconnection; }}return null; } //acquire:ReportedAcquired set totrue, and adds a weak reference to a new flow to the Connection-held allocations -- that is, one flow to this connection.Copy the code
Among these conditions, isEligible, which you can see by following up
- The maximum number of concurrent connections did not reach the upper limit
- Other parameters of the two addresses are the same
- The urls of the two addresses have the same host
If the above conditions are met, then the host is the same and can be reused directly. If the above conditions are not met, there is still a chance to use connections (merge connections) :
- First the connection needs to use HTTP/2
- The Ip address must be the same as the one before reuse, and no proxy can be used
- The new host must be included in the server certificate authorization for this connection.
- The locking certificate must match the host
public boolean isEligible(Address address, @Nullable Route route) { // If this connection is not accepting new streams, we're done. if (allocations.size() >= allocationLimit || noNewStreams) return false; // If the non-host fields of the address don't overlap, we're done. if(! Internal.instance.equalsNonHost(this.route.address(), address)) return false; // If the host exactly matches, we're done: this connection can carry the address. if (address.url().host().equals(this.route().address().url().host())) { return true; // This connection is a perfect match. } // At this point we don't have a hostname match. But we still be able to carry the request if // our connection coalescing requirements are met. See also: // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/ // 1. This connection must be HTTP/2. if (http2Connection == null) return false; // 2. The routes must share an IP address. This requires us to have a DNS address for both // hosts, which only happens after route planning. We can't coalesce connections that use a // proxy, since proxies don't tell us the origin server's IP address. if (route == null) return false; if(route.proxy().type() ! = Proxy.Type.DIRECT)return false; if (this.route.proxy().type() ! = Proxy.Type.DIRECT)return false; if (!this.route.socketAddress().equals(route.socketAddress())) return false; // 3. This connection's server certificate's must cover the new host. if(route.address().hostnameVerifier() ! = OkHostnameVerifier.INSTANCE)return false; if(! supportsUrl(address.url()))return false; // 4. Certificate pinning must match the host. try { address.certificatePinner().check(address.url().host(), handshake().peerCertificates()); } catch (SSLPeerUnverifiedException e) { return false; } return true; // The caller's address can be carried by this connection. } Copy the code
-
Connection pool cleaning and recycling
Executor. Execute (cleanupRunnable) has been used in the put method to cleanup the connection pool and recycle it. Now take a closer look at what it does: trace into cleanupRunnable and find its logic as a timed run cleanup, where timed waits are locked and cannot be interrupted.
private final Runnable cleanupRunnable = new Runnable() { @Override public void run(a) { while (true) { long waitNanos = cleanup(System.nanoTime()); if (waitNanos == -1) return; if (waitNanos > 0) { long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); synchronized (ConnectionPool.this) { try { ConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } } }; Copy the code
The following focuses on clean up. In general, its role is to find restricted links and clear them. Specific analysis can be found:
- InUseConnectionCount counts the number of links in use and idleConnectionCount counts the number of links that are idle. The change of the number of the two links, is through pruneAndGetAllocationCount () method to control, work also naturally to judge the incoming links is idle or running.
- If the connection is longer than the keepalive limit (keepAliveDurationNs 5 minutes), Or if the number of free links exceeds the maximum number (maxIdleConnections5) of the connection pool, the connection is removed
long cleanup(long now) { int inUseConnectionCount = 0; int idleConnectionCount = 0; RealConnection longestIdleConnection = null; long longestIdleDurationNs = Long.MIN_VALUE; // Find either a connection to evict, or the time that the next eviction is due. synchronized (this) { for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) { RealConnection connection = i.next(); // If the connection is in use, keep searching. if (pruneAndGetAllocationCount(connection, now) > 0) { inUseConnectionCount++; continue; } idleConnectionCount++; // If the connection is ready to be evicted, we're done. long idleDurationNs = now - connection.idleAtNanos; if(idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; }}if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) { // We've found a connection to evict. Remove it from the list, then close it below (outside // of the synchronized block). connections.remove(longestIdleConnection); } else if (idleConnectionCount > 0) { // A connection will be ready to evict soon. return keepAliveDurationNs - longestIdleDurationNs; } else if (inUseConnectionCount > 0) { // All connections are in use. It'll be at least the keep alive duration 'til we run again. return keepAliveDurationNs; } else { // No connections, idle or in use. cleanupRunning = false; return -1; } } closeQuietly(longestIdleConnection.socket()); // Cleanup again immediately. return 0; } Copy the code
It is found that the effect is achieved by maintaining the linked list of Reference type. Log connection active (>0 indicates active =0 indicates idle)
The core of the overall logic is this: If the StreamAlloction reference is free, but the item is still in the connection reference list, then a memory leak has occurred
// To clean up any StreamAllocation that might leak and return the number of Streama1Locations that are using this connection, private int pruneAndGetAllocationCount(RealConnection connection, long now) { List<Reference<StreamAllocation>> references = connection.allocations; for (int i = 0; i < references.size(); ) { Reference<StreamAllocation> reference = references.get(i); if(reference.get() ! =null) { i++; continue; } // Indicates the flow is active // We've discovered a leaked allocation. This is an application bug. StreamAllocation.StreamAllocationReference streamAllocRef = (StreamAllocation.StreamAllocationReference) reference; String message = "A connection to " + connection.route().address().url() + " was leaked. Did you forget to close a response body?"; Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace); references.remove(i); connection.noNewStreams = true; // If this was the last allocation, the connection is eligible for immediate eviction. if (references.isEmpty()) { connection.idleAtNanos = now - keepAliveDurationNs; return 0; }}return references.size(); } Copy the code
conclusion
The use of link pooling focuses on three methods, in layman’s terms: put puts exhausted links into the connection pool, and checks if there are any expired links in the pool that need to be cleaned up. Get refers to taking out link reuse from the connection pool. Generally speaking, it needs to meet the same starting point of the line, the line can not be overcrowded, otherwise it is not allowed to reuse from the connection pool. Cleanup refers to looking for expired roads from the link pool, and the method is to see which road has not been used for the longest time. During the search, it is possible that some of the roads have errors in recording the trunk (memory leaks), which are then fixed during the search.