Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby에서 실행 보장, 실패 재시도 및 예외 재발생

발생한 예외는 상황이 잘못되었을 때 대체 코드 경로를 실행하기 위해 구출될 수 있지만 예외를 처리하는 더 많은 방법이 있습니다. 이번 버전의 AppSignal Academy에서는 retry에 대해 알아보겠습니다. ensure 키워드를 살펴보고 구조된 예외를 다시 발생시키는 방법을 살펴보겠습니다.

신뢰할 수 없는 웹 API와 통신한다고 가정해 보겠습니다. 때때로 다운되는 것 외에는 너무 느려서 요청에 몇 초가 걸릴 수 있습니다. 우리 라이브러리는 이 API에 의존하며 최대한 탄력적으로 만들어야 합니다.

ensure

ensure 키워드는 보장에 사용됩니다. 예외가 발생하더라도 코드 블록이 실행됩니다.

우리 라이브러리에서 Net::HTTP.start에 의해 열린 TCP 연결을 확인하고 싶습니다. 예를 들어 시간 초과로 인해 요청이 실패하더라도 닫힙니다. 이를 위해 먼저 begin에서 요청을 래핑합니다. /ensure /end 차단하다. ensure의 코드 앞의 begin에서 예외가 발생하더라도 부분은 항상 실행됩니다. 차단합니다.

ensure에서 차단하려면 Net::HTTP#finish를 호출하여 TCP 연결을 닫습니다. http가 아닌 경우 변수는 nil입니다. , TCP 연결을 여는 데 실패할 수 있습니다(이 경우에도 예외가 발생함).

require "net/http"
 
begin
  puts "Opening TCP connection..."
  http = Net::HTTP.start(uri.host, uri.port)
  puts "Sending HTTP request..."
  puts http.request_get(uri.path).body
ensure
  if http
    puts "Closing the TCP connection..."
    http.finish
  end
end

참고 :나중에 재시도할 때 연결을 사용할 수 있도록 TCP 연결을 수동으로 닫습니다. 그러나 Net::HTTP.start 이후 연결이 닫혔는지 확인하는 블록을 사용하면 위의 샘플을 다시 작성하여 ensure를 제거할 수 있습니다. . 흥미롭게도 보장 블록은 이것이 Net::HTTP 자체에서 구현되는 방식이기도 합니다.

retry

retry 키워드를 사용하면 블록에서 코드 조각을 다시 시도할 수 있습니다. rescue와 결합 블록을 사용하여 연결을 열지 못하거나 API가 응답하는 데 너무 오래 걸리는 경우 다시 시도할 수 있습니다.

이를 위해 read_timeout을 추가합니다. Net::HTTP.start 시간 초과를 10초로 설정하는 호출입니다. 그때까지 요청에 대한 응답이 오지 않으면 Net::ReadTimeout이 발생합니다. .

Errno::ECONNREFUSED에서도 일치합니다. API가 완전히 다운되는 것을 처리하기 위해 TCP 연결을 열 수 없습니다. 이 경우 http 변수는 nil입니다. .

예외가 구조되고 retry합니다. begin을 시작하기 위해 호출됩니다. 다시 차단하면 시간 초과가 발생하지 않을 때까지 코드가 동일한 요청을 수행합니다. http를 재사용합니다. 이미 존재하는 경우 연결을 보유하는 개체입니다.

require "net/http"
 
http = nil
uri = URI("https://localhost:4567/")
 
begin
  unless http
    puts "Opening TCP connection..."
    http = Net::HTTP.start(uri.host, uri.port, read_timeout: 10)
  end
  puts "Executing HTTP request..."
  puts http.request_get(uri.path).body
rescue Errno::ECONNREFUSED, Net::ReadTimeout => e
  puts "Timeout (#{e}), retrying in 1 second..."
  sleep(1)
  retry
ensure
  if http
    puts "Closing the TCP connection..."
    http.finish
  end
end

이제 요청은 Net::ReadTimeout이 없을 때까지 1초마다 다시 시도합니다. 제기됩니다.

$ ruby retry.rb
Opening TCP connection...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 1 second...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 1 second...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 1 second...
Executing HTTP request...
... (in an endless loop)

이렇게 하면 시간 초과에 대해 예외가 발생하지 않을 수 있지만 이와 같이 다시 시도하면 해당 API를 다시 백업하는 데 확실히 도움이 되지 않습니다. API가 응답하지 않으면 이 코드가 계속 반복되기 때문에 문제가 됩니다. 대신 재시도를 분산하고 잠시 후 포기해야 합니다.

포기:raise를 사용하여 예외 다시 발생

예외가 구조되면 발생한 예외 개체가 rescue에 전달됩니다. 차단하다. 이를 사용하여 로그에 메시지를 인쇄하는 것과 같이 예외에서 데이터를 추출할 수 있지만 동일한 스택 추적으로 정확히 동일한 예외를 다시 발생시키는 데 사용할 수도 있습니다.

begin
  raise "Exception!"
rescue RuntimeError => e
  puts "Exception happened: #{e}"
  raise e
end

rescue에서 예외 개체에 액세스할 수 있기 때문에 블록에서 오류를 콘솔이나 오류 모니터에 기록할 수 있습니다. 실제로 AppSignal의 통합이 오류를 추적하는 방법은 바로 구출 및 재기성입니다.

참고 :Ruby는 마지막으로 발생한 예외를 $!라는 변수에 저장합니다. , 그리고 raise 키워드는 기본적으로 그것을 사용합니다. raise 호출 인수가 없으면 마지막 예외가 다시 발생합니다.

우리 라이브러리에서는 몇 번의 재시도 후에 API의 압력을 받기 위해 reraising을 사용할 수 있습니다. 이를 위해 retry에서 수행한 재시도 횟수를 추적합니다. 변수.

시간 초과가 발생할 때마다 최대 3번의 재시도를 다시 시도하기 때문에 횟수를 늘리고 3보다 작거나 같은지 확인합니다. 그렇다면 retry하겠습니다. . 그렇지 않은 경우 raise합니다. 마지막 예외를 다시 발생시킵니다.

require "net/http"
 
http = nil
uri = URI("https://localhost:4567/")
retries = 0
 
begin
  unless http
    puts "Opening TCP connection..."
    http = Net::HTTP.start(uri.host, uri.port, read_timeout: 1)
  end
  puts "Executing HTTP request..."
  puts http.request_get(uri.path).body
rescue Errno::ECONNREFUSED, Net::ReadTimeout => e
  if (retries += 1) <= 3
    puts "Timeout (#{e}), retrying in #{retries} second(s)..."
    sleep(retries)
    retry
  else
    raise
  end
ensure
  if http
    puts 'Closing the TCP connection...'
    http.finish
  end
end

retry 사용 sleep 호출의 변수 , 새로운 시도마다 대기 시간을 늘릴 수 있습니다.

$ ruby reraise.rb
Opening TCP connection...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 1 second(s)...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 2 second(s)...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 3 second(s)...
Executing HTTP request...
Closing the TCP connection...
/lib/ruby/2.4.0/net/protocol.rb:176:in `rbuf_fill': Net::ReadTimeout (Net::ReadTimeout)
...
from reraise.rb:13:in `<main>'

우리의 요청은 코드가 포기하고 마지막 오류를 다시 발생시키기 전에 세 번 재시도됩니다. 그런 다음 오류를 한 단계 위로 처리하거나 API의 응답 없이 작업을 완료할 수 없는 경우 앱을 크래시할 수 있습니다.

복원력 있는 웹 API 클라이언트

이러한 방법을 결합하여 약 20줄의 코드로 탄력적인 웹 API 클라이언트를 구축했습니다. 다운되거나 응답하지 않으면 요청을 다시 시도하고 다시 작동하지 않으면 포기합니다.

예외 처리에 대해 새로운 것을 배웠기를 바라며 이 기사(또는 AppSignal Academy 시리즈의 다른 기사)에 대해 어떻게 생각하는지 알고 싶습니다. 여러분의 생각이나 Ruby 주제에 대해 더 알고 싶은 것이 있으면 주저하지 말고 알려주세요.