当我们配置 rails 的时候应注意 puma 的线程数与数据库连接池数一致。
否则会出来一些奇怪的现象:
例如在并发大,但是在内存,cpu,mysql 压力不大的情况下,ruby 耗时特别的长。使用 Newrelic 与 ab 工具 尝试了解项目的性能
例如得到一个错误:ActiveRecord::ConnectionTimeoutError (could not obtain a connection from the pool within 5.000 seconds (waited 5.002 seconds); all pooled connections were in use)
这里有着一个缓存,缓存着一个 conn, 这个 conn,在此次的请求中可以被使用任意次数。缓存不存在时,将从连接池中获取相应的 conn
#/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#363
def connection
@thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
end
#/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#389
# If all connections are leased and the pool is at capacity (meaning the
# number of currently leased connections is greater than or equal to
# size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
def checkout(checkout_timeout = @checkout_timeout)
checkout_and_verify(acquire_connection(checkout_timeout))
end
获取 conn 的方式有以下三种,第一种是直接返回可用的 conn, 我们现在先关注第二种,尝试创建 conn,并控制着进程中所有的线程创建的连接数。
#/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#706
# Acquire a connection by one of 1) immediately removing one
# from the queue of available connections, 2) creating a new
# connection if the pool is not at capacity, 3) waiting on the
# queue for a connection to become available.
def acquire_connection(checkout_timeout)
if conn = @available.poll || try_to_checkout_new_connection
conn
else
reap
@available.poll(checkout_timeout)
end
end
#/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#739
# If the pool is not at a +@size+ limit, establish new connection. Connecting
# to the DB is done outside main synchronized section.
def try_to_checkout_new_connection
# first in synchronized section check if establishing new conns is allowed
# and increment @now_connecting, to prevent overstepping this pool's @size
# constraint
do_checkout = synchronize do
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
@now_connecting += 1
end
end
if do_checkout
begin
conn = checkout_new_connection
ensure
synchronize do
if conn
adopt_connection(conn)
# returned conn needs to be already leased
conn.lease
end
@now_connecting -= 1
end
end
end
end
当没有可用的 conns,并且也已经达到创建 conn 的上限时,该怎么办呢?这时就会等待使用中的 conns 的释放,等待超时了就会报错
#/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#147
def poll(timeout = nil)
synchronize { internal_poll(timeout) }
end
def internal_poll(timeout)
no_wait_poll || (timeout && wait_poll(timeout))
end
def wait_poll(timeout)
@num_waiting += 1
t0 = Time.now
elapsed = 0
loop do
@cond.wait(timeout - elapsed)
return remove if any?
elapsed = Time.now - t0
if elapsed >= timeout
msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' %
[timeout, elapsed]
raise ConnectionTimeoutError, msg
end
end
ensure
@num_waiting -= 1
end
end