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のテーマについて詳しく知りたい場合は、遠慮なくお知らせください。
-
Rubyで例外にコンテキストデータを追加する方法
標準のバックトレース/エラーメッセージの組み合わせでは不十分な場合があります。エラーの原因を特定するために、追加のデータが必要になる場合があります。幸い、Rubyで行うのはとても簡単です。 エラーメッセージのカスタマイズ エラーにコンテキスト情報を追加する最も簡単な方法は、それを例外のメッセージに追加することです。以下の例では、例外をキャッチし、新しいメッセージで再発生させています: begin raise foo rescue => e raise e.class, bar end # RuntimeError: bar このアプローチの良い使用例は、テンプレートをレン
-
LoggerとLogrageを使用してRubyにログインする
Rubyでのログの操作 ロギングは、アプリケーションが通常対処する主要なタスクの1つです。ログは、たとえば、必要なときに使用されます アプリ内で何が起こっているかを確認します それらを監視する、または 特定のデータの指標を収集します。 新しいプログラミング言語を学ぶとき、情報を記録するための最初の明白な選択は、ネイティブメカニズムです。通常、それは簡単で、文書化されており、コミュニティ全体に広く行き渡っています。 ログデータは、使用している会社、ビジネス、アプリケーションの種類によって大きく異なります。したがって、あなたとあなたのチームが選択したロギングソリューションがその全体的な使