Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

รับรองการดำเนินการ การลองล้มเหลวอีกครั้ง และการเพิ่มข้อยกเว้นใน Ruby

ข้อยกเว้นที่เพิ่มขึ้นสามารถได้รับการช่วยเหลือเพื่อรันเส้นทางรหัสทางเลือกเมื่อมีสิ่งผิดปกติ แต่มีหลายวิธีในการจัดการข้อยกเว้น ใน AppSignal Academy ฉบับนี้ เราจะพูดถึง retry และ ensure คีย์เวิร์ด และเราจะพิจารณาการเพิ่มข้อยกเว้นที่ได้รับการช่วยเหลือ

สมมติว่าเรากำลังสื่อสารกับ Web API ที่ไม่น่าเชื่อถือ นอกจากจะหยุดทำงานเป็นระยะ ๆ มันช้ามากจนอาจใช้เวลาไม่กี่วินาที ห้องสมุดของเราขึ้นอยู่กับ API นี้ และเราจำเป็นต้องทำให้มีความยืดหยุ่นมากที่สุด

ensure

ensure คีย์เวิร์ดใช้สำหรับ มั่นใจ บล็อกของรหัสทำงานแม้ว่าจะมีข้อยกเว้นเกิดขึ้น

ในไลบรารีของเรา เราต้องการให้แน่ใจว่าการเชื่อมต่อ TCP เปิดโดย Net::HTTP.start ถูกปิด แม้ว่าคำขอจะล้มเหลวเพราะหมดเวลา เป็นต้น ในการทำเช่นนี้ ก่อนอื่นเราจะรวมคำขอของเราเป็น begin /ensure /end บล็อก. รหัสใน ensure ส่วนหนึ่งจะทำงานเสมอ แม้ว่าจะมีข้อยกเว้นใน begin . ก่อนหน้า บล็อก

ใน ensure บล็อก เราจะปิดการเชื่อมต่อ TCP โดยเรียก Net::HTTP#finish เว้นแต่ 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 . ที่น่าสนใจคือ บล็อก sure นั้นเป็นวิธีที่นำไปใช้ใน 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 ถูกยกขึ้น

$ 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 ตัวแปร

เมื่อใดก็ตามที่หมดเวลา เราจะเพิ่มจำนวนและตรวจสอบว่ามีค่าน้อยกว่าหรือเท่ากับสามหรือไม่ เนื่องจากเราต้องการลองใหม่อีกครั้งไม่เกินสามครั้ง ถ้าใช่ เราจะ 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 ที่ยืดหยุ่น

ด้วยการรวมวิธีการเหล่านี้ เราได้สร้างไคลเอ็นต์ API ของเว็บที่ยืดหยุ่นได้ในโค้ดประมาณ 20 บรรทัด จะลองส่งคำขออีกครั้งหากระบบหยุดทำงานหรือไม่ตอบสนอง และเราจะยกเลิกเมื่อไม่กลับมาทำงานอีก

เราหวังว่าคุณจะได้เรียนรู้สิ่งใหม่ๆ เกี่ยวกับการจัดการข้อยกเว้น และอยากทราบว่าคุณคิดอย่างไรกับบทความนี้ (หรือบทความอื่นๆ ในซีรี่ส์ AppSignal Academy) โปรดอย่าลังเลที่จะแจ้งให้เราทราบว่าคุณคิดอย่างไร หรือหากคุณมีวิชา Ruby ที่ต้องการเรียนรู้เพิ่มเติม