浏览 5824 次
锁定老帖子 主题:深入Rails2.3 Rack
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-09-02
最后修改:2009-09-02
Rails2.3引入了Rack, 这使得rails内部的Http处理机制发生了很大的变化。Rack是一个非常微型的action stack, 和java社区的webwork里使用的xwork非常类似。 Rack的进入,对rails带来的最大一个好处,就是引入了metal。 ./script/generate metal MetalTest 就可以产生一个metal, 位于app/metal目录下。 metal有什么特性呢? 快,它非常快。但是web应用一次平常的请求,动辄向数据库发送数十条SQL,总时间一般都在100ms的级别。而rails内置的action_pack已经是在ms级别了, 所以对于一般的web请求, 不需要用到它。 但是,对于一些只需要发送几条SQL,然后传给浏览器一些简单的json数据,对于这样的应用场景,证实metal大展身手的地方。 rails引入metal,用DHH自己的话,也是为了这样的场景。他在自己的产品campfire中,有许多定时的ajax请求,这种请求一般用action_pack做,每秒支持200次(5ms/request),而用metal,每秒可以达到3000次(0.3ms/request) 引入rack后,action_pack的地位就陡然下降了。在新的体系里面,所有的东西都是middleware,这里的action_pack,以及metal,都是作为一个middleware,集成到rails内部的。 使用rake middlewares 你可以看到: use Rack::Lock use ActionController::Failsafe use ActionController::Reloader use ActionController::Session::CookieStore, #<Proc:0xb76182a0@(eval):8> use Rails::Rack::Metal use ActionController::ParamsParser use Rack::MethodOverride use Rack::Head use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache run ActionController::Dispatcher.new 这里结构就比以前清晰多了。以上所有的middelware,除了Rack:Metal以外,都是用来实现已有的功能的。从名字上可以非常清晰地看清楚各自的用途。 到这里,就应该提提我之前说到的那个问题了。代码不能重新加载? 这个问题之前也经常遇到,但由于时间关系,没有能够深入地纠察原因。 使用到的工具:一个编辑器,用raise设置堆栈。 首先,查看了Reloader这个middelware,从名称上看,它就是干这个事情的。 def call(env) Dispatcher.reload_application status, headers, body = @app.call(env) # We do not want to call 'cleanup_application' in an ensure block # because the returned Rack response body may lazily generate its data. This # is for example the case if one calls # # render :text => lambda { ... code here which refers to application models ... } # # in an ActionController. # # Instead, we will want to cleanup the application code after the request is # completely finished. So we wrap the body in a BodyWrapper class so that # when the Rack handler calls #close during the end of the request, we get to # run our cleanup code. [status, headers, BodyWrapper.new(body)] end 果不其然,这里有个Dispatcher.reload_application。在每次请求过后,rails都要清除action_support中Dependencies所管理的常量,然后在下一次请求的时候,重新加载整个应用。 很显然,问题应该在清除常量这里,好像Reloader没有做这个操作啊? 呵呵,看一下注释,就清楚了,这里曾经出过一个Bug,Rails开发者用BodyWrapper解决了这个bug.clear常量的操作就在close方法里: def close @body.close if @body.respond_to?(:close) ensure Dispatcher.cleanup_application end 难道是matal没有使用Reload这个中间件? 打个断点试试,首先找到metal这个中间件的核心代码:call def call(env) raise @metals.keys.each do |app| result = app.call(env) return result unless result[0].to_i == 404 end @app.call(env) end 出现了这样的堆栈: /usr/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/rails/rack/metal.rb:43:in `call' /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.3/lib/action_controller/session/c ookie_store.rb:93:in `call' /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.3/lib/action_controller/reloader. rb:29:in `call' /usr/lib/ruby/gems/1.8/gems/actionpack-2.3.3/lib/action_controller/failsafe. rb:26:in `call' ..................... 明明在用,并且和action_controller的堆栈一摸一样。 好,看一下action_support中Dependencies中的代码,发现有log信息,打开日志看看。 ActiveSupport::Dependencies.log_activity=true 发现日志也非常正常! 实在没有办法了,问题只能处在metal自己身上。 陡然间,就在刚才设置raise的地方,发现了: @metals.keys.................... 原来在使用一个内部变量,先试试让每次call都重新刷新@metal变量:问题居然解决了! 哦,原来这个变量中直接将每个metal加载后产生的常量放了进来,虽然reloader有清洗,但是他肯定不会清洗这个变量里的metal类。 到这里,问题就基本解决了,下一步看看怎么切换metal这个中间件。 呵呵,对了,rack中内置了丰富的插入删除更改中间件的方法。 附赠代码: lib/smart_metal.rb # # <<代码重构>>中指出用方法代替变量,是多么的闪闪发光啊。 # 你看,在这里,由于Metal用的是变量,我只能copy&plast class SmartMetal < Rails::Rack::Metal def get_metals metals = ActiveSupport::OrderedHash.new self.class.metals.each { |app| metals[app] = true } metals end def call(env) get_metals.keys.each do |app| result = app.call(env) return result unless result[0].to_i == 404 end @app.call(env) end end config/initializers/my_init.rb Rails::configuration.middleware.swap 'Rails::Rack::Metal', SmartMetal 然后,在运行 $ rake middleware (in /cygdrive/d/dev/twork) use Rack::Lock use ActionController::Failsafe use ActionController::Reloader use ActionController::Session::CookieStore, #<Proc:0x7fc2552c@(eval):8> use SmartMetal############ 看到了吧 ################################# use ActionController::ParamsParser use Rack::MethodOverride use Rack::Head use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache run ActionController::Dispatcher.new 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-09-02
以前据说 metal 的速度还是比不上 sinatra,不知道现在效率怎么样。
|
|
返回顶楼 | |
发表时间:2009-09-02
根据 http://blog.codefront.net/2009/06/15/activerecord-rails-metal-too-many-connections/
如果在Metal里面用ActiveRecord,要手动释放,感觉很tricky。 |
|
返回顶楼 | |
发表时间:2009-09-02
花花公子 写道 根据 http://blog.codefront.net/2009/06/15/activerecord-rails-metal-too-many-connections/
如果在Metal里面用ActiveRecord,要手动释放,感觉很tricky。 呃,很dirty |
|
返回顶楼 | |
发表时间:2009-09-03
最后修改:2009-09-03
花花公子 写道 根据 http://blog.codefront.net/2009/06/15/activerecord-rails-metal-too-many-connections/
如果在Metal里面用ActiveRecord,要手动释放,感觉很tricky。 从下面这个stack可以看出,Metal在ParamsParser的上面,它只支持session,连参数解析都不支持。 1. use Rack::Lock 2. use ActionController::Failsafe 3. use ActionController::Reloader 4. use ActionController::Session::CookieStore, #<Proc:0xb76182a0@(eval):8> 5. use Rails::Rack::Metal 6. use ActionController::ParamsParser 7. use Rack::MethodOverride 8. use Rack::Head 9. use ActiveRecord::ConnectionAdapters::ConnectionManagement 在我的应用中,我把ParamParser交换到Metal的上面,之后metal就支持这一特性了。 config.middleware.delete Rails::Rack::Metal config.middleware.insert_after ActionController::ParamsParser, SmartMetal 最后这个ConnectionManagement 应该就是管理连接的。 如果你非得用active_recod,可以试试把这个中间件放在metal的上面,但是非常不建议。这样以来,估计metal的速度会有所下降。优势就不在了。 10. use ActiveRecord::QueryCache 11. run ActionController::Dispatcher.new |
|
返回顶楼 | |
发表时间:2009-09-03
dazuiba 写道 从下面这个stack可以看出,Metal在ParamsParser的上面,它只支持session,连参数解析都不支持。 在我的应用中,我把ParamParser交换到Metal的上面,之后metal就支持这一特性了。 难道你不是用sinatra作metal中间件吗?如果是自己解析的话,要用到Rack::Request req = Rack::Request.new(env) req.post? req.params["data"] |
|
返回顶楼 | |
发表时间:2009-09-05
to 花花公子
我上面说的有问题,需要更正一下。 我这个middleware,是一个extjs的direct的路由。需要再将请求转发给其他的Controller。而这个ParamsParser负责的是解析action和controller相关的参数。 我需要重新设置这些参数,然后转发给rails 的action_pack处理。 另外,也需要支持批量请求。 说以这个情况下,就需要此ParamsParser了。 |
|
返回顶楼 | |