redis_wmrs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 979cef6e3bf745de0a66e2f5e581ec1d1b6a58b1
4
+ data.tar.gz: fccb0412a5a9ec93fd372d2eb7d1b52516c4e902
5
+ SHA512:
6
+ metadata.gz: 4c884afba7b7c9925bbe5a683cd643aac4c0cba2255d64a592dc8ecb6c5246f7d7ca8c07d947a725c2d651861bf75d8d02b6313713d7b61d09f40f100347daa0
7
+ data.tar.gz: 7ce30770eec208e8bbe0a44807be25a3f2d358cf9cab8de51cc40ffd55dad50f4642c1ef7ee1cb51a38fab65ef0e89dd808939737433e56cbd490cd00fc8a052
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in redis_wmrs.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 akima
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # RedisWmrs
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'redis_wmrs'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install redis_wmrs
18
+
19
+ ## Usage
20
+
21
+ ```
22
+
23
+ redis = RedisWmrs.new(
24
+ :master_name => "sentinel_master",
25
+ :sentinels => [
26
+ {:host => "redis01", :port => 26379},
27
+ {:host => "redis02", :port => 26379},
28
+ {:host => "redis03", :port => 26379}
29
+ ])
30
+
31
+ i = -1
32
+ while true
33
+ i += 1
34
+ i = 0 if i > 10
35
+ redis.set("foo", Time.now.to_s) if i == 0
36
+
37
+ header = "[#{Time.now.to_s}] sentinel:#{redis.client.current_sentinel.client.location} redis:#{redis.client.location}"
38
+ begin
39
+ puts "#{header} #{redis.get 'foo'}"
40
+ rescue => e
41
+ puts "#{header} [#{e.class.name}]: #{e.message}"
42
+ end
43
+ sleep 1
44
+ end
45
+
46
+ ```
47
+
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/examples/test.rb ADDED
@@ -0,0 +1,38 @@
1
+ # -*- coding:utf-8 -*-
2
+
3
+ require 'redis_wmrs'
4
+ require 'logger'
5
+
6
+ @redis = RedisWmrs.new({
7
+ :master_name => "sentinel_apisrv",
8
+ :sentinels => [
9
+ {:host => "redis01", :port => 26379},
10
+ {:host => "redis02", :port => 26379},
11
+ {:host => "redis03", :port => 26379}
12
+ ],
13
+ # :logger => Logger.new(STDOUT)
14
+ })
15
+
16
+ def logging
17
+ master = @redis.client.master.location
18
+ master_sentinel = @redis.client.master.current_sentinel.client.location rescue nil
19
+ slave = @redis.client.slave.location
20
+ slave_sentinel = @redis.client.slave.current_sentinel.client.location rescue nil
21
+
22
+ header = "[#{Time.now.to_s}] MASTER:#{master}~#{master_sentinel} SLAVE:#{slave}~#{slave_sentinel}"
23
+ begin
24
+ result = yield
25
+ puts "#{header} #{result}"
26
+ rescue => e
27
+ puts "#{header} [#{e.class.name}]: #{e.message}"
28
+ end
29
+ end
30
+
31
+ i = -1
32
+ while true
33
+ i += 1
34
+ i = 0 if i > 10
35
+ logging{ @redis.set("foo", Time.now.to_s) } if i == 0
36
+ logging{ @redis.get('foo') }
37
+ sleep 1
38
+ end
@@ -0,0 +1,103 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'redis_wmrs'
3
+
4
+ require 'forwardable'
5
+
6
+ class RedisWmrs::Dispatcher
7
+ extend Forwardable
8
+
9
+ MASTER_SLAVE_COMMANDS = [
10
+ :select, :quit,
11
+ ].freeze
12
+
13
+ DEFAULT_SLAVE_COMMANDS = [
14
+ :info, # :sync,
15
+ :dbsize, :lastsave, :time, :ttl, :pttl, :exists, :keys, :randomkey, :type,
16
+ :get, :mget, :getrange, :getbit, :bitcount, :getset, :strlen, :llen,
17
+ :lindex, :srandmember, :sismember, :smembers, :sdiff, :sinter, :sunion,
18
+ :zcard, :zscore, :zrange, :zrevrange, :zrank, :zrevrank, :zrangebyscore,
19
+ :zrevrangebyscore, :zremrangebyscore, :zcount,
20
+ :hlen, :hget, :hmget, :hexists, :hkeys, :hvals, :hgetall,
21
+ # :watch, :unwatch,
22
+ :echo, :ping
23
+ ].freeze
24
+
25
+ attr_reader :master, :slave
26
+ attr_reader :options
27
+
28
+ def initialize(master, slave, options)
29
+ @master, @slave = master, slave
30
+ @both_cmds = options[:both] || MASTER_SLAVE_COMMANDS
31
+ @slave_cmds = options[:slave] || DEFAULT_SLAVE_COMMANDS
32
+ end
33
+
34
+ def ids
35
+ __map__ &:id
36
+ end
37
+
38
+ def locations
39
+ __map__ &:location
40
+ end
41
+
42
+ def db=(db)
43
+ __both__{|c| c.db = db}; return db
44
+ end
45
+
46
+ def logger=(v)
47
+ __both__{|c| c.logger = v}; return v
48
+ end
49
+
50
+ def connect ; __both__{|c| c.connect }; return self; end
51
+ def disconnect; __both__{|c| c.disconnect}; return self; end
52
+ def reconnect ; __both__{|c| c.reconnect }; return self; end
53
+
54
+ def call(command, &block)
55
+ __dispatch__(*command){|c|c.call(command, &block)}
56
+ end
57
+
58
+ def call_loop(command, &block)
59
+ __dispatch__(*command){|c|c.call_loop(command, &block)}
60
+ end
61
+
62
+ def call_with_timeout(command, timeout, &blk)
63
+ __dispatch__(*command){|c|c.call_with_timeout(command, timeout, &blk)}
64
+ end
65
+
66
+ def call_without_timeout(command, &blk)
67
+ __dispatch__(*command){|c|c.call_with_timeout(command, &blk)}
68
+ end
69
+
70
+ # すべてのpublicメソッドの定義後で実行する必要があります
71
+ master_deleted_methods = (Redis::Client.public_instance_methods(false).
72
+ delete_if{|s| s =~ /\A_|\Ainitialize|\=$/}) - self.public_instance_methods(false)
73
+
74
+ def_delegators :@master, *master_deleted_methods
75
+
76
+ private
77
+
78
+ def __both__
79
+ yield(@master)
80
+ yield(@slave)
81
+ end
82
+ def __master__; yield(@master); end
83
+ def __slave__ ; yield(@slave ); end
84
+
85
+ def __map__
86
+ if block_given?
87
+ [yield(@master), yield(@slave)]
88
+ else
89
+ [@master, @slave]
90
+ end
91
+ end
92
+
93
+ def __dispatch__(command, *, &blk)
94
+ m =
95
+ case
96
+ when @slave_cmds.include?(command) then :__slave__
97
+ when @both_cmds.include?(command) then :__both__
98
+ else :__master__
99
+ end
100
+ send(m, &blk)
101
+ end
102
+
103
+ end
@@ -0,0 +1,14 @@
1
+ require 'redis_wmrs'
2
+
3
+ module RedisWmrs
4
+ class Impl < ::Redis
5
+
6
+ def initialize(options = {})
7
+ dispatch_options = options.delete(:dispatch) || options.delete("dispatch") || {}
8
+ super(options.dup) # Redis#initialize
9
+ m = @client # master client
10
+ s = SlaveClient.new(options)
11
+ @original_client = @client = RedisWmrs::Dispatcher.new(m, s, dispatch_options)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,99 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'redis_wmrs'
3
+
4
+ module RedisWmrs
5
+ class SlaveClient < ::Redis::Client
6
+
7
+ # override Redis::Client#connect overwritten by redis-sentinel
8
+ # https://github.com/flyerhzm/redis-sentinel/blob/master/lib/redis-sentinel/client.rb#L24
9
+ def connect
10
+ if sentinel?
11
+ auto_retry_with_timeout do
12
+ discover_slave
13
+ begin
14
+ connect_without_sentinel
15
+ rescue => e
16
+ @failed ||= []
17
+ f = "#{@options[:host]}:#{@options[:port]}"
18
+ @failed.delete(f)
19
+ @failed.push(f) # 必ず末尾に追加する
20
+ puts "@failed: #{@failed.inspect}"
21
+ raise e
22
+ else
23
+ @failed.delete("#{@options[:host]}:#{@options[:port]}") if @failed
24
+ end
25
+ end
26
+ else
27
+ connect_without_sentinel
28
+ end
29
+ end
30
+
31
+ def discover_slave
32
+ while true
33
+ try_next_sentinel
34
+ host_attrs = fetch_slaves
35
+ host_attrs.each do |attrs|
36
+ begin
37
+ host, port = attrs["ip"], attrs["port"]
38
+ if host && port
39
+ # An ip:port pair
40
+ @options.merge!(:host => host, :port => port.to_i, :password => @master_password)
41
+ refresh_sentinels_list
42
+ return
43
+ else
44
+ # A null reply
45
+ end
46
+ rescue Redis::CommandError => e
47
+ raise unless e.message.include?("IDONTKNOW")
48
+ rescue Redis::CannotConnectError
49
+ # faile to connect to current sentinel server
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ MASTER_PRIORITY = 0
56
+
57
+ def fetch_slaves
58
+ preferred_indexes = []
59
+ slave_attrs_array = current_sentinel.sentinel("slaves", @master_name).map{|pairs| Hash[*pairs]}
60
+ # masterと同じサーバで動作しているクライアントの場合、slaveではなく
61
+ # masterに繋ぐべきなのでマスタも接続先に追加します。
62
+ master_attrs_array = slave_attrs_array.map{|h|
63
+ {"ip" => h["master-host"], "port" => h["master-port"], "slave-priority" => MASTER_PRIORITY}
64
+ }.uniq
65
+ attrs_array = slave_attrs_array + master_attrs_array
66
+ [*self.class.ip_and_hostnames, "127.0.0.1", "localhost"].compact.each do |ip_hostname|
67
+ if idx = attrs_array.index{|attrs| attrs["ip"] == ip_hostname}
68
+ preferred_indexes << idx
69
+ end
70
+ end
71
+ # slave-priorityの値が小さいほどmasterに昇格しやすいので、
72
+ # preferredではないslaves群についてはslave-priorityの降順で接続の優先順位を振ります。
73
+ not_preferred = ((0...attrs_array.length).to_a - preferred_indexes).
74
+ sort_by{|i| attrs_array[i]["slave-priority"].to_i}.reverse
75
+ result = (preferred_indexes + not_preferred).map{|i| attrs_array[i]}
76
+ # 接続に失敗した接続先は優先度を下げます
77
+ puts "@failed: #{@failed.inspect}"
78
+ (@failed || []).each do |failed|
79
+ if idx = result.index{|r| failed == "#{r['ip']}:#{r['port']}" }
80
+ result.push(result.delete_at(idx))
81
+ end
82
+ end
83
+ return result
84
+ rescue Exception => e
85
+ puts "[#{e.class}] #{e.message}\n " << e.backtrace.join("\n ")
86
+ raise e
87
+ end
88
+ private :fetch_slaves
89
+
90
+ def self.ip_and_hostnames
91
+ unless @my_hostname
92
+ @my_hostname = Socket::gethostname rescue nil
93
+ @my_ip = IPSocket::getaddress(@my_hostname) rescue nil
94
+ end
95
+ return @my_hostname, @my_ip
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module RedisWmrs
2
+ VERSION = "0.0.1"
3
+ end
data/lib/redis_wmrs.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "redis_wmrs/version"
2
+
3
+ require "redis"
4
+ require 'redis-sentinel'
5
+
6
+ module RedisWmrs
7
+
8
+ autoload :Impl , 'redis_wmrs/impl'
9
+ autoload :Dispatcher , 'redis_wmrs/dispatcher'
10
+ autoload :SlaveClient, 'redis_wmrs/slave_client'
11
+
12
+ def self.new(options = {})
13
+ RedisWmrs::Impl.new(options)
14
+ end
15
+
16
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'redis_wmrs/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "redis_wmrs"
8
+ spec.version = RedisWmrs::VERSION
9
+ spec.authors = ["akima"]
10
+ spec.email = ["akima@groovenauts.jp"]
11
+ spec.description = %q{redis client to write to master node and read from slave node}
12
+ spec.summary = %q{redis client to write to master node and read from slave node}
13
+ spec.homepage = "https://github.com/groovenauts/redis_wmrs"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "redis" , "~> 3.0.0"
22
+ spec.add_runtime_dependency "redis-sentinel", "~> 1.4.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "pry"
28
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe RedisWmrs do
4
+ it 'should have a version number' do
5
+ RedisWmrs::VERSION.should_not be_nil
6
+ end
7
+
8
+ it 'should do something useful' do
9
+ false.should be_true
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'redis_wmrs'
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis_wmrs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - akima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis-sentinel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 1.4.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: redis client to write to master node and read from slave node
98
+ email:
99
+ - akima@groovenauts.jp
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - .travis.yml
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - examples/test.rb
112
+ - lib/redis_wmrs.rb
113
+ - lib/redis_wmrs/dispatcher.rb
114
+ - lib/redis_wmrs/impl.rb
115
+ - lib/redis_wmrs/slave_client.rb
116
+ - lib/redis_wmrs/version.rb
117
+ - redis_wmrs.gemspec
118
+ - spec/redis_wmrs_spec.rb
119
+ - spec/spec_helper.rb
120
+ homepage: https://github.com/groovenauts/redis_wmrs
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - '>='
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.0.3
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: redis client to write to master node and read from slave node
144
+ test_files:
145
+ - spec/redis_wmrs_spec.rb
146
+ - spec/spec_helper.rb
147
+ has_rdoc: