master_slave 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/master_slave/base.rb +67 -0
- data/lib/master_slave/config.rb +38 -0
- data/lib/master_slave/connection_handler.rb +47 -0
- data/lib/master_slave/railtie.rb +20 -0
- data/lib/master_slave/runtime_registry.rb +32 -0
- data/lib/master_slave/version.rb +3 -0
- data/lib/master_slave.rb +6 -0
- data/lib/rails/generators/config_generator.rb +34 -0
- data/lib/rails/generators/templates/shards.yml +54 -0
- metadata +75 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
module MasterSlave
|
2
|
+
module Base
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
cattr_accessor :slave_connection_names
|
7
|
+
self.slave_connection_names = []
|
8
|
+
|
9
|
+
class << self
|
10
|
+
alias_method :connection_without_master_slave, :connection
|
11
|
+
alias_method :connection, :connection_with_master_slave
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def slave(&block)
|
18
|
+
slave_name = select_slave_connection_name
|
19
|
+
if slave_name.blank?
|
20
|
+
block.call
|
21
|
+
else
|
22
|
+
with_slave(slave_name) do
|
23
|
+
block.call
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def using(slave_name, &block)
|
29
|
+
with_slave(slave_name) do
|
30
|
+
block.call
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def connection_with_master_slave
|
35
|
+
slave_block = MasterSlave::RuntimeRegistry.slave_block
|
36
|
+
current_slave_name = MasterSlave::RuntimeRegistry.current_slave_name
|
37
|
+
|
38
|
+
if defined?(Rails) && Rails.env.test?
|
39
|
+
connection_without_master_slave
|
40
|
+
elsif slave_block && current_slave_name
|
41
|
+
pool_name = MasterSlave::ConnectionHandler.connection_pool_name(current_slave_name)
|
42
|
+
ar_proxy = MasterSlave::ConnectionHandler::ArProxy.new(pool_name)
|
43
|
+
ActiveRecord::Base.connection_handler.retrieve_connection(ar_proxy)
|
44
|
+
else
|
45
|
+
connection_without_master_slave
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def select_slave_connection_name
|
52
|
+
ActiveRecord::Base.slave_connection_names[rand(ActiveRecord::Base.slave_connection_names.size)]
|
53
|
+
end
|
54
|
+
|
55
|
+
def with_slave(slave_name)
|
56
|
+
slave_name = slave_name.to_s.strip
|
57
|
+
raise "#{slave_name} not exist" unless ActiveRecord::Base.slave_connection_names.include?(slave_name)
|
58
|
+
MasterSlave::RuntimeRegistry.current_slave_name = slave_name
|
59
|
+
MasterSlave::RuntimeRegistry.slave_block = true
|
60
|
+
yield
|
61
|
+
ensure
|
62
|
+
MasterSlave::RuntimeRegistry.current_slave_name = nil
|
63
|
+
MasterSlave::RuntimeRegistry.slave_block = false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module MasterSlave
|
2
|
+
|
3
|
+
def self.config
|
4
|
+
@config ||= MasterSlave::Configuration.new
|
5
|
+
end
|
6
|
+
|
7
|
+
class Configuration
|
8
|
+
cattr_accessor :config_file
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@content = load_config
|
12
|
+
end
|
13
|
+
|
14
|
+
def slave_names
|
15
|
+
raise "#{Rails.env}'s slave config not exist" if @content.blank?
|
16
|
+
@shard_names ||= @content.keys.sort
|
17
|
+
end
|
18
|
+
|
19
|
+
def slave_config(slave_name)
|
20
|
+
@content[slave_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.config_file
|
24
|
+
@@config_file ||= File.join(Rails.root, 'config', 'shards.yml')
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def load_config
|
30
|
+
if File.exist?(self.class.config_file)
|
31
|
+
hash_config = YAML.load(ERB.new(File.open(self.class.config_file).read).result)
|
32
|
+
HashWithIndifferentAccess.new(hash_config)[Rails.env]
|
33
|
+
else
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module MasterSlave
|
3
|
+
class ConnectionHandler
|
4
|
+
|
5
|
+
class ArProxy
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def setup
|
16
|
+
setup_connection
|
17
|
+
end
|
18
|
+
|
19
|
+
def connection_pool_name(slave_name)
|
20
|
+
"master_slave_#{slave_name.to_s.strip}"
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def setup_connection
|
26
|
+
ActiveRecord::Base.slave_connection_names ||= []
|
27
|
+
MasterSlave.config.slave_names.each do |slave_name|
|
28
|
+
ActiveRecord::Base.slave_connection_names << slave_name.to_s.strip
|
29
|
+
configuration = MasterSlave.config.slave_config(slave_name).symbolize_keys
|
30
|
+
unless configuration.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
31
|
+
|
32
|
+
adapter_method = "#{configuration[:adapter]}_connection"
|
33
|
+
unless ActiveRecord::Base.respond_to?(adapter_method)
|
34
|
+
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
|
35
|
+
end
|
36
|
+
|
37
|
+
# remove_connection 时会调用方法内部 ar_proxy.name
|
38
|
+
ar_proxy = ArProxy.new(connection_pool_name(slave_name))
|
39
|
+
ActiveRecord::Base.remove_connection(ar_proxy)
|
40
|
+
|
41
|
+
spec = ActiveRecord::Base::ConnectionSpecification.new(configuration, adapter_method)
|
42
|
+
ActiveRecord::Base.connection_handler.establish_connection ar_proxy.name, spec
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module MasterSlave
|
2
|
+
class Railtie < Rails::Railtie # :nodoc:
|
3
|
+
|
4
|
+
config.after_initialize do
|
5
|
+
if File.exist?(MasterSlave::Configuration.config_file)
|
6
|
+
puts "\033[32mmaster_slave is on!\033[0m"
|
7
|
+
ActiveRecord::Base.send :include, MasterSlave::Base
|
8
|
+
MasterSlave::ConnectionHandler.setup
|
9
|
+
else
|
10
|
+
puts "\033[31mNo such file #{MasterSlave::Configuration.config_file}\033[0m"
|
11
|
+
puts "\033[31mPlease execute `rails g master_slave:config`\033[0m"
|
12
|
+
puts "\033[31mmaster_slave is off!\033[0m"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
generators do
|
17
|
+
require File.expand_path('../../rails/generators/config_generator', __FILE__)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_support/per_thread_registry'
|
3
|
+
rescue LoadError
|
4
|
+
module ActiveSupport
|
5
|
+
module PerThreadRegistry
|
6
|
+
protected
|
7
|
+
|
8
|
+
def method_missing(name, *args, &block) # :nodoc:
|
9
|
+
# Caches the method definition as a singleton method of the receiver.
|
10
|
+
define_singleton_method(name) do |*a, &b|
|
11
|
+
per_thread_registry_instance.public_send(name, *a, &b)
|
12
|
+
end
|
13
|
+
|
14
|
+
send(name, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def per_thread_registry_instance
|
20
|
+
Thread.current[name] ||= new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module MasterSlave
|
27
|
+
class RuntimeRegistry
|
28
|
+
extend ActiveSupport::PerThreadRegistry
|
29
|
+
|
30
|
+
attr_accessor :slave_block, :current_slave_name
|
31
|
+
end
|
32
|
+
end
|
data/lib/master_slave.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require "rails/generators/named_base"
|
2
|
+
module MasterSlave
|
3
|
+
module Generators
|
4
|
+
class ConfigGenerator < Rails::Generators::Base
|
5
|
+
desc "Creates a MasterSlave configuration file at config/shards.yml"
|
6
|
+
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
argument :database_name, type: :string, optional: true
|
10
|
+
|
11
|
+
def app_name
|
12
|
+
Rails::Application.subclasses.first.parent.to_s.underscore
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_config_file
|
16
|
+
template 'shards.yml', File.join(Rails.root, 'config', "shards.yml")
|
17
|
+
end
|
18
|
+
|
19
|
+
def mysql_socket
|
20
|
+
@mysql_socket ||= [
|
21
|
+
"/tmp/mysql.sock", # default
|
22
|
+
"/var/run/mysqld/mysqld.sock", # debian/gentoo
|
23
|
+
"/var/tmp/mysql.sock", # freebsd
|
24
|
+
"/var/lib/mysql/mysql.sock", # fedora
|
25
|
+
"/opt/local/lib/mysql/mysql.sock", # fedora
|
26
|
+
"/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql
|
27
|
+
"/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4
|
28
|
+
"/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5
|
29
|
+
"/opt/lampp/var/mysql/mysql.sock" # xampp for linux
|
30
|
+
].find { |f| File.exist?(f) } unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# MySQL. Versions 4.1 and 5.0 are recommended.
|
2
|
+
#
|
3
|
+
# Install the MYSQL driver
|
4
|
+
# gem install mysql2
|
5
|
+
#
|
6
|
+
# Ensure the MySQL gem is defined in your Gemfile
|
7
|
+
# gem 'mysql2'
|
8
|
+
#
|
9
|
+
# And be sure to use new-style password hashing:
|
10
|
+
# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
|
11
|
+
development:
|
12
|
+
slave:
|
13
|
+
adapter: mysql2
|
14
|
+
encoding: utf8
|
15
|
+
database: <%= database_name || app_name %>_development_slave
|
16
|
+
pool: 5
|
17
|
+
username: root
|
18
|
+
password:
|
19
|
+
<% if mysql_socket -%>
|
20
|
+
socket: <%= mysql_socket %>
|
21
|
+
<% else -%>
|
22
|
+
host: localhost
|
23
|
+
<% end -%>
|
24
|
+
|
25
|
+
# Warning: The database defined as "test" will be erased and
|
26
|
+
# re-generated from your development database when you run "rake".
|
27
|
+
# Do not set this db to the same as development or production.
|
28
|
+
test:
|
29
|
+
slave:
|
30
|
+
adapter: mysql2
|
31
|
+
encoding: utf8
|
32
|
+
database: <%= database_name || app_name %>_test_slave
|
33
|
+
pool: 5
|
34
|
+
username: root
|
35
|
+
password:
|
36
|
+
<% if mysql_socket -%>
|
37
|
+
socket: <%= mysql_socket %>
|
38
|
+
<% else -%>
|
39
|
+
host: localhost
|
40
|
+
<% end -%>
|
41
|
+
|
42
|
+
production:
|
43
|
+
slave:
|
44
|
+
adapter: mysql2
|
45
|
+
encoding: utf8
|
46
|
+
database: <%= database_name || app_name %>_production_slave
|
47
|
+
pool: 5
|
48
|
+
username: root
|
49
|
+
password:
|
50
|
+
<% if mysql_socket -%>
|
51
|
+
socket: <%= mysql_socket %>
|
52
|
+
<% else -%>
|
53
|
+
host: localhost
|
54
|
+
<% end -%>
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: master_slave
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tumayun, Zhangyuan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-11-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
- - <
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 3.2.0
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.0
|
33
|
+
- - <
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 3.2.0
|
36
|
+
description: mysql separate read and write.
|
37
|
+
email: tumayun.2010@gmail.com
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- lib/master_slave.rb
|
43
|
+
- lib/rails/generators/templates/shards.yml
|
44
|
+
- lib/rails/generators/config_generator.rb
|
45
|
+
- lib/master_slave/version.rb
|
46
|
+
- lib/master_slave/railtie.rb
|
47
|
+
- lib/master_slave/config.rb
|
48
|
+
- lib/master_slave/connection_handler.rb
|
49
|
+
- lib/master_slave/runtime_registry.rb
|
50
|
+
- lib/master_slave/base.rb
|
51
|
+
homepage: https://github.com/tumayun/master_slave
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.8.23
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: master_slave.
|
75
|
+
test_files: []
|