Est-il possible de postuler à mon propre projet concernant la prise en charge de plusieurs bases de données implémentées à partir de Rails 6.0? Je suivrai la mise en œuvre pour juger que
https://railsguides.jp/active_record_multiple_databases.html#コネクションの自動切り替えを有効にする
La fonction de commutation automatique permet à l'application de passer du primaire au réplica ou du réplica au primaire en fonction du verbe HTTP et de la présence ou de l'absence de l'écriture la plus récente. Lorsqu'une application reçoit une requête POST, PUT, DELETE ou PATCH, elle écrit automatiquement dans le primaire. L'application lira à partir du primaire jusqu'à ce que le temps spécifié s'écoule après l'écriture. Lorsque l'application reçoit une demande GET ou HEAD, elle lit à partir du réplica s'il n'y a pas d'écriture récente.
Il est précisé qu'il est jugé par deux types de jugement par méthode HTTP + jugement par temps d'écriture La méthode de réglage est décrite ci-dessous
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
Commençons par la partie application du middleware
activerecord-6.0.3.2/lib/active_record/railtie.rb
class Railtie < Rails::Railtie # :nodoc:
config.active_record = ActiveSupport::OrderedOptions.new
initializer "active_record.database_selector" do
if options = config.active_record.delete(:database_selector)
resolver = config.active_record.delete(:database_resolver)
operations = config.active_record.delete(:database_resolver_context)
config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
end
end
--Exécuté si database_selector
est défini
--database_resolver
et database_resolver_context
sont maintenant juste passés
Ensuite, nous suivrons l'implémentation de la partie middleware
https://api.rubyonrails.org/classes/ActiveSupport/OrderedOptions.html
Un mécanisme qui permet d'accéder à Hash par points Renvoie nil s'il n'est pas défini avec delete
Ruby2.6.5 pry(main)> x = ActiveSupport::OrderedOptions.new
=> {}
Ruby2.6.5 pry(main)> x.delete(:hoge)
=> nil
activerecord-6.0.3.2/lib/active_record/middleware/database_selector.rb
module ActiveRecord
module Middleware
class DatabaseSelector
def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
@app = app
@resolver_klass = resolver_klass || Resolver
@context_klass = context_klass || Resolver::Session
@options = options
end
attr_reader :resolver_klass, :context_klass, :options
def call(env)
request = ActionDispatch::Request.new(env)
select_database(request) do
@app.call(env)
end
end
--Si database_resolver
n'est pas défini, DatabaseSelector :: Resolver
est défini.
--Si database_resolver_context
n'est pas défini, DatabaseSelector :: Resolver :: Session
est défini.
Ensuite, jetons un œil à l'important # select_database
DatabaseSelector#select_database
module ActiveRecord
module Middleware
class DatabaseSelector
private
def select_database(request, &blk)
context = context_klass.call(request)
resolver = resolver_klass.call(context, options)
if reading_request?(request)
resolver.read(&blk)
else
resolver.write(&blk)
end
end
def reading_request?(request)
request.get? || request.head?
end
end
end
end
Puisque «context» est exécuté en premier et «résolveur» est créé sur cette base, vérifions d'abord «context».
Resolver::Session.call
class Resolver # :nodoc:
class Session # :nodoc:
def self.call(request)
new(request.session)
end
def initialize(session)
@session = session
end
attr_reader :session
.call
est juste # new
, ici seule la session de ʻActionDispatch :: Requestest définie Alors vérifiez ensuite le
.call` du résolveur
Resolver.call
class Resolver # :nodoc:
SEND_TO_REPLICA_DELAY = 2.seconds
def self.call(context, options = {})
new(context, options)
end
def initialize(context, options = {})
@context = context
@options = options
@delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
@instrumenter = ActiveSupport::Notifications.instrumenter
end
attr_reader :context, :delay, :instrumenter
Je suis juste nouveau ici aussi, donc je vais lire un peu plus l'original DatabaseSelector # select_database
.
def select_database(request, &blk)
context = context_klass.call(request)
resolver = resolver_klass.call(context, options)
if reading_request?(request)
resolver.read(&blk)
else
resolver.write(&blk)
end
end
def reading_request?(request)
request.get? || request.head?
end
Ici pour GET ou HEAD => lire Sinon => écrire Exécute le traitement du résolveur en tant que (le résolveur semble faire d'autres jugements)
Commençons par lire
Resolver#read
class Resolver # :nodoc:
def read(&blk)
if read_from_primary?
read_from_primary(&blk)
else
read_from_replica(&blk)
end
end
private
def read_from_primary(&blk)
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
instrumenter.instrument("database_selector.active_record.read_from_primary") do
yield
end
end
end
def read_from_replica(&blk)
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do
instrumenter.instrument("database_selector.active_record.read_from_replica") do
yield
end
end
end
Il semble que le processus se déroule après avoir notifié par instrument le rôle à utiliser. Alors, qu'est-ce qui est jugé par «# read_from_primary?»?
private
def read_from_primary?
!time_since_last_write_ok?
end
def send_to_replica_delay
delay
end
def time_since_last_write_ok?
Time.now - context.last_write_timestamp >= send_to_replica_delay
end
context
La soi-disant session # last_write_timestamp
est
Si le délai du premier config.active_record.database_selector = {delay: 2.seconds}
n'est pas passé, définissez-le sur primaire.
Si le délai est passé, la réplique sera visualisée.
Vérifiez donc le # last_write_timestamp
de la session
class Session # :nodoc:
# Converts milliseconds since epoch timestamp into a time object.
def self.convert_timestamp_to_time(timestamp)
timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
end
def last_write_timestamp
self.class.convert_timestamp_to_time(session[:last_write])
end
Changez simplement sesion [: last_write]
en un objet temporel
C'est la fin de la lecture, alors écrivez
Resolver#write
class Resolver # :nodoc:
def write(&blk)
write_to_primary(&blk)
end
private
def write_to_primary(&blk)
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
yield
ensure
context.update_last_write_timestamp
end
end
end
C'est juste l'écriture dans le rôle d'écriture
class Session # :nodoc:
def self.convert_time_to_timestamp(time)
time.to_i * 1000 + time.usec / 1000
end
def update_last_write_timestamp
session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
end
Et donnez l'horodatage actuel à session [: last_write]
Comme prévu, c'est Rails. Cela a été rendu très facile à comprendre.
Après tout, il n'est traité que sur la base de ActionDispatch :: Request, et si vous réécrivez à la fois # read
et # write
de Resolver
Il me semblait que je pouvais écrire tout ce que je voulais, pas seulement GET and HEAD.
Le jugement par en-tête, adresse IP, hôte local, etc. peut également être utilisé car ce sont les informations dont dispose Request. Je pensais que je pouvais faire beaucoup.
Recommended Posts