class ActiveRecord::ConnectionAdapters::ConnectionPool
Пул подключений Active Record
Базовый класс пула подключений для управления базами данных Active Record.
Введение
Пул подключений синхронизирует доступ потоков к ограниченному числу подключений к базе данных. Основная идея заключается в том, что каждый поток запрашивает подключение к базе данных из пула, использует его и возвращает подключение обратно в пул. ConnectionPool полностью потокобезопасен и гарантирует, что одно подключение не может использоваться двумя потоками одновременно, если соблюдается контракт ConnectionPool. Он также обрабатывает случаи, когда количество потоков больше, чем количество подключений: если все подключения заняты, и поток пытается получить подключение, тогда ConnectionPool будет ожидать, пока другой поток не вернет подключение или не истечет таймаут checkout_timeout.
Получение (выдача) подключения
Подключения могут быть получены и использованы из пула несколькими способами:
-
Просто используйте ActiveRecord::Base.lease_connection. Когда вы закончите работу с подключением(ями) и хотите вернуть его в пул, вызывайте ActiveRecord::Base.connection_handler.clear_active_connections!. Это стандартное поведение Active Record при использовании с циклом обработки запросов Action Pack.
-
Вручную получите подключение из пула с помощью ActiveRecord::Base.connection_pool.checkout. Вы сами отвечаете за возврат подключения в пул, вызвав ActiveRecord::Base.connection_pool.checkin(connection).
-
Используйте ActiveRecord::Base.connection_pool.with_connection(&block), который получает подключение, передает его в качестве единственного аргумента в блок и возвращает его в пул после завершения блока.
Подключения в пуле фактически являются объектами AbstractAdapter (или объектами, совместимыми с интерфейсом AbstractAdapter).
Пока поток имеет подключение, выданное из пула с помощью одного из трёх вышеперечисленных методов, это подключение автоматически будет использоваться ActiveRecord запросами, выполняемыми в этом потоке. Нет необходимости явно передавать полученное подключение в модели Rails или запросы, например.
Параметры
Существует несколько параметров, связанных с пулом подключений, которые можно добавить в конфигурацию соединения с базой данных:
-
pool: максимальное количество подключений, которые может управлять пул (по умолчанию 5). -
idle_timeout: количество секунд, в течение которых подключение будет храниться в пуле без использования, прежде чем будет автоматически отключено (по умолчанию 300 секунд). Установите значение 0, чтобы хранить подключения навсегда. -
checkout_timeout: количество секунд ожидания подключения, прежде чем прекратить ожидание и выбросить ошибку таймаута (по умолчанию 5 секунд).
Константы
- WeakThreadKeyMap
Атрибуты
Открытые методы класса
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 212 def install_executor_hooks(executor = ActiveSupport::Executor) executor.register_hook(ExecutorHooks) end
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 232 def initialize(pool_config) super() @pool_config = pool_config @db_config = pool_config.db_config @role = pool_config.role @shard = pool_config.shard @checkout_timeout = db_config.checkout_timeout @idle_timeout = db_config.idle_timeout @size = db_config.pool # This variable tracks the cache of threads mapped to reserved connections, with the # sole purpose of speeding up the +connection+ method. It is not the authoritative # registry of which thread owns which connection. Connection ownership is tracked by # the +connection.owner+ attr on each +connection+ instance. # The invariant works like this: if there is mapping of <tt>thread => conn</tt>, # then that +thread+ does indeed own that +conn+. However, an absence of such # mapping does not mean that the +thread+ doesn't own the said connection. In # that case +conn.owner+ attr should be consulted. # Access and modification of <tt>@leases</tt> does not require # synchronization. @leases = LeaseRegistry.new @connections = [] @automatic_reconnect = true # Connection pool allows for concurrent (outside the main +synchronize+ section) # establishment of new connections. This variable tracks the number of threads # currently in the process of independently establishing connections to the DB. @now_connecting = 0 @threads_blocking_new_connections = 0 @available = ConnectionLeasingQueue.new self @pinned_connection = nil @pinned_connections_depth = 0 @async_executor = build_async_executor @schema_cache = nil @reaper = Reaper.new(self, db_config.reaping_frequency) @reaper.run end
Создаёт новый объект ConnectionPool. pool_config — это объект PoolConfig, описывающий информацию о подключении к базе данных (например, адаптер, имя хоста, имя пользователя, пароль и т.д.), а также максимальный размер для этого ConnectionPool.
По умолчанию максимальный размер ConnectionPool равен 5.
Публичные методы экземпляра
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 376 def active_connection? connection_lease.connection end
Возвращает true, если есть открытое соединение, используемое для текущего потока.
Этот метод работает только для соединений, полученных через lease_connection или with_connection методы. Соединения, полученные через checkout, не будут обнаружены active_connection?
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 570
def checkin(conn)
return if @pinned_connection.equal?(conn)
conn.lock.synchronize do
synchronize do
connection_lease.clear(conn)
conn._run_checkin_callbacks do
conn.expire
end
@available.add conn
end
end
end Возвращает соединение с базой данных в пул, указывая, что оно больше не нужно.
conn: объект AbstractAdapter, полученный ранее вызовом checkout из этого пула.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 547
def checkout(checkout_timeout = @checkout_timeout)
if @pinned_connection
@pinned_connection.lock.synchronize do
synchronize do
@pinned_connection.verify!
# Any leased connection must be in @connections otherwise
# some methods like #connected? won't behave correctly
unless @connections.include?(@pinned_connection)
@connections << @pinned_connection
end
end
end
@pinned_connection
else
checkout_and_verify(acquire_connection(checkout_timeout))
end
end Извлекает соединение с базой данных из пула, указывая, что вы хотите его использовать. Вы должны вызвать checkin, когда оно больше не нужно.
Это делается либо путем возврата и аренды существующего соединения, либо путем создания нового соединения и его аренды.
Если все соединения арендованы, и пул заполнен (то есть количество текущих арендованных соединений больше или равно предельному размеру), будет возбуждено исключение ActiveRecord::ConnectionTimeoutError.
Возвращает: объект AbstractAdapter.
Вызывает:
-
ActiveRecord::ConnectionTimeoutErrorсоединение не может быть получено из пула.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 505
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
synchronize do
@connections.each do |conn|
if conn.in_use?
conn.steal!
checkin conn
end
conn.disconnect! if conn.requires_reloading?
end
@connections.delete_if(&:requires_reloading?)
@available.clear
end
end
end Очищает кэш, который отображает классы и переподключает соединения, требующие перезагрузки.
Вызывает:
-
ActiveRecord::ExclusiveConnectionTimeoutErrorесли не удается получить право собственности на все соединения в пуле в течение интервала времени ожидания (время по умолчанию составляетspec.db_config.checkout_timeout * 2секунд).
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 529 def clear_reloadable_connections! clear_reloadable_connections(false) end
Очищает кэш, который отображает классы и переподключает соединения, требующие перезагрузки.
Пул сначала пытается получить право собственности на все соединения. Если это не удается в течение интервала времени ожидания (время по умолчанию составляет spec.db_config.checkout_timeout * 2 секунд), то пул принудительно очищает кэш и перезагружает соединения без учета других потоков, владеющих соединениями.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 427
def connected?
synchronize { @connections.any?(&:connected?) }
end Возвращает true, если соединение уже открыто.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 442
def connections
synchronize { @connections.dup }
end Возвращает массив, содержащий соединения, которые в данный момент находятся в пуле. Доступ к массиву не требует синхронизации пула, поскольку массив создается заново и не хранится в пуле.
Однако этот метод обходит схему многопоточного доступа к соединениям ConnectionPool. Возвращаемое соединение может принадлежать другому потоку, не принадлежать ни одному потоку или случайно принадлежать вызывающему потоку.
Вызов методов для соединения без владения подчиняется гарантиям многопоточности базового метода. Многие методы в классах адаптеров соединений по своей природе не являются потокобезопасными.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 452
def disconnect(raise_on_acquisition_timeout = true)
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
synchronize do
@connections.each do |conn|
if conn.in_use?
conn.steal!
checkin conn
end
conn.disconnect!
end
@connections = []
@leases.clear
@available.clear
end
end
end Отключает все соединения в пуле и очищает пул.
Вызывает:
-
ActiveRecord::ExclusiveConnectionTimeoutErrorесли не удается получить право собственности на все соединения в пуле в течение интервала времени ожидания (время по умолчанию составляетspec.db_config.checkout_timeout * 2секунд).
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 475 def disconnect! disconnect(false) end
Отключает все соединения в пуле и очищает пул.
Пул сначала пытается получить право собственности на все соединения. Если это не удается в течение интервала времени ожидания (время по умолчанию составляет spec.db_config.checkout_timeout * 2 секунд), то пул принудительно отключается без учета других потоков, владеющих соединениями.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 643
def flush(minimum_idle = @idle_timeout)
return if minimum_idle.nil?
idle_connections = synchronize do
return if self.discarded?
@connections.select do |conn|
!conn.in_use? && conn.seconds_idle >= minimum_idle
end.each do |conn|
conn.lease
@available.delete conn
@connections.delete conn
end
end
idle_connections.each do |conn|
conn.disconnect!
end
end Отключает все соединения, которые были неактивны не менее minimum_idle секунд. Соединения, в настоящее время извлеченные или возвращенные менее чем minimum_idle секунд назад, не затрагиваются.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 665 def flush! reap flush(-1) end
Отключает все текущие неактивные соединения. Текущие извлеченные соединения не затрагиваются.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 315 def lease_connection lease = connection_lease lease.sticky = true lease.connection ||= checkout end
Извлекает соединение, связанное с текущим потоком, или вызывает checkout для получения одного, если это необходимо.
lease_connection может вызываться любое количество раз; соединение хранится в кэше с ключом, представляющим собой поток.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 620
def reap
stale_connections = synchronize do
return if self.discarded?
@connections.select do |conn|
conn.in_use? && !conn.owner.alive?
end.each do |conn|
conn.steal!
end
end
stale_connections.each do |conn|
if conn.active?
conn.reset!
checkin conn
else
remove conn
end
end
end Восстанавливает потерянные соединения для пула. Потерянное соединение может возникнуть, если программист забывает вернуть соединение в конце потока или поток неожиданно завершается.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 388
def release_connection(existing_lease = nil)
if conn = connection_lease.release
checkin conn
return true
end
false
end Сигнализирует о том, что поток завершил работу с текущим соединением. release_connection освобождает связь поток-соединение и возвращает соединение в пул.
Этот метод работает только для соединений, полученных через lease_connection или with_connection методы, соединения, полученные через checkout, не будут автоматически освобождены.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 588
def remove(conn)
needs_new_connection = false
synchronize do
remove_connection_from_thread_cache conn
@connections.delete conn
@available.delete conn
# @available.any_waiting? => true means that prior to removing this
# conn, the pool was at its max size (@connections.size == @size).
# This would mean that any threads stuck waiting in the queue wouldn't
# know they could checkout_new_connection, so let's do it for them.
# Because condition-wait loop is encapsulated in the Queue class
# (that in turn is oblivious to ConnectionPool implementation), threads
# that are "stuck" there are helpless. They have no way of creating
# new connections and are completely reliant on us feeding available
# connections into the Queue.
needs_new_connection = @available.any_waiting?
end
# This is intentionally done outside of the synchronized section as we
# would like not to hold the main mutex while checking out new connections.
# Thus there is some chance that needs_new_connection information is now
# stale, we can live with that (bulk_make_new_connections will make
# sure not to exceed the pool's @size limit).
bulk_make_new_connections(1) if needs_new_connection
end Удаляет соединение из пула соединений. Соединение останется открытым и активным, но больше не будет управляться этим пулом.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 285 def schema_cache @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self) end
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 289 def schema_reflection=(schema_reflection) pool_config.schema_reflection = schema_reflection @schema_cache = nil end
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 677
def stat
synchronize do
{
size: size,
connections: @connections.size,
busy: @connections.count { |c| c.in_use? && c.owner.alive? },
dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
idle: @connections.count { |c| !c.in_use? },
waiting: num_waiting_in_queue,
checkout_timeout: checkout_timeout
}
end
end Возвращает статистику использования пула подключений.
ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 405
def with_connection(prevent_permanent_checkout: false)
lease = connection_lease
sticky_was = lease.sticky
lease.sticky = false if prevent_permanent_checkout
if lease.connection
begin
yield lease.connection
ensure
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
end
else
begin
yield lease.connection = checkout
ensure
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
release_connection(lease) unless lease.sticky
end
end
end Передает подключение из пула подключений в блок. Если подключение еще не взято текущей нитью, подключение будет взято из пула, передано в блок, а затем возвращено в пул по завершении блока. Если подключение уже взято текущей нитью, например, через lease_connection или with_connection, то это существующее подключение будет передано в блок, и оно не будет автоматически возвращено в пул по завершении блока; ожидается, что такое существующее подключение будет корректно возвращено в пул кодом, который его взял.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.