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 秒)。將它設為零表示永久保留連線。 -
checkout_timeout
:等待連線可用的秒數,過後會放棄並傳出逾時錯誤(預設為 5 秒)。
- 類別 ActiveRecord::ConnectionAdapters::ConnectionPool::Queue
- 類別 ActiveRecord::ConnectionAdapters::ConnectionPool::Reaper
- A
- C
- D
- F
- I
- L
- N
- R
- S
- W
- MonitorMixin
屬性
[R] | async_executor | |
[RW] | automatic_reconnect | |
[RW] | checkout_timeout | |
[R] | db_config | |
[R] | pool_config | |
[R] | reaper | |
[R] | role | |
[R] | shard | |
[R] | size |
類別共用方法
install_executor_hooks(executor = ActiveSupport::Executor) 連結
來源:顯示 | 於 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 206 def install_executor_hooks(executor = ActiveSupport::Executor) executor.register_hook(ExecutorHooks) end
new(pool_config) 連結
建立新的 ConnectionPool
物件。pool_config
是 PoolConfig 物件,其中描述了資料庫連線資訊(例如,介面、主機名稱、使用者名稱、密碼等),以及此 ConnectionPool
的最大大小。
預設 ConnectionPool
的最大大小為 5。
來源:顯示 | 於 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 226 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
執行個體共用方法
active_connection?() 連結
如果目前執行緒正在使用已開啟的連線,則傳回 true。
這個方法僅會對透過 lease_connection
或 with_connection
方法取得的連線運作。透過 checkout
取得的連線將不會被 active_connection?
偵測到
來源:顯示 | 於 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 370 def active_connection? connection_lease.connection end
checkin(conn) 連結
將資料庫連線檢查回池中,表示您不再需要這個連線。
conn
:一個 AbstractAdapter
物件,最初是由呼叫此池上的 checkout
所取得。
來源:顯示 | 於 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 564 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
checkout(checkout_timeout = @checkout_timeout) 連結
從池中檢查出一個資料庫連線,表示您要使用它。當您不再需要時,應呼叫 checkin
。
這個動作會傳回並租用現有的連線,或是建立新的連線並租用它。
如果所有連線都被租用,且池處於容量已滿的狀態(表示目前租用的連線數目大於或等於設定的大小限制),將會引發 ActiveRecord::ConnectionTimeoutError
例外狀況。
傳回:一個 AbstractAdapter
物件。
引發
-
ActiveRecord::ConnectionTimeoutError
:無法取得來自池的連線。
來源:顯示 | 於 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 541 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
clear_reloadable_connections(raise_on_acquisition_timeout = true) 連結
清除會對應到類別的快取,並重新連線需要重新載入的連線。
引發
-
ActiveRecord::ExclusiveConnectionTimeoutError
如果在逾時區間內無法取得所有連線池中的佔有權(預設期間為spec.db_config.checkout_timeout * 2
秒)。
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 499 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
clear_reloadable_connections!() 連結
清除會對應到類別的快取,並重新連線需要重新載入的連線。
池子首先會嘗試取得所有連線的佔有權。如果在逾時區間內無法這樣做(預設期間為spec.db_config.checkout_timeout * 2
秒),則池子將強制清除快取並重新載入連線,而不管其他擁有連線的執行緒。
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 523 def clear_reloadable_connections! clear_reloadable_connections(false) end
connected?() 連結
如果已開啟連線,則傳回 true。
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 421 def connected? synchronize { @connections.any?(&:connected?) } end
connections() 連結
傳回一個儲存目前池中連線的陣列。存取陣列不需要對池子進行同步,因為陣列是新建立的,而不會保留在池中。
不過,這個方法會繞過連線池具有執行緒安全性的連線存取模式。傳回的連線可能會被其他執行緒佔有、未被佔有,或碰巧被呼叫執行緒佔有。
在沒有擁有權的情況下,對連線呼叫方法會受到基礎方法的執行緒安全性保障所約束。連線配接器類別中的許多方法本質上會導致多執行緒不安全。
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 436 def connections synchronize { @connections.dup } end
disconnect(raise_on_acquisition_timeout = true) 連結
中斷池子中所有連線,並清除池子。
引發
-
ActiveRecord::ExclusiveConnectionTimeoutError
如果在逾時區間內無法取得所有連線池中的佔有權(預設期間為spec.db_config.checkout_timeout * 2
秒)。
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 446 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
disconnect!() 連結
中斷池子中所有連線,並清除池子。
池子首先會嘗試取得所有連線的佔有權。如果在逾時區間內無法這樣做(預設期間為spec.db_config.checkout_timeout * 2
秒),則池子將在不考慮其他擁有連線的執行緒的情況下強制中斷連線。
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 469 def disconnect! disconnect(false) end
flush(minimum_idle = @idle_timeout) 連結
斷開所有連線閒置至少 `minimum_idle` 秒的連線。目前已簽出的或是在 `minimum_idle` 秒前簽入的連線不受影響。
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 637 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
flush!() 連結
斷開所有當前閒置的連線。目前已簽出的連線不受影響。
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 659 def flush! reap flush(-1) end
lease_connection() 連結
擷取與目前的執行緒關聯的連線,或在必要時呼叫 checkout
來取得一個連線。
lease_connection
可以呼叫多次;連線會保留在以執行緒為金鑰的快取中。
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 309 def lease_connection lease = connection_lease lease.sticky = true lease.connection ||= checkout end
reap() 連結
復原池中的遺失連線。如果程式設計師忘記在執行緒結束時チェック入連線,或是執行緒意外中斷,都可能發生遺失連線的情況。
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 614 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
release_connection(existing_lease = nil) 連結
發出執行緒已完成使用目前連線的訊號。 release_connection
會釋放連線至執行緒的關聯,並將連線傳回池中。
此方法僅適用於透過 lease_connection
或 with_connection
方法取得的連線,不適用於透過 checkout
方法取得的連線。
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 382 def release_connection(existing_lease = nil) if conn = connection_lease.release checkin conn return true end false end
remove(conn) 連結
從連線池中移除一個連線。該連線將保持開啟和活動狀態,但不再受此池管理。
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 582 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
schema_cache() 連結
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 279 def schema_cache @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self) end
schema_reflection=(schema_reflection) 連結
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 283 def schema_reflection=(schema_reflection) pool_config.schema_reflection = schema_reflection @schema_cache = nil end
stat() 連結
傳回連接池的使用情況統計資料。
ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 671 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
with_connection(prevent_permanent_checkout: false) 連結
將來自連接池的連接讓出給區塊使用。若目前執行緒尚未簽出任何連接,就會從池中簽出連接,讓出給區塊使用,然後在區塊完成時歸還給池。若目前執行緒已簽出連接,例如透過 lease_connection
或 with_connection
,讓出的將會是該現有的連接,而且它不會在區塊結束時自動歸還給池;預期這樣的連接將會由簽出的程式碼妥善歸還給池。
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb, line 399 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