Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyでの実行の確認、失敗の再試行、および例外の再発生

発生した例外は、問題が発生したときに代替コードパスを実行するためにレスキューできますが、例外を処理する方法は他にもあります。今回のAppSignalAcademyでは、再試行について説明します。 およびensure キーワード、およびレスキューされた例外の再発生について見ていきます。

信頼性の低いWebAPIと通信しているとしましょう。たまにダウンすることを除けば、リクエストが数秒かかるほど遅いです。私たちのライブラリはこのAPIに依存しているため、可能な限り復元力を高める必要があります。

確認

ensure キーワードは確認に使用されます 例外が発生した場合でも、コードのブロックが実行されます。

私たちのライブラリでは、TCP接続が Net ::HTTP.startによって開かれていることを確認したいと思います。 たとえば、リクエストがタイムアウトしたために失敗した場合でも、は閉じられます。これを行うには、最初にリクエストを beginでラップします /確認 /終了 ブロック。 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を削除できます。 。興味深いことに、ensureブロックは、これがNet::HTTP自体に実装される方法でもあります。

再試行

再試行 キーワードを使用すると、ブロック内のコードの一部を再試行できます。 レスキューと組み合わせる ブロックすると、接続を開くことができなかった場合、またはAPIの応答に時間がかかりすぎる場合に、これを使用して再試行できます。

そのために、 read_timeoutを追加します Net ::HTTP.startへ タイムアウトを10秒に設定する呼び出し。それまでにリクエストへの応答がない場合は、 Net ::ReadTimeoutが発生します。 。

Errno ::ECONNREFUSEDにも一致します APIが完全にダウンしていることを処理します。これにより、TCP接続を開くことができなくなります。その場合、 http 変数はnilです 。

例外は救出され、再試行 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のプレッシャーを取り除くことができます。そのために、 retriesで行った再試行回数を追跡します。 変数。

タイムアウトが発生するたびに、最大で3回の再試行を再試行するため、数値をインクリメントして3以下かどうかを確認します。その場合は、再試行します 。そうでない場合は、 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

再試行を使用する 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>'

コードが放棄されて最後のエラーが再発生する前に、リクエストが3回再試行されます。その後、エラーを1レベル上で処理するか、APIの応答なしにアプリがジョブを完了できない場合はアプリをクラッシュさせることができます。

復元力のあるWebAPIクライアント

これらのメソッドを組み合わせることで、約20行のコードで復元力のあるWebAPIクライアントを構築しました。ダウンしている場合や応答がない場合はリクエストを再試行し、再度起動しない場合はあきらめます。

例外の処理について何か新しいことを学び、この記事(またはAppSignal Academyシリーズの他の記事)についてどう思ったかを知りたいと思います。ご意見をお聞かせください。または、Rubyのテーマについて詳しく知りたい場合は、遠慮なくお知らせください。


  1. Rubyで例外にコンテキストデータを追加する方法

    標準のバックトレース/エラーメッセージの組み合わせでは不十分な場合があります。エラーの原因を特定するために、追加のデータが必要になる場合があります。幸い、Rubyで行うのはとても簡単です。 エラーメッセージのカスタマイズ エラーにコンテキスト情報を追加する最も簡単な方法は、それを例外のメッセージに追加することです。以下の例では、例外をキャッチし、新しいメッセージで再発生させています: begin raise foo rescue => e raise e.class, bar end # RuntimeError: bar このアプローチの良い使用例は、テンプレートをレン

  2. LoggerとLogrageを使用してRubyにログインする

    Rubyでのログの操作 ロギングは、アプリケーションが通常対処する主要なタスクの1つです。ログは、たとえば、必要なときに使用されます アプリ内で何が起こっているかを確認します それらを監視する、または 特定のデータの指標を収集します。 新しいプログラミング言語を学ぶとき、情報を記録するための最初の明白な選択は、ネイティブメカニズムです。通常、それは簡単で、文書化されており、コミュニティ全体に広く行き渡っています。 ログデータは、使用している会社、ビジネス、アプリケーションの種類によって大きく異なります。したがって、あなたとあなたのチームが選択したロギングソリューションがその全体的な使