0

Apache HttpComponents의 HttpClient v4.5.3을 사용하여 HTTP 요청을 실행하는 스칼라 함수를 구현했습니다. 아이디어는 응답을 구문 분석하고 확인 (즉, 리턴 코드 200)인지 확인하는 것입니다. 그렇지 않은 경우 n 번 시도하십시오.스칼라 : Map 값의 메소드 실행을 복제합니다.

응답이 정상이면 모든 것이 예상대로 작동합니다. 로거 출력에서 ​​처음에는 HTTP 오류가 발생했을 때 재 시도가 예상대로 실행되었음을 알 수 있습니다. 그러나 n 번 실패한 경우 전체 프로세스를 한 번 더 다시 시작하는 것으로 보입니다.

import org.apache.http.client.methods.{CloseableHttpResponse, HttpPost, HttpUriRequest} 
[...] 

class SearchQuery { 
    [...] 
    def executeRequest(retries: Int = 2, delay: Int = 1): Option[SearchResponse] = { 
     val request: HttpUriRequest = makeRequest 
     val response: CloseableHttpResponse = config.httpClient.execute(request) 
     val searchResults: Option[SearchResponse] = parseResponse(response) 
     response.close() 
     if (searchResults.isEmpty && retries > 0) { 
     logger.warn(s"Failed to retrieve response from ${source}. " + 
      s"Retrying $retries more time(s) in $delay second(s)...") 
     Thread.sleep(delay * 1000) 
     executeRequest(retries - 1, delay) 
     } 
     else searchResults 
    } 
    [...] 
} 

있어서 makeRequest()는 HTTP 요청을 생성

은 요청을 실행하는 기능이다. config.httpClient은 재사용 가능한 HttpClient 인스턴스를 제공합니다.

재귀 호출 외에도 다음과 같이 executeRequests()이 호출됩니다. 이 아이디어는 호출자가 정의한 하나 또는 여러 개의 서버 (소스)가 호출을 병렬로 실행하는 것입니다. 그들은 enum 유형 Source으로 정의됩니다. queryFromfile()은 주어진 파일의 내용을 읽어 SearchQuery 객체를 생성 한 다음 executeRequest이 호출됩니다.

val sources: Set[Source] = Set(Source.1) 
val file: File = ... 
val responses: parallel.ParMap[Source, Option[SearchResponse]] = sources 
    .par 
    .map(source => (source, SearchQuery.queryFromFile(file, source))) 
    .toMap 
    .mapValues(query => query.executeRequest()) 

이 예제에서 executeRequest은 각 소스에 대해 한 번 호출되어야합니다. 하나의 소스가 잘 전달되고 다른 소스는 여기서 실패 할 것으로 예상됩니다.

로그에서

다음 인 IntelliJ 디버거를 사용

14:56:48.656 [scala-execution-context-global-15] WARN query.SearchQuery - Failed to retrieve response from source1. Retrying 2 more time(s) in 3 second(s)... 
14:57:07.136 [scala-execution-context-global-15] WARN query.SearchQuery - Failed to retrieve response from source1. Retrying 1 more time(s) in 3 second(s)... 
14:57:25.538 [ScalaTest-run-running-FileProcessorTest] ERROR process.FileProcessor - Failed to retrieve results for file 'XXX' from sources: source1. Continuing. 
14:57:40.933 [scala-execution-context-global-15] WARN query.SearchQuery - Failed to retrieve response from source1. Retrying 2 more time(s) in 3 second(s)... 
14:57:59.214 [scala-execution-context-global-15] WARN query.SearchQuery - Failed to retrieve response from source1. Retrying 1 more time(s) in 3 second(s)... 

는 논리는 n 개의 시도 후 라인 else searchResults에서 끝나는, 첫번째로 예상대로 보인다. 그러나 실제로 결과를 리턴하는 대신 executor는 if 절로, 재귀 호출 executeRequest(retries - 1, delay)으로 점프합니다.

는 업데이트 : 나는이 문제가 responses 발에 연속적인 작동에 의한했다 알아 낸 : 호출 중 하나가 실패하고 그랬다면 오류를 기록하는 경우 여기

val emptyResponses = responses.filter(_._2.isEmpty) 
if (emptyResponses.nonEmpty) 
    logger.error(
    s"Failed to retrieve results for '$fileName' from sources: " + 
     s"${emptyResponses.keys.mkString(",")}. Continuing." 
) 

, 나는 확인한다. 나는 이것이 왜 executeRequest() 두 번째 호출을 유발하는지 이해하지 못한다. 왜 그럴까요? 개념적으로 매우 유사한 것으로 보인다하더라도

은 또한, 다음 줄 executeRequest() 행 번째 통화가 발생하지 않는다 :

responses.values 
    .filter(response => response.isDefined) 
    .map(response => response.get) 
+0

주 :'filter' 대신'withFilter'를 사용하여 동작을 변경하지 마십시오. – Carsten

답변

0

값을 액세스 할 때 executeResponse()가 다른 시간에 실행되는 이유는 그 값 스칼라 맵은 액세스 할 때마다 다시 계산됩니다.

val responses = sources 
    .par 
    .map(source => (source, SearchQuery.queryFromFile(file, source))) 
    .map(pair => (pair._1, pair._2.executeRequest())) 

이것은 기본적 mapValues의 간결함을 제거하지만 튜플의 2 요소의 고정 된 값의 결과 responses 그러므로 튜플 (source, Option[SearchResponse]로 모델링 될 필요가있다.

+1

엄밀히 말하면 값은'map's 및'ParMap's에 대해서만 다시 계산되며'mapValues' 호출의 결과입니다. 다른 가능한 수정은'respond' 계산의 끝에'.toMap'을 하나 더 추가하는 것입니다. 이것은 결과의 "구체화"를 강요하므로 더 이상의 액세스에서 다시 계산되지 않습니다. – SergGr