OkHttp 라이브러리는 "Connection Pooling" 기능을 제공한다. Connection Pooling 기능을 이용하면 동일한 URL로의 커넥션을 풀링하여 다음번 요청때 재사용하게 된다. 커넥션을 새로 맺는 동작은 짧지만 무시할 수 없는 오버헤드가 될 수 있다.
OkHttp를 사용할 때 OkHttpClient 객체를 생성했다.
OkHttpClient client = new OkHttpClient();
OkHttpClient() 생성자의 내부를 따라가다보면 ConnectionPool() 이라는 객체를 만나게된다.
1 2 3 | public ConnectionPool() { this(5, 5L, TimeUnit.MINUTES); } | cs |
이 Client 객체를 사용하여 여러 REST API에 요청을 보내면 5개의 Connection Pool을 만들어 관리한다. 같은 주소로의 요청은 새로운 커넥션을 맺지 않고 ConnectionPool에 유지되어 있는 커넥션을 사용하게 된다.
ConnectionPool(5, 5L, TimeUnit.MINUTES) 생성자의 첫 번째 인자는 최대 몇 개의 커넥션을 유지할 것인지를 의미한다. 두 번째 인자는 얼마동안 커넥션을 유지할 것인지에 대한 내용이다. 마지막 인자는 두 번째 인자의 단위를 의미한다. 위에서 본 예제에서는 5분(TimeUnit.MINUTES)을 의미한다.
기본적으로 이런 커넥선 풀링을 지원해줘서 잘 모르고 사용해도 어느정도는 효율적으로 동작하게 된다. 하지만 OkHttpClient 객체를 매번 만들어 사용하는 경우에는 문제가 될 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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(); } } | cs |
GET 요청을 날리는 메소드인 getUserInfo() 안에서 OkHttpClient 객체를 매번 만든다고 하자. 매번 호출되는 getUserInfo() 메소드에서는 매번 동일한 URL로 요청을 보내지만 매번 OkHttpClient 객체가 새로 만들어지기 때문에 커네션은 공유되지 않는다. getUserInfo 메소드를 10000번 호출했으므로 결과적으로 커넥션은 10000개가 만들어진다.
운영체제의 설정에 따라 가용한 fd 개수가 모두 소모될 수 있고 일부 시스템에서는 시스템 장애가 생길 수도 있다. (일부 macOS 버전에서 테스트를 했을 때 시스템이 다운되는 경험까지 했었다.)
이 코드는 ConnectionPool과 OkHttpClient.Builder 를 이용해서 다음 코드처럼 바꿀 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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 값 등을 잘 조절해서 사용하도록 하자.
댓글