mongo-ruby-driver/lib/mongo/repl_set_connection.rb

343 lines
11 KiB
Ruby
Raw Normal View History

# encoding: UTF-8
# --
2011-01-17 17:26:32 +00:00
# Copyright (C) 2008-2011 10gen Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ++
module Mongo
2010-12-15 19:14:06 +00:00
# Instantiates and manages connections to a MongoDB replica set.
2010-12-13 19:07:32 +00:00
class ReplSetConnection < Connection
2011-08-16 20:47:07 +00:00
attr_reader :nodes, :secondaries, :arbiters, :read_pool, :secondary_pools,
:replica_set_name, :ping_ranges
2010-12-15 19:14:06 +00:00
# Create a connection to a MongoDB replica set.
#
# Once connected to a replica set, you can find out which nodes are primary, secondary, and
# arbiters with the corresponding accessors: Connection#primary, Connection#secondaries, and
# Connection#arbiters. This is useful if your application needs to connect manually to nodes other
# than the primary.
#
2011-03-12 13:40:29 +00:00
# @param [Array] args A list of host-port pairs to be used as seed nodes followed by a
# hash containing any options. See the examples below for exactly how to use the constructor.
2010-12-15 19:14:06 +00:00
#
# @option options [String] :rs_name (nil) The name of the replica set to connect to. You
# can use this option to verify that you're connecting to the right replica set.
2010-12-15 19:14:06 +00:00
# @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
# propogated to DB objects instantiated off of this Connection. This
# default can be overridden upon instantiation of any DB by explicity setting a :safe value
# on initialization.
# @option options [Boolean] :read_secondary(false) If true, a random secondary node will be chosen,
# and all reads will be directed to that node.
# @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
# @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
# connection pool. Note: this setting is relevant only for multi-threaded applications.
# @option options [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
2010-12-15 19:14:06 +00:00
# this is the number of seconds to wait for a new connection to be released before throwing an exception.
# Note: this setting is relevant only for multi-threaded applications.
# @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
# Disabled by default.
# @option opts [Float] :connect_timeout (nil) The number of seconds to wait before timing out a
# connection attempt.
2010-12-15 19:14:06 +00:00
#
2011-03-12 13:40:29 +00:00
# @example Connect to a replica set and provide two seed nodes. Note that the number of seed nodes does
# not have to be equal to the number of replica set members. The purpose of seed nodes is to permit
# the driver to find at least one replica set member even if a member is down.
2010-12-15 19:14:06 +00:00
# ReplSetConnection.new(['localhost', 30000], ['localhost', 30001])
#
# @example Connect to a replica set providing two seed nodes and ensuring a connection to the replica set named 'prod':
# ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :rs_name => 'prod')
#
# @example Connect to a replica set providing two seed nodes and allowing reads from a secondary node:
2010-12-15 19:14:06 +00:00
# ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :read_secondary => true)
#
# @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
#
# @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
# driver fails to connect to a replica set with that name.
def initialize(*args)
if args.last.is_a?(Hash)
2010-12-13 19:07:32 +00:00
opts = args.pop
else
opts = {}
end
unless args.length > 0
raise MongoArgumentError, "A ReplSetConnection requires at least one node."
end
2011-08-16 20:47:07 +00:00
# Get the list of seed nodes
@seeds = args
2011-08-16 20:47:07 +00:00
# The members of the replica set, stored as instances of Mongo::Node.
@nodes = []
2011-08-16 20:47:07 +00:00
# Connection pool for primay node
@primary = nil
@primary_pool = nil
# Connection pools for each secondary node
2011-08-16 20:47:07 +00:00
@secondaries = []
@secondary_pools = []
2011-08-16 20:47:07 +00:00
# The secondary pool to which we'll be sending reads.
@read_pool = nil
2011-08-16 20:47:07 +00:00
# A list of arbiter address (for client information only)
@arbiters = []
# An array mapping secondaries by proximity
@ping_ranges = Array.new(3) { |i| Array.new }
2010-12-13 19:07:32 +00:00
# Are we allowing reads from secondaries?
@read_secondary = opts.fetch(:read_secondary, false)
2011-08-16 20:47:07 +00:00
# Replica set name
if opts[:rs_name]
warn ":rs_name option has been deprecated and will be removed in 2.0. " +
"Please use :name instead."
@replica_set_name = opts[:rs_name]
else
@replica_set_name = opts[:name]
end
2010-12-13 19:07:32 +00:00
setup(opts)
end
2011-08-16 20:47:07 +00:00
# Use the provided seed nodes to initiate a connection
# to the replica set.
def connect
2011-08-16 20:47:07 +00:00
connect_to_members
initialize_pools
if connected?
choose_node_for_reads
update_seed_list
BSON::BSON_CODER.update_max_bson_size(self)
else
2011-08-16 20:47:07 +00:00
close
if @primary.nil?
raise ConnectionFailure, "Failed to connect to primary node."
2011-08-16 20:47:07 +00:00
else
raise ConnectionFailure, "Failed to connect to any given member."
end
end
end
2011-08-16 20:47:07 +00:00
def connected?
@primary_pool || (!@secondary_pools.empty? && @read_secondary)
2011-08-16 20:47:07 +00:00
end
# @deprecated
def connecting?
2011-08-16 20:47:07 +00:00
false
end
# The replica set primary's host name.
#
# @return [String]
def host
super
end
# The replica set primary's port.
#
# @return [Integer]
def port
super
end
# Determine whether we're reading from a primary node. If false,
# this connection connects to a secondary node and @read_secondaries is true.
#
# @return [Boolean]
def read_primary?
!@read_pool
end
alias :primary? :read_primary?
# Close the connection to the database.
def close
super
@nodes.each do |member|
2011-08-16 20:47:07 +00:00
member.disconnect
end
@nodes = []
@read_pool = nil
@secondary_pools.each do |pool|
pool.close
end
@secondaries = []
@secondary_pools = []
@arbiters = []
end
# If a ConnectionFailure is raised, this method will be called
# to close the connection and reset connection values.
# @deprecated
def reset_connection
close
warn "ReplSetConnection#reset_connection is now deprecated. " +
"Use ReplSetConnection#close instead."
end
# Is it okay to connect to a slave?
#
# @return [Boolean]
def slave_ok?
@read_secondary
end
def authenticate_pools
super
@secondary_pools.each do |pool|
pool.authenticate_existing
end
end
def logout_pools(db)
super
@secondary_pools.each do |pool|
pool.logout_existing(db)
end
end
private
2011-08-16 20:47:07 +00:00
# Iterate through the list of provided seed
# nodes until we've gotten a response from the
# replica set we're trying to connect to.
#
# If we don't get a response, raise an exception.
def get_valid_seed_node
@seeds.each do |seed|
node = Mongo::Node.new(self, seed)
if node.connect && node.set_config
return node
end
end
raise ConnectionFailure, "Cannot connect to a replica set using seeds " +
"#{@seeds.map {|s| "#{s[0]}:#{s[1]}" }.join(', ')}"
end
2011-08-16 20:47:07 +00:00
# Connect to each member of the replica set
# as reported by the given seed node, and cache
# those connections in the @nodes array.
2011-08-16 20:47:07 +00:00
def connect_to_members
seed = get_valid_seed_node
2011-08-16 20:47:07 +00:00
seed.node_list.each do |host|
node = Mongo::Node.new(self, host)
if node.connect && node.set_config
@nodes << node
2011-08-16 20:47:07 +00:00
end
end
end
2011-08-16 20:47:07 +00:00
# Initialize the connection pools to the primary and secondary nodes.
def initialize_pools
if @nodes.empty?
2011-08-16 20:47:07 +00:00
raise ConnectionFailure, "Failed to connect to any given member."
end
@arbiters = @nodes.first.arbiters
2011-08-16 20:47:07 +00:00
@nodes.each do |member|
2011-08-16 20:47:07 +00:00
if member.primary?
@primary = member.host_port
@primary_pool = Pool.new(self, member.host, member.port,
:size => @pool_size,
:timeout => @timeout,
:node => member)
2011-08-16 20:47:07 +00:00
elsif member.secondary? && !@secondaries.include?(member.host_port)
@secondaries << member.host_port
@secondary_pools << Pool.new(self, member.host, member.port,
:size => @pool_size,
:timeout => @timeout,
:node => member)
end
end
end
# Pick a node from the set of possible secondaries.
# If more than one node is available, use the ping
# time to figure out which nodes to choose from.
def choose_node_for_reads
return if @secondary_pools.empty?
if @secondary_pools.size == 1
@read_pool = @secondary_pools.first
else
@secondary_pools.each do |pool|
case pool.ping_time
when 0..150
@ping_ranges[0] << pool
when 150..1000
@ping_ranges[1] << pool
else
@ping_ranges[2] << pool
end
end
for list in @ping_ranges do
break if !list.empty?
end
@read_pool = list[rand(list.length)]
end
end
def update_seed_list
@seeds = @nodes.map { |n| n.host_port }
end
# Checkout a socket for reading (i.e., a secondary node).
def checkout_reader
connect unless connected?
if @read_secondary && @read_pool
@read_pool.checkout
else
checkout_writer
end
end
# Checkout a socket for writing (i.e., a primary node).
def checkout_writer
connect unless connected?
@primary_pool.checkout
end
# Checkin a socket used for reading.
def checkin_reader(socket)
if @read_secondary && @read_pool
@read_pool.checkin(socket)
else
checkin_writer(socket)
end
end
# Checkin a socket used for writing.
def checkin_writer(socket)
if @primary_pool
@primary_pool.checkin(socket)
end
end
end
end