我正在构建一个服务,基本上允许用户与机器人聊天,然后机器人对用户发送的聊天进行一些奇怪的处理,并最终用一些有意义的数据回复。基本上类似于Aardvark使用(?(的工作方式。
我有机器人在工作,现在正在听,我有一个单独的rails应用程序,它将完成所有其他繁重的工作。这两个部分单独工作都很好,现在我只能将两者连接起来。我的想法是通过Resque将机器人程序(基本上是一个小的ruby脚本(与rails应用程序接口——任何进入的东西都会进入队列,被提取,然后结果再次推回到队列,然后脚本会回复结果。
我不太清楚如何建立这个接口:
- 我需要写一个rake任务来启动/停止/重新加载机器人吗
- 如果我在没有rake的情况下运行它(据说是由Monit监控的独立进程(,那么我如何与Resque接口或访问我的rails模型
我知道这些可能是非常琐碎的问题,但我很难理解哪一个更有效,以及如何进行设置。
Rails应用程序和这个机器人守护进程之间有三种通信方式:
- 通过将Rails应用程序作为HTTP请求调用(从Rails应用程序推送/提取数据(
- 通过与Rails应用程序使用的数据库直接交互(可能是Mysql/Postgres(
- 通过与Redis数据库支持的Resque工作队列系统交互
当您将Resque作业排入队列并从各种作业队列中取出时,您只是通过API读取/写入共享Redis数据库。机器人和Rails应用程序都通过网络与Redis DB进行对话。
我建议将bot直接作为由monit管理的ruby进程或rake任务运行。听起来你已经知道怎么做了。
我认为这里的主要问题是,您需要另一种消息传递解决方案(类似IPC,而不是IM(,而不是试图弯曲"只是"一个队列的Resque。其中一些选项是amqp-gem(amqp协议(或zmq-gem(ZeroMQ协议(,但您也可以通过Ruby标准库Socket类使用普通的旧UNIX套接字(很好的例子(。它们都有不同的优点和缺点,所以这取决于你和你的需求。
交互可能看起来像这样:
- Bot启动
- Bot开始侦听IPC消息
- Bot接收来自发送方的查询(通过XMPP(
- Bot通过Resque对作业进行排队
- Job通过HTTP调用Rails应用程序
- Rails应用程序完成了自己的工作
- 有人或某事解析了查询,并通过Rails应用程序输入结果
- Rails应用程序使用一些IPC方法将结果发送给机器人
- Bot将结果发送给原始发件人(通过XMPP(
可以像往常一样进行一些更改。例如,我认为您根本不需要Resque。机器人可以简单地将请求立即传递给Rails应用程序。然而,这取决于负载、您想要实现的响应时间、您当前的架构等。也许Resque作业可以等待Rails应用程序返回结果,然后该作业(而不是Rails应用程序(将使用IPC。还有其他变体…
我需要写一个rake任务来启动/停止/重新加载机器人吗
不,你没有。这取决于你如何以及何时运行它。毕竟,Rake可以被视为一种将多个Ruby脚本放在一起并在它们之间创建依赖关系的方便方式。如果你认为机器人除了运行它之外还有其他任务(一些清理、部署等(,那么为了方便起见,最好使用Rake。如果你还没有,重构机器人的逻辑到类,并使用Rake任务来初始化它。但如果你离开它,只需按原样运行脚本(使用monit、自定义init.d脚本、ad-hoc等(,也会很好。
如果我在没有rake的情况下运行它(据说是由Monit监控的独立进程(,那么我如何与Resque接口或访问我的rails模型?
Rake对此没有任何影响。从操作系统的角度来看,无论你是通过Rake运行Resque,还是通过Rake或作为独立脚本运行机器人,这都无关紧要。无论如何,它们将是不同的过程。此外,请记住,Resque需要Redis在某个地方运行。
我知道这些可能是非常琐碎的问题
没有。我认为像这样的问题还需要一段时间才能被认为是微不足道的。
您可以将代码放在初始化器上运行,并可以完全访问所有Rails模型或库。
这样,你就不需要在你的机器人和你的Rails应用程序之间"通信",因为你的机器人就在你的Rails App中。
Boilerplate代码如下:
config/ininitializers/background_app_tasks.rb
class BackgroundWorker
#-------------------------------
def initialize(operation='normal')
@exit = false
@lock = Mutex.new # For thread safety
@thread = nil
say "Starting in '#{operation}' mode..."
case operation
when 'normal'
@thread = Thread.new() { loopme }
when 'cleanup'
@thread = Thread.new() { cleanup }
when 'nothing'
#startup without threads
end
@thread.run if @thread
end
#-------------------------------
def exit!
begin
return if @exit # #stop?
say "Exiting #{}, waiting for mutex..."
@lock.synchronize {
say "exiting thread #{@thread.to_s || '<sem nome>' }..."
@exit = true # #stop
}
rescue Exception => e
exceptme(e)
end
end
#-------------------------------
def loopme
at_exit { exit! }
i=0; ok=false;
nap = 30
while true do
begin
break if @exit
i+=1
#lock mutex for processing...
@lock.synchronize {
#.... do some work ....
}
rescue StandardError => e
#....
end
sleep(nap)
end
end
end #class
# ------ M A I N --------
Thread.abort_on_exception=false
e = BackgroundWorker.new(OPERATION)