Computer >> Máy Tính >  >> Lập trình >> Ruby

Đảm bảo thực thi, thử lại các lỗi và sắp xếp lại các ngoại lệ trong Ruby

Các ngoại lệ đã tăng có thể được cứu để thực thi một đường dẫn mã thay thế khi có sự cố, nhưng có nhiều cách hơn để xử lý các ngoại lệ. Trong ấn bản này của AppSignal Academy, chúng ta sẽ xem xét retryensure từ khóa và chúng tôi sẽ xem xét sắp xếp lại các trường hợp ngoại lệ đã giải cứu.

Giả sử chúng ta đang giao tiếp với một API web không đáng tin cậy. Bên cạnh việc thỉnh thoảng bị lỗi, nó chậm đến mức các yêu cầu đến nó có thể mất vài giây. Thư viện của chúng tôi phụ thuộc vào API này và chúng tôi cần làm cho nó linh hoạt nhất có thể.

ensure

ensure từ khóa được sử dụng để đảm bảo một khối mã chạy, ngay cả khi một ngoại lệ xảy ra.

Trong thư viện của mình, chúng tôi muốn đảm bảo kết nối TCP được mở bởi Net::HTTP.start bị đóng, ngay cả khi yêu cầu không thành công vì nó đã hết thời gian, chẳng hạn. Để thực hiện việc này, trước tiên chúng tôi sẽ kết thúc yêu cầu của mình trong một begin / ensure / end khối. Mã trong ensure phần sẽ luôn chạy, ngay cả khi một ngoại lệ được đưa ra trong begin trước khối.

Trong ensure chặn, chúng tôi sẽ đảm bảo đóng kết nối TCP bằng cách gọi Net::HTTP#finish trừ khi http biến là nil , điều này có thể xảy ra khi mở kết nối TCP không thành công (điều này cũng sẽ tạo ra một ngoại lệ).

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

Lưu ý :Chúng tôi đóng kết nối TCP theo cách thủ công để cho phép chúng tôi sử dụng kết nối khi thử lại sau. Tuy nhiên, vì Net::HTTP.start lấy một khối xử lý đảm bảo kết nối được đóng, mẫu trên có thể được viết lại để loại bỏ ensure . Điều thú vị là, khối ensure cũng là cách điều này được triển khai trong chính Net ::HTTP.

retry

retry từ khóa cho phép thử lại một đoạn mã trong một khối. Kết hợp với rescue chặn, chúng tôi có thể sử dụng nó để thử lại nếu chúng tôi không mở được kết nối hoặc nếu API mất quá nhiều thời gian để phản hồi.

Để làm điều đó, chúng tôi sẽ thêm một read_timeout vào Net::HTTP.start cuộc gọi đặt thời gian chờ thành 10 giây. Nếu đến thời điểm đó, phản hồi cho yêu cầu của chúng tôi không đến, nó sẽ tạo ra Net::ReadTimeout .

Chúng tôi cũng sẽ đối sánh trên Errno::ECONNREFUSED để xử lý API bị lỗi hoàn toàn, điều này sẽ ngăn chúng tôi mở kết nối TCP. Trong trường hợp đó, http biến là nil .

Ngoại lệ được giải cứu và retry được gọi để bắt đầu begin chặn lại, dẫn đến mã thực hiện cùng một yêu cầu cho đến khi không xảy ra thời gian chờ. Chúng tôi sẽ sử dụng lại http đối tượng giữ kết nối nếu nó đã tồn tại.

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

Bây giờ, yêu cầu của chúng tôi sẽ thử lại sau mỗi giây cho đến khi không có Net::ReadTimeout được nâng lên.

$ 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)

Mặc dù điều đó có thể đảm bảo rằng không có ngoại lệ nào được đưa ra cho bất kỳ thời gian chờ nào, nhưng thử đập lại nó như thế này chắc chắn sẽ không giúp ích cho việc khôi phục lại API đó. Đây là vấn đề vì mã này sẽ lặp lại mãi mãi nếu API vẫn không phản hồi. Thay vào đó, chúng ta nên thử nghiệm lại và từ bỏ sau một thời gian.

Từ bỏ:sắp xếp lại các trường hợp ngoại lệ bằng cách sử dụng raise

Khi một ngoại lệ được giải cứu, đối tượng ngoại lệ được nâng lên sẽ được chuyển đến rescue khối. Chúng tôi có thể sử dụng nó để trích xuất dữ liệu từ ngoại lệ, như in thông báo vào nhật ký, nhưng chúng tôi cũng có thể sử dụng nó để sắp xếp lại cùng một ngoại lệ, với cùng một dấu vết ngăn xếp.

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

Vì chúng tôi có quyền truy cập vào đối tượng ngoại lệ trong rescue , chúng tôi có thể ghi lỗi vào bảng điều khiển hoặc trình theo dõi lỗi. Trên thực tế, việc cứu và sắp xếp lại chính xác là cách tích hợp của AppSignal theo dõi lỗi.

Lưu ý :Ruby lưu trữ ngoại lệ được nêu ra cuối cùng trong một biến có tên $!raise từ khóa sẽ sử dụng nó theo mặc định. Gọi raise mà không có bất kỳ đối số nào sẽ sắp xếp lại ngoại lệ cuối cùng.

Trong thư viện của chúng tôi, chúng tôi có thể sử dụng sắp xếp lại để chịu áp lực của API sau một vài lần thử lại. Để làm điều đó, chúng tôi sẽ theo dõi số lần thử lại mà chúng tôi đã thực hiện trong retries biến.

Bất cứ khi nào hết thời gian chờ xảy ra, chúng tôi sẽ tăng số lượng và kiểm tra xem nó có nhỏ hơn hoặc bằng ba hay không, bởi vì chúng tôi muốn thử lại tối đa ba lần. Nếu vậy, chúng tôi sẽ retry . Nếu không, chúng tôi sẽ raise để đánh giá lại ngoại lệ cuối cùng.

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

Bằng cách sử dụng retries trong lệnh gọi sleep , chúng tôi có thể tăng thời gian chờ cho mỗi lần thử mới.

$ 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>'

Yêu cầu của chúng tôi được thử lại ba lần trước khi mã loại bỏ và sắp xếp lại lỗi cuối cùng. Sau đó, chúng tôi có thể xử lý lỗi khi lên cấp một hoặc làm hỏng ứng dụng của chúng tôi nếu ứng dụng không thể hoàn thành công việc mà không có phản hồi của API.

Ứng dụng khách API web linh hoạt

Bằng cách kết hợp các phương pháp này, chúng tôi đã xây dựng một ứng dụng khách API web linh hoạt trong khoảng hai mươi dòng mã. Nó sẽ thử lại các yêu cầu nếu nó không hoạt động hoặc không phản hồi và chúng tôi sẽ từ bỏ khi nó không hoạt động trở lại.

Chúng tôi hy vọng bạn đã học được điều gì đó mới mẻ về cách xử lý các ngoại lệ và rất muốn biết bạn nghĩ gì về bài viết này (hoặc bất kỳ bài nào khác trong loạt bài AppSignal Academy). Vui lòng cho chúng tôi biết suy nghĩ của bạn hoặc nếu bạn có bất kỳ môn học nào về Ruby mà bạn muốn tìm hiểu thêm.