【问题标题】:Making multiple HTTP requests asynchronously异步发出多个 HTTP 请求
【发布时间】:2011-01-08 20:34:00
【问题描述】:
require 'net/http'

urls = [
  {'link' => 'http://www.google.com/'},
  {'link' => 'http://www.yandex.ru/'},
  {'link' => 'http://www.baidu.com/'}
]

urls.each do |u|
  u['content'] = Net::HTTP.get( URI.parse(u['link']) )
end

print urls

此代码以同步方式工作。第一个请求,第二个,第三个。我想异步发送所有请求并在所有请求完成后打印urls

最好的方法是什么? Fiber 适合吗?

【问题讨论】:

    标签: ruby concurrency asynchronous fiber


    【解决方案1】:

    一年多之后我才看到这个,但希望对于某些 googler 来说还不算太晚...

    Typhoeus 迄今为止最好的解决方案。它以一种非常优雅的方式包装了 libcurl。您可以将max_concurrency 设置为高达 200 左右而不会窒息。

    关于超时,如果您向 Typhoeus 传递 :timeout 标志,它只会将超时注册为响应......然后您甚至可以将请求放回另一个 hydra 中,如果您愿意,可以再试一次。

    这是用 Typhoeus 重写的程序。希望这可以帮助以后遇到此页面的任何人!

    require 'typhoeus'
    
    urls = [
      'http://www.google.com/',
      'http://www.yandex.ru/',
      'http://www.baidu.com/'
    ]
    
    hydra = Typhoeus::Hydra.new
    
    successes = 0
    
    urls.each do |url|
        request = Typhoeus::Request.new(url, timeout: 15000)
        request.on_complete do |response|
            if response.success?
                puts "Successfully requested " + url
                successes += 1
            else
                puts "Failed to get " + url
            end
        end
        hydra.queue(request)
    end
    
    hydra.run 
    
    puts "Fetched all urls!" if successes == urls.length
    

    【讨论】:

      【解决方案2】:

      这是一个使用线程的示例。

      require 'net/http'
      
      urls = [
        {'link' => 'http://www.google.com/'},
        {'link' => 'http://www.yandex.ru/'},
        {'link' => 'http://www.baidu.com/'}
      ]
      
      urls.each do |u|
        Thread.new do
          u['content'] = Net::HTTP.get( URI.parse(u['link']) )
          puts "Successfully requested #{u['link']}"
      
          if urls.all? {|u| u.has_key?("content") }
            puts "Fetched all urls!"
            exit
          end
        end
      end
      
      sleep
      

      【讨论】:

      • 似乎可行。但是如果服务器在 15 秒后没有响应,如何杀死线程?
      • 您可以使用Timeout.timeotu(20) do .... end。但是,这会引发一个错误,因此您需要对程序的流程进行一些处理,并且除了检查 content 键是否存在之外,还有一种方法可以标记请求已完成。
      【解决方案3】:

      我已经写了一篇关于这个主题的深入博客文章,其中包含一个与 8 月发布的答案有些相似的答案 - 但有一些关键区别: 1) 跟踪“线程”数组中的所有线程引用。 2)在程序结束时使用“join”方法来捆绑线程。

      require 'net/http'
      
      # create an array of sites we wish to visit concurrently.
      urls = ['link1','link2','link3']  
      # Create an array to keep track of threads.
      threads = []
      
      urls.each do |u|  
        # spawn a new thread for each url
        threads << Thread.new do
        Net::HTTP.get(URI.parse(u))
          # DO SOMETHING WITH URL CONTENTS HERE
          # ...
          puts "Request Complete: #{u}\n"
        end
      end
      
      # wait for threads to finish before ending program.
      threads.each { |t| t.join }
      
      puts "All Done!"  
      

      完整教程(以及一些性能信息)可在此处获得:https://zachalam.com/performing-multiple-http-requests-asynchronously-in-ruby/

      【讨论】:

      • 当最佳答案获得最低票数时,您不讨厌它。
      【解决方案4】:

      这可以通过 C 库 cURL 来完成。该库的ruby binding 存在,但它似乎不支持开箱即用的此功能。但是,看起来有 a patch 添加/修复它(示例代码在页面上可用)。我知道这听起来不太好,但如果没有更好的建议,可能值得一试。

      【讨论】:

        【解决方案5】:

        concurrent-ruby的帮助下,您可以同时处理数据:

        require 'net/http'
        require 'concurrent-ruby'
        
        class Browser
          include Concurrent::Async
        
          def render_page(link)
            sleep 5
            body = Net::HTTP.get( URI.parse(link) )
            File.open(filename(link), 'w') { |file| file.puts(body)}
          end
        
          private
        
          def filename(link)
            "#{link.gsub(/\W/, '-')}.html"
          end
        end
        
        pages = [
          'https://www.google.com',
          'https://www.bing.com',
          'https://www.baidu.com'
        ].map{ |link| Browser.new.async.render_page(link) }.map(&:value)
        

        【讨论】:

          【解决方案6】:

          这取决于你想在函数之后做什么。你可以用简单的线程来做到这一点:

          见:http://snipplr.com/view/3966/simple-example-of-threading-in-ruby/

          【讨论】:

            【解决方案7】:

            您可以让不同的线程执行每个 Net::HTTP.get。然后等待所有线程完成。

            顺便说一句,打印网址将同时打印链接和内容。

            【讨论】:

              【解决方案8】:

              work_queue gem 是在应用程序中异步和并发执行任务的最简单方法。

              wq = WorkQueue.new 2 # Limit the maximum number of simultaneous worker threads
              
              urls.each do |url|
                wq.enqueue_b do
                  response = Net::HTTP.get_response(url)
                  # use the response
                end
              end
              
              wq.join # All requests are complete after this
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2016-11-15
                • 2018-04-10
                • 2023-03-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-09-04
                相关资源
                最近更新 更多