티스토리 뷰
1. Overview
Spring Framework에서 RestTemplate 사용 시 설정 관련 내용입니다.
Connection Pool과 Timeout 설정 등에 관해 찾아 보다 좋은 글이 있어 정리해 보았습니다. 원본글이 설명은 디테일합니다.
원본글
Troubleshooting Spring's RestTemplate Requests Timeout
- https://tech.asimio.net/2016/12/27/Troubleshooting-Spring-RestTemplate-Requests-Timeout.html
개발 환경
- Spring boot 2.1.x
- java 8
2. 개발하기
작업 내용 : 코드는 대부분 원본 글 참고 했습니다.
2-1. Rest 서비스 프로젝트 생성
2-2. Client 서비스 프로젝트 생성
2-3. application.properties에 필요한 설정 값 추가
2-4. 설정 값을 매핑할 클래스 생성
2-5. RestTemplate Configuration 생성
2-6. RestTemplate Util 생성
2-7. 테스트용 컨트롤러 생성
2-1. Rest 서비스 프로젝트 생성
Rest 서비스는 딱히 할게 없습니다. Spring Starter Project로 생성합니다. 생성 시 옵션에서 'web' 하나 만 선택하면 됩니다.
생성 후 @RestController로 컨트롤러 하나 추가하시면 끝 ~~~
1 2 3 4 5 6 7 8 9 10 11 12 13 | @RestController public class DemoController { @GetMapping("/demo") public String test() { try { Thread.sleep(2800); } catch (InterruptedException e) { e.printStackTrace(); } return "demo"; } } | cs |
테스트를 위해 sleep을 좀 여유있게 잡아 줍니다.
이렇게 하고 바로 실행하면 8080 포트로 바로 실행이 됩니다.
2-2. Client 서비스 프로젝트 생성
프로젝트 생성은 위와 동일합니다. 그외 사용하는 라이브러리를 위해 build.gradle을 수정합니다.
1 2 3 4 5 6 7 8 9 | dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.apache.httpcomponents:httpclient:4.5.7' implementation 'commons-collections:commons-collections' implementation 'org.springframework.boot:spring-boot-configuration-processor' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' testImplementation 'org.springframework.boot:spring-boot-starter-test' } | cs |
2-3. application.properties에 필요한 설정 값 추가
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # server 설정 server.port=8083 server.contextPath=/ # httpConnPool 설정 # maxTotal httpconnpool.max-total=20 # 라우트 당 최대 값 설정 httpconnpool.default-max-per-route=20 # 최대 pending 값, pending이 일정 수준 이상 쌓이면 서비스를 제한 하기 위한 용도 httpconnpool.max-pending=50 # Time out 설정 httpconnpool.connection-request-timeout=3000 httpconnpool.connection-timeout=3000 httpconnpool.socket-timeout=3000 # 사이트별 최대 값 관리 httpconnpool.max-per-routes[0].scheme=http httpconnpool.max-per-routes[0].host=localhost httpconnpool.max-per-routes[0].port=8080 httpconnpool.max-per-routes[0].max-per-route=20 | cs |
새로운 사이트 추가 시
httpconnpool.max-per-routes[1].scheme=http
httpconnpool.max-per-routes[1].host=xxx.xxx.com
httpconnpool.max-per-routes[1].port=80
httpconnpool.max-per-routes[1].max-per-route=20
2-4. 설정 값을 매핑할 클래스 생성
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 | @Configuration @PropertySource("classpath:application.properties") @ConfigurationProperties(prefix = "httpconnpool") @Data @ToString public class HttpHostsConfiguration { private Integer maxTotal; private Integer defaultMaxPerRoute; private Integer maxPending; private Integer connectionRequestTimeout; private Integer connectionTimeout; private Integer socketTimeout; private List<HttpHostConfiguration> maxPerRoutes; @Data @ToString public static class HttpHostConfiguration { private String scheme; private String host; private Integer port; private Integer maxPerRoute; } } | cs |
2-5. RestTemplate Configuration 생성
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | @Configuration public class RestTemplateConfiguration { private HttpHostsConfiguration httpHostConfiguration; @Autowired public void setHttpHostsConfiguration(HttpHostsConfiguration httpHostConfiguration) { this.httpHostConfiguration = httpHostConfiguration; } @Bean public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() { PoolingHttpClientConnectionManager result = new PoolingHttpClientConnectionManager(); result.setMaxTotal(this.httpHostConfiguration.getMaxTotal()); // Default max per route is used in case it's not set for a specific route result.setDefaultMaxPerRoute(this.httpHostConfiguration.getDefaultMaxPerRoute()); // and / or if (!CollectionUtils.isEmpty(this.httpHostConfiguration.getMaxPerRoutes())) { for (HttpHostConfiguration httpHostConfig : this.httpHostConfiguration.getMaxPerRoutes()) { HttpHost host = new HttpHost(httpHostConfig.getHost(), httpHostConfig.getPort(), httpHostConfig.getScheme()); // Max per route for a specific host route result.setMaxPerRoute(new HttpRoute(host), httpHostConfig.getMaxPerRoute()); } } return result; } @Bean public RequestConfig requestConfig() { return RequestConfig.custom() .setConnectionRequestTimeout(httpHostConfiguration.getConnectionRequestTimeout()) .setConnectTimeout(httpHostConfiguration.getConnectionTimeout()) .setSocketTimeout(httpHostConfiguration.getSocketTimeout()) .build(); } @Bean public CloseableHttpClient httpClient() { return HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager()) .setDefaultRequestConfig(requestConfig()).build(); } @Bean public RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient()); return new RestTemplate(requestFactory); } } | cs |
2-6. RestTemplate Util 생성
pending 카운터를 가져와서 체크하는 기능과 로그를 보여 주는 기능을 별도로 분리했습니다.
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | @Component @Slf4j public class RestTemplateUtil { private PoolingHttpClientConnectionManager poolingHttpClientConnectionManager; @Autowired public void setPoolingHttpClientConnectionManager( PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) { this.poolingHttpClientConnectionManager = poolingHttpClientConnectionManager; } private HttpHostsConfiguration httpHostConfiguration; @Autowired public void setHttpHostConfiguration(HttpHostsConfiguration httpHostConfiguration) { this.httpHostConfiguration = httpHostConfiguration; } public boolean checkPending() { PoolStats totalStats = poolingHttpClientConnectionManager.getTotalStats(); int pendingCount = totalStats.getPending(); log.debug("RestTemplateMonitor.pendingCount : "+pendingCount); if(pendingCount > httpHostConfiguration.getMaxPending()) { log.debug("RestTemplateMonitor.checkPending : false"); return false; } log.debug("RestTemplateMonitor.checkPending : true"); return true; } public String createHttpInfo() { StringBuilder sb = new StringBuilder(); sb.append("=========================").append("\n"); sb.append("General Info:").append("\n"); sb.append("-------------------------").append("\n"); sb.append("MaxTotal: ").append(poolingHttpClientConnectionManager.getMaxTotal()).append("\n"); sb.append("DefaultMaxPerRoute: ").append(poolingHttpClientConnectionManager.getDefaultMaxPerRoute()).append("\n"); sb.append("ValidateAfterInactivity: ").append(poolingHttpClientConnectionManager.getValidateAfterInactivity()).append("\n"); sb.append("=========================").append("\n"); PoolStats totalStats = poolingHttpClientConnectionManager.getTotalStats(); sb.append(createPoolStatsInfo("Total Stats", totalStats)); Set<HttpRoute> routes = poolingHttpClientConnectionManager.getRoutes(); if (routes != null) { for (HttpRoute route : routes) { sb.append(createRouteInfo(poolingHttpClientConnectionManager, route)); } } return sb.toString(); } private String createRouteInfo(PoolingHttpClientConnectionManager connectionManager, HttpRoute route) { PoolStats routeStats = connectionManager.getStats(route); return createPoolStatsInfo(route.getTargetHost().toURI(), routeStats); } private String createPoolStatsInfo(String title, PoolStats poolStats) { StringBuilder sb = new StringBuilder(); sb.append(title + ":").append("\n"); sb.append("-------------------------").append("\n"); if (poolStats != null) { sb.append("Available: ").append(poolStats.getAvailable()).append("\n"); sb.append("Leased: ").append(poolStats.getLeased()).append("\n"); sb.append("Max: ").append(poolStats.getMax()).append("\n"); sb.append("Pending: ").append(poolStats.getPending()).append("\n"); } sb.append("=========================").append("\n"); return sb.toString(); } } | cs |
2-7. 테스트용 컨트롤러 생성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @RestController @Slf4j public class DelegatorController { @Autowired private RestTemplate restTemplate; @Autowired private RestTemplateUtil restTemplateUtil; @GetMapping("/delegate/demo") public String getDemoDelegate() { // 화면에 모니터링 로그 남기기 log.info(restTemplateUtil.createHttpInfo()); try { return this.restTemplate.getForObject("http://localhost:8080/demo", String.class); }catch(Exception e) { // 화면에 모니터링 로그 남기기 log.error(restTemplateUtil.createHttpInfo()); return e.getMessage(); } } } | cs |
pending count로 요청이 일정 수 이상 쌓이면 차단한다.
3. 마무리
원본 글 보면서 따라하기를 한번 시전해 봤습니다.
application.properties에 값을 객체에 바인딩하는 부분이랑 Pool 사용하는 부분 그외 상태를 확인하는 부분 등 쓸만한 부분이 많이 있었던거 같습니다.
Github 링크 : https://github.com/kkaok/examples
'Spring Frameworks' 카테고리의 다른 글
Spring framework legacy 시스템 redis 연동 예제 (0) | 2020.01.03 |
---|---|
Spring boot - API Gateway, Zuul 예제 (0) | 2019.03.17 |
Spring boot - Assert 사용 예제 (0) | 2019.03.05 |
Spring boot - Object Mapper (0) | 2019.03.04 |
Spring boot - Ehcache, cache 예제 (0) | 2019.02.26 |
- Total
- Today
- Yesterday
- SHEETJS
- REST
- lombok
- 그리드
- Javascript
- oracle
- 샘플
- 예제
- listToMap
- mybatis
- UI
- 스프링
- example
- cache
- spring
- 메시지
- thymeleaf
- 스프링부트
- sample
- ag grid
- mapToList
- java
- Spring Boot
- AG-GRID
- springboot
- 설정
- 엑셀
- restful서비스
- 타임리프
- RESTful
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |