web game里经常出现这样的需求:
1.建造一个房子,等待n秒后建好
2.种植一个植物,等待n秒后完成
3.生产一个汽车,等待n秒后完成
4.升级一个基地,等待n秒后完成
..................
无论是汽车还是房子,建造或升级这个动作很简单,只需要更新一下数据库里的某个字段。
关键是如何处理等待n秒这个操作。
cron + rake
最简单的做法就是后台定时rake,每隔一段时间扫描一下整个表,根据结束时间去改变状态字段。
但是这样作的缺陷很明显,即使扫描的时间间隔再短也达不到准确,还有就是rake每次执行都要加载一次rails环境,然后再释放,这样效率很低。
Delay Job:
看了各种delay job插件,这些插件主要解决的问题是异步的问题,同样达不到精确的计划。
异步线程or进程+sleep n
这种方案看起来不错,但是无论线程还是进程,一直sleep都会占很多资源.
EventMachine
引用
EventMachine是一个基于Reactor设计模式的、用于网络编程和并发编程的框架。Reactor模式描述了一种服务处理器,它接受事件并将其分发给已注册的事件处理。这种模式的好处就是清晰的分离了时间分发和处理事件的应用程序逻辑,而不需引入多线程来把代码复杂化。
好了,神器在手,,看代码:
Delay Server:
#!/usr/bin/env ruby
require 'rubygems'
require 'optparse'
require 'eventmachine'
require 'evma_httpserver'
require 'rack'
options = {
:Port => 3000,
:Host => "0.0.0.0",
:environment => (ENV['RAILS_ENV'] || "development").dup
}
ARGV.clone.options do |opts|
opts.on("-p", "--port=port", Integer,
"Runs Delay Server on the specified port.", "Default: 3000") { |v| options[:Port] = v }
opts.on("-b", "--binding=ip", String,
"Binds Delay Server to the specified ip.", "Default: 0.0.0.0") { |v| options[:Host] = v }
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
opts.separator ""
opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
opts.parse!
end
puts "=> Delay Server starting on http://#{options[:Host]}:#{options[:Port]}"
ENV["RAILS_ENV"] = options[:environment]
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
require(File.join(File.dirname(__FILE__), 'config', 'environment'))
class DelayServer < EM::Connection
include EM::HttpServer
def post_init
super
end
def process_http_request
params = Rack::Utils.parse_query(@http_query_string)
response = EM::DelegatedHttpResponse.new(self)
response.status = 200
response.content_type 'text/html'
current_time = Time.now
response.content = "request time -> #{current_time.to_s(:db)} / delay time -> #{params['delay']}s\n"
EM::add_timer(params["delay"]) do
p params["operate"]
puts Time.now.to_s(:db)
end
response.send_response
end
end
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server"
EM.run{
EM.start_server options[:Host], options[:Port], DelayServer
}
Rails Server:
class XxxController < ApplicationController
def build
#.................
system('%Q{curl "http://0.0.0.0:3000?delay=10&operate=build" > ./log/delay_server.log 2>&1 &})
#.................
end
end
其实rails server与delay server之间通讯可以有多种选择,比如drb
本着简单高效的原则,选择了http协议..
虽然eventmachine是非阻塞io,但是单个进程的吞吐量有限,利用http协议很容易在前面加个nginx,变成一个小集群....