我只想从我的Rails控制器内部命中服务器,但不等待响应。这可能吗? (没有启动其他线程,出于性能原因我不能这样做)
答案 0 :(得分:5)
可以打开一个插座并关闭它。这将建立连接并关闭连接,而不在连接的上下文中传输任何数据......
...你需要等待连接打开 - 尽管可能有办法避免这种情况。
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请求的更多信息,并获取一些想法,例如here。
只是澄清(由于评论):
这将引入与建立连接(和发送请求)相关的延迟,但您不必等待处理和接收回复。
删除连接(关闭套接字的一半)可能会产生以下任何影响 - 所有这些都假设一个不错的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'
请注意,无声失败(您可以调整代码)。
答案 1 :(得分: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,您也可以直接使用其中一个队列服务。
答案 2 :(得分: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
答案 3 :(得分:1)
ruby有几种不同的HTTP库。其中一些允许&#34;异步&#34;请求红宝石。虽然通常它会在另一个线程中。我认为你可能因为性能原因而无法做到这一点。
HTTPClient是我首选的HTTP客户端库,尽管它不是最受欢迎的。使用HTTPClient,您可以:
conn = HTTPClient.new.get_async(&#34; http://example.com&#34;)
通常,您可以使用返回的连接对象检查请求何时完成,但您也可以忽略返回的连接对象。在任何一种情况下,实际的HTTP请求都是在一个单独的线程中进行的,因此您的主线程不会等待它并且可以继续执行其他代码。
其他ruby http客户端库也提供异步模式。您也可以通过启动发出http请求的线程来自己完成,如果您不关心它,甚至不等待线程完成。您可以使用concurrent-ruby中的一些工具来使用线程池和其他人已编写的代码,以最大限度地减少对性能的担忧。也许是来自并发红宝石的未来。
如果你真的不想使用线程,那么你基本上必须像其他人建议的那样使用EventMachine。我不认为这会在一定程度上导致更好的表现。
有一些已编译的C gem可以让你创建异步请求,而你创建线程并不明显 - 但是C代码仍然可能在C盘中创建线程。线程基本上是你可以像你想做的那样做异步的事情。或者像EventMachine这样复杂的基于事件/光纤的解决方案。
在Rails 4.2中,您可以使用ActiveJob将HTTP请求排队为后台进程。这是启动HTTP请求但不让控制器等待它的另一种方法。但是你必须为ActiveJob设置一个后端(有几种选择),后端将在一个完全不同的进程中运行(可能不止一个)....或者再次,后端将是为你创建线程。
我建议考虑放弃你对线程的抵制,线程确实是处理这个问题的好方法,不应该是性能问题 - 我可能会使用concurrent-ruby,而且有些更高级别的抽象它给你像期货(它仍然是在引擎盖下的线程实现),以避免必须自己直接编写线程代码,并使用由其他知道他们正在做什么的人写的库并针对任何性能问题合理地设置一切。或者,如果你真的想要避免线程,我使用ActiveJob,使用不使用线程的后端适配器。就个人而言,我不会采用EventMachine路线,只是出于异步http请求的目的,它会添加很多东西来处理。
或者,是的,只是提出一个HEAD请求,并认为它足够快,不要担心它。
或者,当然,Myst的答案是关于直接打开一个套接字,这样你可以立即关闭它而不必等待响应似乎很有趣。
答案 4 :(得分:1)
如果制作一个新流程是正常的(并不是整个铁路流程让您关注),您可以使用以下内容: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 pid
248ms
进行比较
答案 5 :(得分:0)
在我脑海中浮现的第一件事是:或许以某种方式分离请求。不确定这是否可以在ruby中请求...我知道你可以分离进程。
其他,而不是100%,解决方案是只询问标题,因此您只需传输少量数据。这个帖子似乎有一些很好的提示:https://stackoverflow.com/a/9699874/1933185