발생한 예외는 상황이 잘못되었을 때 대체 코드 경로를 실행하기 위해 구출될 수 있지만 예외를 처리하는 더 많은 방법이 있습니다. 이번 버전의 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 주제에 대해 더 알고 싶은 것이 있으면 주저하지 말고 알려주세요.