класс ActiveRecord::ConnectionAdapters::ConnectionPool
Базовый класс пула соединений для управления базами данных Active Record.
Введение
Пул соединений синхронизирует доступ потоков к ограниченному числу соединений с базой данных. Основная идея заключается в том, что каждый поток запрашивает соединение с базы данных из пула, использует его и возвращает в пул. ConnectionPool полностью потокобезопасен и гарантирует, что одно соединение не может быть использовано двумя потоками одновременно, если контракт ConnectionPool соблюдается правильно. Он также обрабатывает случаи, когда количество потоков больше, чем количество соединений: если все соединения заняты, и поток пытается получить соединение, ConnectionPool будет ожидать, пока какое-либо другое соединение будет возвращено в пул.
Получение (выдача) соединения
Соединения могут быть получены и использованы из пула несколькими способами:
-
Просто используйте ActiveRecord::Base.connection, как в Active Record 2.1 и ранее (до пула соединений). В конце концов, когда вы закончите работу с соединением(ями) и хотите вернуть его в пул, вызываете ActiveRecord::Base.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).
Параметры
Существует несколько параметров, связанных с пулом соединений, которые вы можете добавить в конфигурацию соединения с базой данных:
-
pool: число, указывающее размер пула соединений (по умолчанию 5) -
checkout_timeout: количество секунд, в течение которых следует блокироваться и ожидать соединения, прежде чем отказаться и выбросить ошибку таймаута (по умолчанию 5 секунд). -
reaping_frequency: частота в секундах для периодического запуска Reaper, который пытается найти и восстановить соединения из мертвых потоков, что может произойти, если программист забывает закрыть соединение в конце потока или поток завершается неожиданно. Независимо от этого параметра, Reaper будет вызван перед каждым блокирующим ожиданием. (По умолчанию nil, что означает, что Reaper не должен планироваться).
Атрибуты
Публичные методы класса
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 321 def initialize(spec) super() @spec = spec @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5 @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f)) @reaper.run # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 # The cache of threads mapped to reserved connections, the sole purpose # of the cache is to speed-up +connection+ method, it is not the authoritative # registry of which thread owns which connection, that is tracked by # +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 a 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 +@thread_cached_conns+ 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 end
Создаёт новый объект ConnectionPool. spec — объект ConnectionSpecification, описывающий информацию о соединении с базой данных (например, адаптер, имя хоста, имя пользователя, пароль и т. д.), а также максимальный размер для этого ConnectionPool.
По умолчанию максимальный размер ConnectionPool составляет 5.
Методы публичного экземпляра
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 372 def active_connection? @thread_cached_conns[connection_cache_key(Thread.current)] end
Существует ли открытое подключение, используемое для текущей нити?
Этот метод работает только с подключениями, полученными через методы connection или with_connection, подключения, полученные через checkout, не будут обнаружены методом active_connection?
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 498
def checkin(conn)
synchronize do
remove_connection_from_thread_cache conn
conn._run_checkin_callbacks do
conn.expire
end
@available.add conn
end
end Возвращение подключения к базе данных в пул, означающее, что это подключение больше не нужно.
conn: объект AbstractAdapter, полученный ранее с помощью вызова checkout для этого пула.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 489 def checkout(checkout_timeout = @checkout_timeout) checkout_and_verify(acquire_connection(checkout_timeout)) end
Выдача подключения к базе данных из пула, означающее, что вы хотите его использовать. Вы должны вызвать checkin, когда вам больше не нужно это подключение.
Это делается либо путем возврата и аренды существующего подключения, либо путем создания нового подключения и его аренды.
Если все подключения заняты, а пул заполнен (т.е. количество в настоящее время занятых подключений равно или больше установленного лимита размера), будет поднято исключение ActiveRecord::ConnectionTimeoutError.
Возвращает: объект AbstractAdapter.
Исключения:
-
ActiveRecord::ConnectionTimeoutError — подключение не может быть получено из пула.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 447
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.config[:checkout_timeout] * 2секунд).
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 471 def clear_reloadable_connections! clear_reloadable_connections(false) end
Очищает кэш, который сопоставляет классы и повторно подключает подключения, требующие перезагрузки.
Пул сначала пытается получить владение всеми подключениями. Если это не удается в течение заданного времени ожидания (по умолчанию spec.config[:checkout_timeout] * 2 секунд), пул насильно очищает кэш и перезагружает подключения без учета других нитей, владеющих подключениями.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 404
def connected?
synchronize { @connections.any? }
end Возвращает true, если подключение уже открыто.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 363 def connection @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout end
Получение подключения, связанного с текущей нитью, или вызов checkout для получения его при необходимости.
Метод connection можно вызывать любое количество раз; подключение хранится в кэше, индексируемом нитью.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 414
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.config[:checkout_timeout] * 2секунд).
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 436 def disconnect! disconnect(false) end
Отключает все подключения в пуле и очищает его.
Пул сначала пытается получить владение всеми подключениями. Если это не удается в течение заданного времени ожидания (по умолчанию spec.config[:checkout_timeout] * 2 секунд), пул насильно отключается без учета других нитей, владеющих подключениями.
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 544
def reap
stale_connections = synchronize do
@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 383
def release_connection(owner_thread = Thread.current)
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 512
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 393
def with_connection
unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
conn = connection
fresh_connection = true
end
yield conn
ensure
release_connection if fresh_connection
end Если подключение, полученное через методы connection или with_connection, уже существует, передать его в блок. Если такое подключение не существует, взять подключение, передать его в блок и закрыть подключение по завершении.
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.