본문 바로가기
Old Posts/Java

[Java] OkHttp 커넥션 풀(Connection Pool) 기능

by A6K 2021. 4. 22.

OkHttp 라이브러리는 '커넥션 풀(Connection Pool)' 기능을 제공한다. REST API 서버에 요청을 보낼 때, 매번 연결을 맺는 대신 커넥션 풀(Connection Pool) 기능을 이용해서 동일한 URL로의 커넥션을 풀링하여 다음번 요청때에 재사용하는 기능이다. 커넥션을 새로 빌드하는 동작은 그 하나로는 짧지만 경우에 따라서 무시할 수 없는 오버헤드로 작용할 수 있다.

OkHttp 커넥션 풀링 기능

OkHttp 클라이언트 객체를 생성할 때, 기본적으로 커넥션 풀링 기능이 적용되어 있다. 

OkHttpClient client = new OkHttpClient();

REST API를 이용하고자 할 때, OkHttpClient 객체를 생성했다. 이 생성자의 내부를 들어가보면 ConnectionPool 객체를 만나게 된다.

public ConnectionPool() {
    this(5, 5L, TimeUnit.MINUTES);
}

기본적으로 OkHttpClient 객체는 내부적으로 ConnectionPool 객체를 생성해서 사용한다. 5개의 커넥션을 관리하는 ConnectionPool을 두고 같은 OkHttpClient에서 같은 주소로의 요청은 새로운 커넥션을 맺지 않고 ConnectionPool에 유지되어 있는 커넥션을 사용하게 된다. 커넥션의 유지 시간은 5분으로 설정되어 있다. (ConnectionPool 생성자의 두 번째 인자는 시간, 세번째 인자는 두번째 인자의 단위를 의미하며 커넥션을 유지하는 시간을 나타낸다.)

문제는 구현 방법에 따라서 매번 OkHttpClient 객체를 만드는 경우에 발생할 수 있다. 다음 소스코드를 생각해보자.

public void getAll() {

    for (int i = 0; i < 10000; i++)
        getUserInfo(Integer.toString(i));
}

private void getUserInfo(String key) {

    try {
        String url = "http://127.0.0.1:8080/v1/information/get?key=" + key;

        OkHttpClient client = new OkHttpClient();

        Request.Builder builder = new Request.Builder().url(url).get();
        builder.addHeader("Password", "BlahBlah");

        Request request = builder.build();

        Response response = client.newCall(request).execute();
        if (response.isSuccessful()) {
            ResponseBody body = response.body();
            if (body != null) {
                System.out.println("Response:" + body.string());
            
            }
        }
        else
            System.err.println("Error Occurred");

    } catch(Exception e) {
        e.printStackTrace();
    }
}

10000번 루프를 돌면서 REST API에 요청을 보낸다. 이 때 매 요청마다 OkHttpClient 객체가 생성된다. 매번 생성되는 OkHttp 객체 안쪽에는 ConnectionPool이 들어있고, 요청을 보낼 때 생성된 연결이 5분동안 남아있게 된다. 결국 빠른시간동안 연결이 5만개가 생성된다.

문제는 5분동안 연결이 살아있기 때문에 운영체제 설정에 따라서 가용 fd 자원이 모두 소모될 수 있다는 점이다. 이와 비슷한 상황의 코드를 돌리다가 macOS가 뻗어버린 경험도 하게 되었다.;;;

이 코드는 기본적으로 OkHttpClient를 재사용하면된다. 혹은 다음과 같이 OkHttpClient 생성시 ConnectionPool 객체를 명시해주면 된다.

import okhttp3.*;

public class testOkHttp {

    public void getAll() {

        ConnectionPool connectionPool = new ConnectionPool();
        
        for (int i = 0; i < 10000; i++)
            getUserInfo(Integer.toString(i), connectionPool);
    }
    private void getUserInfo(String key, ConnectionPool connectionPool) {
        try {
            String url = "http://127.0.0.1:8080/v1/information/get?key=" + key;
            OkHttpClient client = 
new OkHttpClient.Builder().connectionPool(connectionPool).build();
            Request.Builder builder = new Request.Builder().url(url).get();
            builder.addHeader("Password", "BlahBlah");
            Request request = builder.build();
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                ResponseBody body = response.body();
                if (body != null) {
                    System.out.println("Response:" + body.string());

                }
            }
            else
                System.err.println("Error Occurred");
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

OkHttpClient.Builder에 ConnectionPool을 인자로 전달할 수 있다. 혹은 getAll() 메소드에서 OkHttpClient.Builder 객체를 만들어 놓고 매번 build()를 호출해서 써도 상관없다. 

ConnectionPool의 MaxIdleConnection 값과 KeepAlive 값 등을 잘 조절해서 사용하도록 하자.

댓글