Ruby 关于 ruby 的 timeout 超时控制

zxzllyj · 2015年11月11日 · 最后由 zxzllyj 回复于 2015年11月13日 · 6376 次阅读

先来一段看起来非常好使的代码:

def self.page_check
   pages = Pages.pages #读取配置文件中页面的地址
    pages.each do |page|
    begin
        timeout(30) do
          begin
            RestClient.get("http://#{@@domain}#{page}"){|respose|
            if respose.code != 200
              puts "页面检测失败,页面地址为:http://#{@@domain}#{page}  状态为#{respose.code}".colorize(:red)
            else
              puts "页面#{page}检测通过".colorize(:green)
            end
            }
        end
        end
      rescue TimeoutError
        puts "页面检测超时,页面地址为:http://#{@@domain}#{page}".colorize(:yellow)
    end
    end

这段代码的作用就是检查网站的页面,在更新时用来检查是不是所有的页面都可用。 看起来好给力的样子,有 http 状态码检查,有超时控制,似乎非常完美了,然而,这段代码跑起来经常因为超时错误而中断,是的,你没看错,超时!!!代码中明明有捕获超时异常并且处理的代码,但脚本却因为超时而中断 报的错误日志如下: 百思不得其解,然后使劲翻文档,翻官方解释,最后总结出 ruby 的 timeout 针对的是代码块,也就是说整个 timeout 作用范围的代码加起来只能运行设定的时常,超过了才会报错误,但是,如果说代码块中某一句在还没到设定时间时报了超时,那么异常还是异常,timeout 并不给予处理,比如本段代码中使用到了 restclient 这个 gem 包,他报的超时 timeout 并不会处理,而是如实的打印出来并终止程序,这也就是明明我们设定了 timeout 但还是报超时异常的原因 而对于这个问题的原因,结合官方文档,给出如下猜测:ruby 的 timeout 是通过线程实现的,他将设定的代码块弄到一个线程里去执行,然后本身暂停,等待设定的时间到比如 5 秒钟后取检测这个线程还在不在,如果还在就报超时并干掉这个线程,不在了就正常执行(注意,也可能是 timeout 是线程,代码块是主进程,这个观点来源于猜测,不具备权威性)。所以对于这段代码报超时的问题,我们不能再指望本身的 timeout,结合错误日志,很明显,超时的是 restclient,所以应该给 restclient 设定一个超时时间,然而,对 restclient 设定 timeout 是不现实的,因为 ruby 的 timeout 并不会管代码本身如何,他只会判断代码还有没有在执行,故只能在 restclient 上设定超时,当然,如何设定 restclient 的超时时间那是另一个问题了,本文讲的是 ruby 的 timeout,所以这里不再讲 restclient 的 timeout 当然,具体的指明 ruby 的 timeout 运行机制以及代码阅读我就不参合了。 所以,ruby 的 timeout 在有网络请求的时候就是一个坑啊!!! 好吧,我知道有点水,新人第一次发帖,如果帖子有问题麻烦管理员帮忙编辑调整一下

应该是这样吧?

rescue Timeout::Error

#1 楼 @huacnlee 我试过,然而捕获不到。。

RestClient 自己有 timeout 的参数,你就别自己去折腾 timeout 了

自带的 timeout 当然不会理解网络请求的细节, 不过它的实现方式确实如你所说, 开了两个线程, 一个负责超时后干掉另一个...我在想这种形式的实现在大量使用时会不会有性能问题

#3 楼 @ywencn 如果直接使用 RestClient.get 这种形式的话,是无法传递超时参数的,只能通过其他方法使用,或者直接重写一次 RestClient

#5 楼 @shinkxw 如果大量使用的话,我想肯定会,因为不管怎样,都会占用资源,总资源一定的情况下,timeout 占用的资源多了那么其他的肯定就少了,如果再来个高并发。啧啧。。。

应该是这样吧?

begin
  RestClient::Request.execute(method: :get, url: 'http://example.com/resource', timeout: 0.1)
rescue RestClient::RequestTimeout => e
  p 'timeout'
end

还有你说大量使用的话,都是一个 @@domain, 可以使用 keep-alive 减少 tcp 的握手次数,用线程提高效率

require 'net/http'
require 'uri'

def self.page_check
  pages = Pages.pages
  threads = []
  pages.each_slice(5) do |five_pages|
    five_pages.each do |page|
      threads << Thread.new {
        begin
          Net::HTTP.start(@@domain, 80) do |http|
            begin
              req = Net::HTTP::Get.new(URI("http://#{@@domain}#{page}"))
              response = http.request req
              if !respose.is_a?(Net::HTTPSuccess)
                puts "页面检测失败,页面地址为:http://#{@@domain}#{page}  状态为#{respose.code}".colorize(:red)
              else
                puts "页面#{page}检测通过".colorize(:green)
              end
            rescue Net::ReadTimeout
              puts "页面读取超时,页面地址为:http://#{@@domain}#{page}".colorize(:yellow)
            end
          end
        rescue Timeout::Error
          puts "页面连接超时,页面地址为:http://#{@@domain}#{page}".colorize(:yellow)
        end
      }
    end
  end
  threads.each(&:join)
end

可不可以改成异步请求?

#9 楼 @MrPasserby 并没有使用这个方法,因为这只是一个测试脚本,慢点跑无所谓,只要不出错就好。另外,这里确实可以使用线程,但没这个需求。。。

#8 楼 @MrPasserby 是的,应该是这样,不过我嫌这样写麻烦,重新写了 restclient 的模块,加入了这个参数,然后改了下名字,这样就可以如 restclient 一样用了。。。

#10 楼 @hmilym 可以,不过。。这个貌似不在本帖的讨论范围,本帖讨论的是这该死的 ruby 超时而不是线程、异步请求

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册