4

我只想从我的 Rails 控制器内部访问服务器,而不是等待响应。这可能吗?(在不启动其他线程的情况下,出于性能原因我不能这样做)

4

6 回答 6

6

可以通过打开套接字并关闭它来实现。这将建立一个连接并关闭连接,而不在连接的上下文中传输任何数据......

...您将需要等待连接打开 - 尽管可能有办法避免这种情况。

require 'socket'
# opens a connection, will wait for connection but not for data.
s = TCPSocket.new 'host.url.com', 80
# closes the connection
s.close

它可能相当于一个 ping 并且不会打开一个新线程......虽然,它不是完全异步的。

对于 HTTP 请求,代码可能如下所示:

require 'socket'
host = 'www.google.com'
# opens a connection, will wait for connection but not for data.
s = TCPSocket.new host, 80
# send a GET request for '/' .
s.puts "GET / HTTP/1.1\r\nHost: #{host}\r\n\r\n"
# closes the connection
s.close

您可以在堆栈交换上搜索有关 HTTP 请求的更多信息并获得一些想法,例如此处

只是为了澄清(由于评论):

这将引入与建立连接(和发送请求)相关的延迟,但您不必等待回复被处理和接收。

断开连接(关闭你的一半套接字)可能会产生以下任何效果 - 所有这些都假设一个不错的 Web 服务器:

  • 如果s.close在 Web 服务器完全发送响应之前完成,则 Web 服务器将首先处理请求,然后在尝试发送数据时在 Web 服务器的套接字上引发异常。Web 服务器然后应该关闭套接字并释放所有资源。

  • 如果s.close在 Web 服务器完全发送响应之后完成,则服务器可能:1. 立即关闭套接字(正常的 HTTP 1 行为)或 2. 保持连接活动直到发生超时(可选的 HTTP 1.1 行为) -超时通常约为 10 秒。

以非常小的间隔重复访问 Web 服务器可能会导致 DOS 安全标志被触发并阻止未来的连接(无论您如何访问 Web 服务器都是如此)。

我可能会选择使用工作线程,如下所示:

我相信运行一个单独的线程可能没有你想象的那么昂贵。对于所有异步 Web 请求,可能有一个线程周期。

这是一个想法:

require 'socket'

REQUESTS_MUTEX = Mutex.new
REQUESTS_QUE = []
REQUESTS_THREAD = Thread.new do
   begin
      loop do
         sleep 0.5 while REQUESTS_QUE.empty?
         host, path = REQUESTS_MUTEX.synchronize {REQUESTS_QUE.shift}
         # the following will open a connection and start a request,
         # but it's easier to use the built in HTTP API...
         # although it will wait for a response. 
         s = TCPSocket.new host, 80
         s.puts "GET #{path} HTTP/1.1\r\nHost: #{host}\r\n\r\n"
         s.close
         # log here: 
         puts "requested #{path} from #{host}."
      end
   rescue Exception => e
      retry
   end
end
def asynch_request host, path = '/'
   REQUESTS_MUTEX.synchronize {REQUESTS_QUE << [host, path]}
   REQUESTS_THREAD.alive?
end

现在,对于每个请求,您可以简单地调用asynch_request,并且循环线程应该在它醒来并注意到队列后立即访问 Web 服务器。

您可以通过粘贴一些请求从终端测试它:

asynch_request 'www.google.com'
asynch_request 'www.yahoo.com'
asynch_request 'I.Dont.exist.com'
asynch_request 'very bad host address...'
asynch_request 'www.github.com'

注意静默失败(您可以调整代码)。

于 2015-04-07T03:57:04.890 回答
3

从您的控制器中,将 request-url 作业添加到队列中。

然后运行从队列中读取并执行请求的后台进程。

这将从您的控制器操作中消除请求性能延迟。

Rails 4.2 包含一种从特定后端实现中抽象出来的方法。它被称为 ActiveJob:

https://github.com/rails/rails/tree/master/activejob

这是将其与 Sidekiq 服务一起使用的示例:

https://github.com/mperham/sidekiq/wiki/Active-Job

如果您使用的是旧版本的 Rails,您也可以直接使用其中一种队列服务。

于 2015-04-06T23:07:40.753 回答
2

这是可能的,但你需要使用ruby​​ eventmachine

然后你可以使用em-http-request来执行异步 http 请求,即:

首先安装宝石

gem install 'eventmachine'
gem install 'em-http-request'

然后试试代码

require 'rubygems'
require 'eventmachine'
require 'em-http'

urls = %w(http://www.google.com http://www.rorra.com.ar)

pending = urls.size

EM.run do
  urls.each do |url|
    http = EM::HttpRequest.new(url).get
    http.callback {
      puts "#{url}\n#{http.response_header.status} - #{http.response.length} bytes\n"
      puts http.response

      pending -= 1
      EM.stop if pending < 1
    }
    http.errback {
      puts "#{url}\n" + http.error

      pending -= 1
      EM.stop if pending < 1
    }
  end
end
于 2013-10-21T19:25:35.060 回答
1

如果创建一个新进程是可以的(不是整个 rails 进程介意你),你可以使用以下几行:Executing shell command in background from ruby​​ with proper argument escaping

# Spawn a new process and run the curl command
pid = Process.spawn("curl", "http://example.com", :out =>    '/dev/null', :err => '/dev/null')

# Detach the spawned process
Process.detach pid

当我对此进行基准测试时,我得到了1.999ms. 与使用Process.wait pidwhich相比248ms

于 2015-04-08T15:34:17.450 回答
1

ruby 有几个不同的 HTTP 库。其中一些允许对 ruby​​ 的“异步”请求。虽然通常它会在另一个线程中。我认为您说出于性能原因不能这样做可能是不正确的。

HTTPClient是我首选的 HTTP 客户端库,尽管它不一定是最受欢迎的。使用 HTTPClient,您可以:

conn = HTTPClient.new.get_async(" http://example.com ")

通常,您会使用返回的连接对象检查请求何时完成,但您也可以忽略返回的连接对象。在任何一种情况下,实际的 HTTP 请求都是在一个单独的线程中发出的,因此您的主线程不会等待它并且可以继续执行其他代码。

其他 ruby​​ http 客户端库也提供异步模式。您也可以简单地通过启动一个发出 http 请求的线程来自己完成,如果您不关心它,甚至不需要等待线程完成。您可以使用concurrent-ruby中的一些工具来使用线程池和其他人已经编写的代码,以最大程度地减少对性能的担忧。也许是并发红宝石的未来。

如果您真的不想使用线程,那么您基本上必须按照其他人的建议使用 EventMachine。不过,我不会认为这一定会带来更好的性能。

有一些已编译的 C gem 可以让您在创建线程并不明显的情况下发出异步请求——但 C 代码仍可能在 C 领域创建线程。线程基本上是你可以做你想做的异步事情的方式。或者是复杂的基于事件/光纤的解决方案,比如 EventMachine,当然。

在 Rails 4.2 中,您可以使用 ActiveJob 将 HTTP 请求排队作为后台进程。这是启动 HTTP 请求但不让控制器等待它的另一种方法。但是您必须为 ActiveJob 设置后端(有多种选择),并且后端将在完全不同的进程中运行(可能不止一个).... 或者,后端将是为您创建线程。

我建议考虑放弃对线程的抵抗,线程确实是处理这个问题的好方法,不应该成为性能问题——我可能会使用并发 ruby​​,它给你的一些更高级别的抽象喜欢Futures(仍然在底层的线程方面实现),以避免自己直接编写线程代码,并使用由知道自己在做什么的其他人编写的库,并为任何性能问题合理设置所有内容. 或者,如果您真的想避免使用线程,我会使用 ActiveJob,它带有一个不使用线程的后端适配器。就个人而言,我不会采用 EventMachine 路线,它增加了很多要处理的事情,只是为了异步 http 请求。

或者,是的,只需提出一个 HEAD 请求,并认为它足够快,不用担心。

或者,当然,Myst 关于直接打开套接字以便您可以立即关闭它而无需等待响应的答案似乎很有趣。

于 2015-04-07T05:08:52.373 回答
0

我想到的第一件事是:也许以某种方式分离请求。不知道这是否可能与红宝石的请求......我知道你可以分离进程。

其他不是 100% 的解决方案是只询问标题,因此您只传输少量数据。这个线程似乎有一些很好的提示:https ://stackoverflow.com/a/9699874/1933185

于 2013-10-21T20:24:20.790 回答