http连接池请求损耗

97 阅读2分钟

模拟一个http server, 且能动态展示当前连接数

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", abc)
	server := &http.Server{
		Addr:           ":8000",
		Handler:        mux,
		ConnState:      connStateHandler,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		IdleTimeout:    10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	go func() {
		for {
			fmt.Println("当前活跃的连接数:", atomic.LoadInt32(&activeConnections))
			time.Sleep(1 * time.Second)
		}
	}()
	server.ListenAndServe()
}
func connStateHandler(conn net.Conn, state http.ConnState) {
	switch state {
	case http.StateNew:
		atomic.AddInt32(&activeConnections, 1)
	case http.StateActive:
	case http.StateIdle:
	case http.StateClosed:
		atomic.AddInt32(&activeConnections, -1)
	}
}
func abc(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html;charset=utf-8")
	w.Write([]byte("为了新中国,前进!"))
}

1 使用webclient,查看Connection不同取值的耗时

        System.setProperty("reactor.netty.ioWorkerCount", "8");
        WebClient client = webClient();
        int n = 10000;
        CountDownLatch latch = new CountDownLatch(n);
        long start = System.nanoTime();
        for (int i = 0; i < n; i++) {
            client.get()
                    .uri("/api/classmates")
                    .header("Connection", *sample*)
                    .retrieve()
                    .bodyToFlux(String.class)
                    .subscribe(v -> {

                        latch.countDown();
                    }, v -> {
                        System.out.println(v);
                        latch.countDown();
                    });

            latch.countDown();
        }
        latch.await();
        System.err.println((System.nanoTime() - start) / 1000000);
    public static WebClient webClient() {
        ConnectionProvider provider = ConnectionProvider.builder("pool")
                .maxConnections(50) // 自定义最大连接数
                .pendingAcquireMaxCount(-1) // 自定义挂起获取的最大数量
                .pendingAcquireTimeout(Duration.ofSeconds(100)) // 自定义挂起获取的超时时间
                .build();
        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(HttpClient.create(provider)))
                .baseUrl("http://127.0.0.1:8000")
                .build();
    }

运行10000个请求,总耗时结果如下

closekeep-alive
1462ms4435ms

2 使用java.net.http.HttpClient,对比WebClient

        System.setProperty("jdk.httpclient.connectionPoolSize", "50");
        HttpClient client = HttpClient.newHttpClient();
        int n = 1000;
        CountDownLatch latch = new CountDownLatch(n);
        long start = System.nanoTime();
        for (int i = 0; i < n; i++) {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("http://127.0.0.1:8000"))
                    .GET()
                    .build();
            client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                    .thenAccept(v -> latch.countDown()).exceptionally( v -> {
                        System.out.println(v);
                        latch.countDown();
                        return null;
                    });
        }
        latch.await();
        System.err.println((System.nanoTime() - start) / 1000000);

总耗时结果如下

HttpClientWebClient
764ms432ms
  • 在可用率上, HttpClient明显表现比较差, 有比较多的java.util.concurrent.CompletionException: java.net.ConnectException。相比WebClient是要差很多。经过分析, 找到了一部分原因, 目前设置System.setProperty("jdk.httpclient.connectionPoolSize", "5");起初认为不生效,进一步研究后, 发现设计上有所不同。

image.png

它只是returnToPool的时候, 会用到connectionPoolSize这个参数的判断。在执行过程中, 如果没有可用的connection, 它会并发的创新大量的连接,导致建连失败。从下图观测可得知

image.png

  • 参数的可控性上, HttpClient一些header是不能手动设置的,由内部产生。 包括它解析协议的I/O线程数, 也无法设置