慎用establish connection
在一个Rails应用中连接多个数据库,我们常常这么做:
Ruby代码
- class Cookie < ActiveRecord::Base
- establish_connection :monitor_spider
- ...
- end
这样,在使用Cookie.find等操作的时候,就会连接到database.yml中monitor_spider配置的数据库上操作。
以前一直都这么用,没发现什么不妥。最近一个项目,由于启动的进程比较多,老是碰到数据库连接池链接获取超时的错误。通过MySQL Client用命令:show processlist;
发 现数据库连接数量一直居高不下,轻轻松松就上2k+的连接。通过读Rails框架的connection_pool.rb文件代码,发现在各模型中用 establish_connection连接数据库会造成很大的问题。文件中类ConnectionHandler的 establish_connection方法代码如下:
Ruby代码
- def establish_connection(name, spec)
- @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
- end
那要怎么处理呢?
我现在的思路是自己来维护数据库连接池。
首先,对ConnectionAdapters进行一下改造,将如下代码放到initializers中:
Ruby代码
- module ActiveRecord
- module ConnectionAdapters
- class ConnectionHandler
- def specified_establish_connection(name, spec)
- @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
- end
- def retrieve_connection_pool(klass)
- if klass.respond_to?(:pool_name)
- pool = @connection_pools[klass.pool_name]
- # ActiveRecord::Base.logger.info "pool: #{pool.inspect}"
- return pool if pool
- end
- pool = @connection_pools[klass.name]
- # puts pool.inspect
- return pool if pool
- return nil if ActiveRecord::Base == klass
- retrieve_connection_pool klass.superclass
- end
- end
- end
- end
在environment.rb中事先连接建立数据库连接池:
Ruby代码
- #specified pool
- config = YAML::load(File.open("config/database.yml"))
- %w(monitor_center monitor_spider).each do |pool_name|
- spec = config[pool_name]
- adapter_method = "#{spec["adapter"]}_connection"
- ActiveRecord::Base.connection_handler.specified_establish_connection(pool_name, ActiveRecord::Base::ConnectionSpecification.new(spec, adapter_method))
- end
Ruby代码
- class TaskStat < ActiveRecord::Base
- def self.pool_name;"monitor_spider";end
- ...
- end
- class TaskInfo < ActiveRecord::Base
- def self.pool_name;"monitor_center";end
- ...
- end
这样,TaskStat就会用monitor_spider pool,TaskInfo会用monitor_center pool。同时整个应用就只有三个ConnectionPool(外加默认的ActiveRecord::Base的)
通过这样的改造之后,效果明显,现在show processlist一般只有200~300的连接数。