класс ActiveRecord::ConnectionAdapters::ConnectionPool
Пул подключений Active Record
Базовый класс пула подключений для управления базами данных Active Record.
Введение
Пул подключений синхронизирует доступ потоков к ограниченному числу подключений к базе данных. Основная идея заключается в том, что каждый поток запрашивает подключение к базе данных из пула, использует его и возвращает обратно в пул. ConnectionPool полностью потокобезопасен и гарантирует, что одним подключением не смогут пользоваться два потока одновременно, если соглашение ConnectionPool соблюдается корректно. Он также обрабатывает случаи, когда количество потоков превышает количество подключений: если все подключения заняты, а поток пытается захватить подключение, тогда ConnectionPool будет ждать, пока другой поток вернёт подключение в пул или срок ожидания checkout_timeout истечёт.
Получение (выдача) подключения
Подключения могут быть получены и использованы из пула несколькими способами:
-
Просто используйте ActiveRecord::Base.connection, как в Active Record 2.1 и ранее (до пулов подключений). Когда вы закончите с подключением(ями) и хотите вернуть его в пул, вызывайте 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 секунд).
Атрибуты
Публичные методы класса
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 122 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>@thread_cached_conns</tt> does not require # synchronization. @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size) @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 @lock_thread = false @async_executor = build_async_executor @reaper = Reaper.new(self, db_config.reaping_frequency) @reaper.run end
Создаёт новый объект ConnectionPool. pool_config — объект PoolConfig, описывающий информацию о подключении к базе данных (например, адаптер, имя хоста, имя пользователя, пароль и т. д.), а также максимальный размер для данного ConnectionPool.
Максимальный размер ConnectionPool по умолчанию равен 5.
ActiveRecord::ConnectionAdapters::QueryCache::ConnectionPoolConfiguration::new Публичные методы экземпляра
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 198 def active_connection? @thread_cached_conns[connection_cache_key(current_thread)] end
Возвращает true, если есть открытое соединение, используемое для текущего потока.
Этот метод работает только для соединений, полученных через connection или with_connection методы. Соединения, полученные через checkout, не будут обнаружены active_connection?
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 365
def checkin(conn)
conn.lock.synchronize do
synchronize do
remove_connection_from_thread_cache conn
conn._run_checkin_callbacks do
conn.expire
end
conn.lock_thread = nil
@available.add conn
end
end
end Возвращает соединение с базой данных в пул, указывая, что вам больше не нужно это соединение.
conn: объект AbstractAdapter, который был получен ранее вызовом checkout в этом пуле.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 354 def checkout(checkout_timeout = @checkout_timeout) connection = checkout_and_verify(acquire_connection(checkout_timeout)) connection.lock_thread = @lock_thread connection end
Извлекает соединение с базой данных из пула, указывая, что вы хотите его использовать. Вы должны вызвать checkin, когда оно вам больше не понадобится.
Это делается либо путем возврата и аренды существующего соединения, либо путем создания нового соединения и его аренды.
Если все соединения арендованы, и пул достиг своей емкости (то есть количество текущих арендованных соединений больше или равно установленному пределу размера), будет выброшено исключение ActiveRecord::ConnectionTimeoutError.
Возвращает: объект AbstractAdapter.
Вызывает:
-
ActiveRecord::ConnectionTimeoutErrorневозможно получить соединение из пула.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 312
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 336 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 235
def connected?
synchronize { @connections.any? }
end Возвращает true, если соединение уже открыто.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 183 def connection @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout end
Извлекает соединение, связанное с текущим потоком, или вызывает checkout для получения одного, если необходимо.
connection может вызываться любое количество раз; соединение хранится в кэше, ключом которого является поток.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 250
def connections
synchronize { @connections.dup }
end Возвращает массив, содержащий соединения, которые находятся в данный момент в пуле. Доступ к массиву не требует синхронизации в пуле, потому что массив создается заново и не хранится пулом.
Однако; этот метод обходит потокобезопасную схему доступа к соединению ConnectionPool. Возвращаемое соединение может принадлежать другому потоку, не принадлежать ни одному потоку или случайно принадлежать вызывающему потоку.
Вызов методов в отношении соединения без владения подчиняется гарантиям потокобезопасности базового метода. Многие методы в классах адаптеров соединений по своей природе не являются потокобезопасными.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 260
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 = []
@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 282 def disconnect! disconnect(false) end
Отключает все соединения в пуле и очищает пул.
Пул сначала пытается получить владение всеми соединениями. Если это невозможно сделать в течение интервала времени ожидания (время по умолчанию составляет spec.db_config.checkout_timeout * 2 секунд), то пул принудительно отключается, не обращая внимания на другие потоки, владеющие соединениями.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 437
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 459 def flush! reap flush(-1) end
Отключает все текущие неактивные соединения. Текущие арендованные соединения не затрагиваются.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 166
def lock_thread=(lock_thread)
if lock_thread
@lock_thread = ActiveSupport::IsolatedExecutionState.context
else
@lock_thread = nil
end
if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
active_connection.lock_thread = @lock_thread
end
end # File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 414
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 209
def release_connection(owner_thread = ActiveSupport::IsolatedExecutionState.context)
if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
checkin conn
end
end Сигнализирует о том, что поток завершил работу с текущим соединением. release_connection освобождает связь поток-соединение и возвращает соединение в пул.
Этот метод работает только для соединений, полученных через connection или with_connection методы, соединения, полученные через checkout, не будут автоматически освобождены.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 382
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 472
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 224
def with_connection
unless conn = @thread_cached_conns[connection_cache_key(ActiveSupport::IsolatedExecutionState.context)]
conn = connection
fresh_connection = true
end
yield conn
ensure
release_connection if fresh_connection
end Возвращает подключение из пула подключений в блок. Если подключение еще не выдано текущей нити, то подключение будет взято из пула, передано в блок, и затем возвращено в пул по завершении блока. Если подключение уже выдано текущей нити, например, через connection или with_connection, то будет использовано это существующее подключение, и оно не будет автоматически возвращено в пул по окончании блока; ожидается, что такое существующее подключение будет надлежащим образом возвращено в пул кодом, который его взял.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.