joffice_redis 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENSE-2.0.txt +202 -0
- data/README.markdown +11 -0
- data/Rakefile +29 -0
- data/lib/joffice_redis.rb +35 -0
- data/lib/joffice_redis/cache_model.rb +205 -0
- data/lib/joffice_redis/hash_table.rb +73 -0
- data/lib/joffice_redis/marshal.rb +65 -0
- data/lib/joffice_redis/model_factory.rb +417 -0
- data/lib/joffice_redis/redis_client_base.rb +121 -0
- data/lib/joffice_redis/redis_manager.rb +213 -0
- data/lib/joffice_redis/redis_method_factory.rb +445 -0
- data/lib/joffice_redis/storages/presence_redis.rb +80 -0
- metadata +132 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright 2010 Denis Kokorin <virkdi@mail.ru>
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
|
19
|
+
require 'joffice/kernel/array'
|
20
|
+
|
21
|
+
module JOffice
|
22
|
+
class RedisClientBase
|
23
|
+
|
24
|
+
DEFAULT_KEYS_TO_SAVE= [:id].freeze
|
25
|
+
|
26
|
+
def [](k)
|
27
|
+
db.get(key(k))
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(k, value)
|
31
|
+
db.set!(key(k), value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def exists?(k)
|
35
|
+
db.exists?(key(k))
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete_all
|
39
|
+
db.del!(db.keys("#{prefix}*"))
|
40
|
+
end
|
41
|
+
|
42
|
+
def db_name
|
43
|
+
raise "Missed db name in class #{self}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def db
|
47
|
+
@db||=JOffice::RedisManager.instance.open_db_em(db_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def prefix
|
51
|
+
''
|
52
|
+
end
|
53
|
+
|
54
|
+
def key(hash_key)
|
55
|
+
"#{prefix}#{hash_key.to_s.gsub(/ /,'_')}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def keys_to_save
|
59
|
+
DEFAULT_KEYS_TO_SAVE
|
60
|
+
end
|
61
|
+
|
62
|
+
def flush_db
|
63
|
+
db.flush_db!
|
64
|
+
end
|
65
|
+
|
66
|
+
def update_attribute(id, key, value)
|
67
|
+
db.lset!("#{prefix}#{id}", keys_to_save.index(key), Marshal.dump(value))
|
68
|
+
end
|
69
|
+
|
70
|
+
def set(id, value, raw=true)
|
71
|
+
db.set!(key(id), raw ? value : Marshal.dump(value))
|
72
|
+
end
|
73
|
+
|
74
|
+
def get(id, raw=true)
|
75
|
+
value=db.get(key(id))
|
76
|
+
((raw || !value) ? value : Marshal.load(value))
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_key(model)
|
80
|
+
key(model_id(model))
|
81
|
+
end
|
82
|
+
|
83
|
+
def model_id(model)
|
84
|
+
model.id
|
85
|
+
end
|
86
|
+
|
87
|
+
def update_attributes(model, attributes)
|
88
|
+
id=model_id(model)
|
89
|
+
attributes.each do |name|
|
90
|
+
update_attribute(id, name, model.send(name))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_or_update(model)
|
95
|
+
id=build_key(model)
|
96
|
+
keys_to_save.each do |key|
|
97
|
+
db.tail_push!(id, Marshal.dump(model.send(key)))
|
98
|
+
end
|
99
|
+
db.ltrim!(id, 0, keys_to_save.length)
|
100
|
+
end
|
101
|
+
|
102
|
+
def load_by_range(key, ranges)
|
103
|
+
result=[]
|
104
|
+
ranges.each do |range|
|
105
|
+
db.lrange(key,range.first,range.last).each {|v| result << Marshal.load(v); }
|
106
|
+
end
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
110
|
+
def load_values(id, *keys)
|
111
|
+
load_by_range(key(id), to_index_range(keys))
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_index_range(keys)
|
115
|
+
keys.flatten.map {|key| keys_to_save.index(key.to_sym) }.to_ranges
|
116
|
+
end
|
117
|
+
private :to_index_range
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright 2010 Denis Kokorin <virkdi@mail.ru>
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
|
19
|
+
require 'thread'
|
20
|
+
|
21
|
+
module JOffice
|
22
|
+
class RedisManager
|
23
|
+
include Singleton
|
24
|
+
attr_reader :db_prefix
|
25
|
+
include JOffice::FiberEvents
|
26
|
+
def error_callback
|
27
|
+
@error_callback||= lambda {|code|
|
28
|
+
#$log.error("Redis"){"[RedisEM]Error code: #{code}"}
|
29
|
+
puts "[RedisEM]Error code: #{code}";
|
30
|
+
#JOffice::LogOutputter.flush;
|
31
|
+
#EM.next_tick{EM.stop}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def flush_all
|
36
|
+
open_redis.flush_all
|
37
|
+
end
|
38
|
+
|
39
|
+
def increment_key
|
40
|
+
:"db.increment"
|
41
|
+
end
|
42
|
+
|
43
|
+
private :increment_key
|
44
|
+
|
45
|
+
def init_ping_timer
|
46
|
+
@@ping_fiber||=Fiber.new{
|
47
|
+
while Fiber.yield
|
48
|
+
p "[Redis] ping"
|
49
|
+
(@@redis_em||={}).values.each do |r|
|
50
|
+
r.exists!('0')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
}
|
54
|
+
EventMachine::PeriodicTimer.new(10.minutes) do
|
55
|
+
@@ping_fiber.resume true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
private :init_ping_timer
|
59
|
+
|
60
|
+
def start_ping_timer
|
61
|
+
@@ping_timer_thread||=init_ping_timer
|
62
|
+
end
|
63
|
+
private :start_ping_timer
|
64
|
+
|
65
|
+
def config=(v)
|
66
|
+
v.symbolize_keys!
|
67
|
+
@config=v
|
68
|
+
@db_prefix=(config[:prefix] || 'test')
|
69
|
+
p "DB_PREFIX: #{db_prefix}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def load_all_names(redis)
|
73
|
+
@db_names={}
|
74
|
+
a=redis.keys('db:*')
|
75
|
+
a.each do |name|
|
76
|
+
@db_names[name]=redis.get(name)
|
77
|
+
end if a
|
78
|
+
end
|
79
|
+
private :load_all_names
|
80
|
+
|
81
|
+
def config
|
82
|
+
@config||={}
|
83
|
+
end
|
84
|
+
|
85
|
+
def logger=(v)
|
86
|
+
@logger=v
|
87
|
+
end
|
88
|
+
|
89
|
+
def logger
|
90
|
+
@logger||=nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def db_names
|
94
|
+
@db_names||={}
|
95
|
+
end
|
96
|
+
|
97
|
+
private :db_names
|
98
|
+
|
99
|
+
def last_access
|
100
|
+
@last_access||={}
|
101
|
+
end
|
102
|
+
private :last_access
|
103
|
+
|
104
|
+
#def new_redis(db=1)
|
105
|
+
# ::Redis.new({:logger=>logger,:db=>db}.merge(config))
|
106
|
+
#end
|
107
|
+
|
108
|
+
#def get_or_open_redis_instance(db=1, forse=false)
|
109
|
+
# key=:"redisdb_#{db_prefix}_#{db}"
|
110
|
+
#
|
111
|
+
# if forse
|
112
|
+
# p "Reopen db #{db}"
|
113
|
+
# Thread.current[key]=new_redis(db)
|
114
|
+
# else
|
115
|
+
# Thread.current[key]||=new_redis(db)
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
#end
|
119
|
+
#private :get_or_open_redis_instance
|
120
|
+
|
121
|
+
def force_open_em_redis(db)
|
122
|
+
redis = EM::Protocols::Redis.connect((config[:host] || '127.0.0.1'), (config[:port] || 6379).to_i)
|
123
|
+
redis.on_error(&error_callback)
|
124
|
+
redis.select(db)
|
125
|
+
redis
|
126
|
+
end
|
127
|
+
private :force_open_em_redis
|
128
|
+
|
129
|
+
def redis_em
|
130
|
+
@@redis_em||=Hash.new { |hash, id| hash[id] = force_open_em_redis(id); };
|
131
|
+
end
|
132
|
+
|
133
|
+
def open_em_redis(db=1)
|
134
|
+
redis_em[db]
|
135
|
+
end
|
136
|
+
|
137
|
+
#private :open_em_redis
|
138
|
+
def semaphore
|
139
|
+
@semaphore||=Mutex.new
|
140
|
+
end
|
141
|
+
|
142
|
+
def register_database(key)
|
143
|
+
register_fiber_and_singleton_task("register_database(#{key}) ") do
|
144
|
+
redis=open_em_redis
|
145
|
+
id=redis.get(key)
|
146
|
+
if (new_db=id.nil?)
|
147
|
+
id=redis.incr(increment_key)
|
148
|
+
while id<2
|
149
|
+
id=redis.incr(increment_key)
|
150
|
+
end
|
151
|
+
redis.set(key,id)
|
152
|
+
end
|
153
|
+
p "Open db #{key} id=#{id} #{new_db}"
|
154
|
+
db_names[key]=id
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Lock the object so no other instances can modify it.
|
159
|
+
# This method implements the design pattern for locks
|
160
|
+
# described at: http://code.google.com/p/redis/wiki/SetnxCommand
|
161
|
+
#
|
162
|
+
# @see Model#mutex
|
163
|
+
def lock!
|
164
|
+
db=open_em_redis
|
165
|
+
until db.setnx('_lock', lock_timeout)
|
166
|
+
next unless lock = db.get('_lock')
|
167
|
+
sleep(2) and next unless lock_expired?(lock)
|
168
|
+
break unless lock = db.getset('_lock', lock_timeout)
|
169
|
+
break if lock_expired?(lock)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Release the lock.
|
174
|
+
# @see Model#mutex
|
175
|
+
def unlock!
|
176
|
+
open_em_redis.del('_lock')
|
177
|
+
end
|
178
|
+
|
179
|
+
def lock_timeout
|
180
|
+
Time.now.to_f + 5
|
181
|
+
end
|
182
|
+
|
183
|
+
def lock_expired? lock
|
184
|
+
lock.to_f < Time.now.to_f
|
185
|
+
end
|
186
|
+
|
187
|
+
def open_db_em(name, force=false)
|
188
|
+
key= :"db:#{db_prefix}_#{name}"
|
189
|
+
open_em_redis(db_names[key] || register_database(key))
|
190
|
+
end
|
191
|
+
|
192
|
+
# def opend_db(name, forse=false)
|
193
|
+
# redis=nil
|
194
|
+
# key= :"db:#{db_prefix}_#{name}"
|
195
|
+
# new_db=false
|
196
|
+
# id=(db_names[key] || (new_db=register_database(key)))
|
197
|
+
#
|
198
|
+
# redis=get_or_open_redis_instance(id, forse)
|
199
|
+
# redis.flush_db if new_db
|
200
|
+
#
|
201
|
+
# if block_given?
|
202
|
+
# begin
|
203
|
+
# yield redis
|
204
|
+
# rescue
|
205
|
+
# p "Error #{$!}"
|
206
|
+
# yield get_or_open_redis_instance(id, true)
|
207
|
+
# end
|
208
|
+
# end
|
209
|
+
# #last_access[id]=Time.new.to_i
|
210
|
+
# redis
|
211
|
+
# end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,445 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
=begin
|
3
|
+
Copyright 2010 Denis Kokorin <virkdi@mail.ru>
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
|
19
|
+
require 'fileutils'
|
20
|
+
require 'em-redis'
|
21
|
+
require 'active_support'
|
22
|
+
|
23
|
+
module EventMachine
|
24
|
+
module Protocols
|
25
|
+
class RedisMethodFactory
|
26
|
+
def initialize(parent)
|
27
|
+
@class_name='syncronized_redis_protocol'
|
28
|
+
#@buffer=['', "require 'em-redis'","require 'active_support'",'module EventMachine', 'module Protocols', 'module Redis']
|
29
|
+
@buffer=['']
|
30
|
+
yield self if block_given?
|
31
|
+
method 'ping' do |m|
|
32
|
+
m << 'exists?(0)'
|
33
|
+
end
|
34
|
+
method('blank?', 'args') do |m|
|
35
|
+
m << "args.blank? || args.flatten!.blank?"
|
36
|
+
end
|
37
|
+
#@buffer << 'end'
|
38
|
+
#@buffer << 'end'
|
39
|
+
#@buffer << 'end'
|
40
|
+
#generate
|
41
|
+
parent.class_eval @buffer.join("\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
def method(name, args=nil)
|
45
|
+
buf=[]
|
46
|
+
yield buf
|
47
|
+
buf=buf.map{|v| " #{v}"}.join("\n")
|
48
|
+
signature=(args ? "#{name}(#{args})" : name)
|
49
|
+
@buffer << ''
|
50
|
+
@buffer << "def #{signature}\n#{buf}\nend"
|
51
|
+
@buffer << "public :#{name}"
|
52
|
+
@buffer << ''
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_call_args
|
56
|
+
@method_call_args||=['', 'key', 'key, value']
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_call(name, options={})
|
60
|
+
name_sync="#{name}_sync"
|
61
|
+
name_async="#{name}_async"
|
62
|
+
|
63
|
+
args = (method_call_args[options[:args_count] || 1] || '*args')
|
64
|
+
|
65
|
+
call_args = case (options[:args_count] || 1)
|
66
|
+
when 2 then "[:#{name}, key, value]"
|
67
|
+
when 1 then "[:#{name}, key]"
|
68
|
+
when 0 then "[:#{name}]"
|
69
|
+
else "args.unshift(:#{name})"
|
70
|
+
end
|
71
|
+
|
72
|
+
method(name_sync, args) do |m|
|
73
|
+
m << "return [] if blank?(args)" if (options[:args_count] || 1)>3
|
74
|
+
m << "c=Fiber.current"
|
75
|
+
m << "call_command(#{call_args}) { |a| c.resume(a); }"
|
76
|
+
m << "Fiber.yield"
|
77
|
+
end
|
78
|
+
|
79
|
+
method(name_async, args) do |m|
|
80
|
+
m << "return if blank?(args)" if (options[:args_count] || 1)>3
|
81
|
+
m << "call_command(#{call_args}) { |a| }"
|
82
|
+
end
|
83
|
+
|
84
|
+
@buffer << "alias :#{name}! :#{name_async}"
|
85
|
+
@buffer << "alias :#{name} :#{options[:sync] ? name_sync : name_async}"
|
86
|
+
|
87
|
+
if options[:alias]
|
88
|
+
options[:alias].each do |alias_name|
|
89
|
+
@buffer << "alias :#{alias_name} :#{name}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def method_alias(name, options={})
|
95
|
+
em_name="#{name}_real"
|
96
|
+
name_sync="#{name}_sync"
|
97
|
+
name_async="#{name}_async"
|
98
|
+
@buffer << "alias :#{em_name} :#{name}"
|
99
|
+
args = (method_call_args[options[:args_count].to_i || 1] || '*args')
|
100
|
+
|
101
|
+
method(name_sync, args) do |m|
|
102
|
+
m << "return [] if blank?(args)" if (options[:args_count] || 1)>3
|
103
|
+
m << "c=Fiber.current"
|
104
|
+
m << "#{em_name}(#{args}) { |a| c.resume(a); }"
|
105
|
+
m << "Fiber.yield"
|
106
|
+
end
|
107
|
+
|
108
|
+
if options[:cache]
|
109
|
+
@buffer << "alias :#{name_sync}_no_cache :#{name_async}"
|
110
|
+
method(name_sync, args) do |m|
|
111
|
+
m << "@cache_#{name}||=#{name_sync}_no_cache(#{args})"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
method(name_async, args) do |m|
|
116
|
+
m << "return if blank?(args)" if (options[:args_count] || 1)>3
|
117
|
+
m << "#{em_name}(#{args}) { |a| }"
|
118
|
+
end
|
119
|
+
|
120
|
+
@buffer << "alias :#{name}! :#{name_async}"
|
121
|
+
@buffer << "alias :#{name} :#{options[:sync] ? name_sync : name_async}"
|
122
|
+
|
123
|
+
if options[:alias]
|
124
|
+
options[:alias].each do |alias_name|
|
125
|
+
@buffer << "alias :#{alias_name} :#{name}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def generate
|
131
|
+
dir=File.join(File.absolute_path(File.dirname(__FILE__)),'metadata')
|
132
|
+
FileUtils.mkdir_p(dir)
|
133
|
+
|
134
|
+
path=File.join(dir, "#{@class_name}.rb")
|
135
|
+
f=File.open(path,'w')
|
136
|
+
f.write(@buffer.join("\n"))
|
137
|
+
f.close
|
138
|
+
#require path
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
module EventMachine
|
146
|
+
module Protocols
|
147
|
+
module Redis
|
148
|
+
RedisMethodFactory.new(self) do |m|
|
149
|
+
#m.method_alias :set , {:sync=>false, :args_count=>2}
|
150
|
+
#m.method_alias :sort , {:sync=>true}
|
151
|
+
m.method_alias :incr , {:sync=>true, :alias=>[:increment], :args_count=>1}
|
152
|
+
m.method_alias :decr , {:sync=>false, :alias=>[:decrement], :args_count=>1}
|
153
|
+
m.method_alias :mapped_mget , {:sync=>true, :args_count=>999999}
|
154
|
+
m.method_alias :quit , {:sync=>false}
|
155
|
+
m.method_alias :type , {:sync=>true, :alias=>[:type?], :args_count=>1}
|
156
|
+
|
157
|
+
m.method_call :keys , {:sync=>true, :args_count=>1}
|
158
|
+
m.method_call :get , {:sync=>true}
|
159
|
+
m.method_call :getset , {:sync=>true, :args_count=>2}
|
160
|
+
#m.method_call :incrby , {:sync=>true, :args_count=>2}
|
161
|
+
m.method_call :select , {:sync=>false, :args_count=>1}
|
162
|
+
m.method_call :scard , {:sync=>true, :alias=>[:set_count], :args_count=>1}
|
163
|
+
m.method_call :smembers , {:sync=>true, :alias=>[:set_member?], :args_count=>1}
|
164
|
+
m.method_call :rename , {:sync=>false, :args_count=>2}
|
165
|
+
m.method_call :flushall , {:sync=>false, :alias=>[:flush_all], :args_count=>0}
|
166
|
+
m.method_call :flushdb , {:sync=>false, :alias=>[:flush_db, :flush_db!], :args_count=>0}
|
167
|
+
m.method_call :srem , {:sync=>false, :alias=>[:set_delete, :set_remove, :set_del], :args_count=>2}
|
168
|
+
m.method_call :sadd , {:sync=>false, :alias=>[:set_add], :args_count=>2}
|
169
|
+
|
170
|
+
m.method_call :smembers , {:sync=>true, :alias=>[:members, :set_members], :args_count=>1}
|
171
|
+
m.method_call :srandmember , {:sync=>true, :args_count=>1}
|
172
|
+
|
173
|
+
m.method_call :sismember , {:sync=>true, :alias=>[:set_member?], :args_count=>2}
|
174
|
+
m.method_call :exists , {:sync=>true, :alias=>[:exists?], :args_count=>1}
|
175
|
+
m.method_call :version , {:sync=>true, :args_count=>0, :cache=>true}
|
176
|
+
|
177
|
+
m.method_call :hset , {:sync=>true, :args_count=>3}
|
178
|
+
|
179
|
+
[:multi, :exec, :discard].each do |name|
|
180
|
+
m.method_call name, {:sync=>false, :args_count=>0}
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
MULTI_BULK_COMMANDS['hset']=true
|
187
|
+
MULTI_BULK_COMMANDS['hdel']=true
|
188
|
+
|
189
|
+
def bulk_array
|
190
|
+
@bulk_array||=[]
|
191
|
+
end
|
192
|
+
|
193
|
+
def delete_array
|
194
|
+
@delete_array||=[]
|
195
|
+
end
|
196
|
+
|
197
|
+
def bulk_expire_array
|
198
|
+
@bulk_expire_array||=[]
|
199
|
+
end
|
200
|
+
|
201
|
+
def bulk_insert?
|
202
|
+
!@bulk_insert.nil?
|
203
|
+
end
|
204
|
+
|
205
|
+
=begin
|
206
|
+
Добавление всех данных в одной транзакции
|
207
|
+
Redis send:
|
208
|
+
MULTI
|
209
|
+
COMMAND_1 ...
|
210
|
+
COMMAND_2 ...
|
211
|
+
COMMAND_N ...
|
212
|
+
EXEC or DISCARD
|
213
|
+
=end
|
214
|
+
def transaction
|
215
|
+
return unless block_given?
|
216
|
+
begin
|
217
|
+
multi
|
218
|
+
yield
|
219
|
+
exec
|
220
|
+
rescue Exception => e
|
221
|
+
discard
|
222
|
+
raise e
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def bulk_set
|
227
|
+
bulk_insert=@bulk_insert.nil?
|
228
|
+
begin
|
229
|
+
@bulk_insert=true
|
230
|
+
yield if block_given?
|
231
|
+
if bulk_insert
|
232
|
+
@bulk_insert=nil
|
233
|
+
del_real(delete_array)
|
234
|
+
mset_real(bulk_array)
|
235
|
+
bulk_expire_array.each do |value|
|
236
|
+
call_command(value) { |a| }
|
237
|
+
end
|
238
|
+
end
|
239
|
+
ensure
|
240
|
+
if bulk_insert
|
241
|
+
delete_array.clear
|
242
|
+
bulk_array.clear
|
243
|
+
bulk_expire_array.clear
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
#set
|
249
|
+
|
250
|
+
def set_async(key, value)
|
251
|
+
if bulk_insert?
|
252
|
+
bulk_array << key
|
253
|
+
bulk_array << value
|
254
|
+
else
|
255
|
+
call_command(['set', key, value]) { |a| }
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
alias :set! :set_async
|
260
|
+
alias :set :set_async
|
261
|
+
|
262
|
+
def mset_real(args)
|
263
|
+
argv=args.flatten
|
264
|
+
call_command(argv.unshift('mset')) { |a| } unless argv.empty?
|
265
|
+
end
|
266
|
+
|
267
|
+
def mset_async(*args)
|
268
|
+
if bulk_insert?
|
269
|
+
bulk_array+=args
|
270
|
+
else
|
271
|
+
mset_real(args)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
alias :mset! :mset_async
|
276
|
+
alias :mset :mset_async
|
277
|
+
|
278
|
+
def expire_async(key, value)
|
279
|
+
if bulk_insert?
|
280
|
+
bulk_expire_array << ['expire', key, value]
|
281
|
+
else
|
282
|
+
call_command(['expire', key, value]) { |a| }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
alias :expire! :expire_async
|
287
|
+
alias :expire :expire_async
|
288
|
+
#end set
|
289
|
+
|
290
|
+
def del_real(keys)
|
291
|
+
call_command(keys.unshift('del')) { |a| } unless keys.empty?
|
292
|
+
end
|
293
|
+
|
294
|
+
def del_async(*agrs)
|
295
|
+
values=agrs.flatten
|
296
|
+
if bulk_insert?
|
297
|
+
delete_array += values
|
298
|
+
else
|
299
|
+
del_real(values)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
alias :del! :del_async
|
304
|
+
alias :del :del_async
|
305
|
+
alias :delete :del_async
|
306
|
+
|
307
|
+
def mget(keys)
|
308
|
+
return [] if keys.blank?
|
309
|
+
#keys.flatten!
|
310
|
+
#return [] if keys.empty?
|
311
|
+
#$log.debug("Redis"){"call mget (#{args.inspect})"} if $debug
|
312
|
+
|
313
|
+
c=Fiber.current
|
314
|
+
call_command(keys.flatten.unshift('mget')) { |a| c.resume(a); }
|
315
|
+
Fiber.yield
|
316
|
+
end
|
317
|
+
|
318
|
+
def set_remove_if_empty(key_name, value)
|
319
|
+
set_remove(key_name, value)
|
320
|
+
del(key_name) if scard(key_name)==0
|
321
|
+
end
|
322
|
+
|
323
|
+
def method_missing(*argv)
|
324
|
+
name=argv[0].to_s
|
325
|
+
p "call #{name} (#{argv[1..argv.length]})"
|
326
|
+
if $debug && false
|
327
|
+
p "call #{name} (#{argv[1..argv.length]})"
|
328
|
+
#pp Kernel.caller
|
329
|
+
#$log.debug("Redis"){"call #{name} (#{argv[1..argv.length]})"}
|
330
|
+
argv[0]=(name=name[0,name.length-1]) if name.end_with?('!')
|
331
|
+
end
|
332
|
+
if name.end_with?('!')
|
333
|
+
argv[0]=name[0,name.length-1]
|
334
|
+
call_command(argv) {|a| }
|
335
|
+
else
|
336
|
+
c=Fiber.current
|
337
|
+
call_command(argv) { |a| c.resume(a); }
|
338
|
+
Fiber.yield
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def support_mset?
|
343
|
+
@support_mset||=version >= "1.1"
|
344
|
+
end
|
345
|
+
|
346
|
+
def raw_call_command(args, &blk)
|
347
|
+
argv = args.flatten.map{|v| v.to_s}
|
348
|
+
|
349
|
+
if MULTI_BULK_COMMANDS[argv.first]
|
350
|
+
command = "*#{argv.size}\r\n"
|
351
|
+
argv.each do |v|
|
352
|
+
command << "$#{get_size(v)}\r\n"
|
353
|
+
command << "#{v}\r\n"
|
354
|
+
end
|
355
|
+
else
|
356
|
+
name = argv[0].downcase
|
357
|
+
argv[0] = (ALIASES[name] || name)
|
358
|
+
raise "#{name} command is disabled" if DISABLED_COMMANDS[name]
|
359
|
+
if argv.length > 2 and BULK_COMMANDS[name]
|
360
|
+
bulk=argv[-1]
|
361
|
+
argv[-1]=get_size(bulk)
|
362
|
+
end
|
363
|
+
command = "#{argv.join(' ')}\r\n"
|
364
|
+
command << "#{bulk}\r\n" if bulk
|
365
|
+
end
|
366
|
+
|
367
|
+
puts "*** sending: #{command}" if $debug
|
368
|
+
@redis_callbacks << [REPLY_PROCESSOR[argv[0]], blk]
|
369
|
+
send_data command
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
def process_cmd(line)
|
374
|
+
puts "*** processing #{line}" if $debug
|
375
|
+
# first character of buffer will always be the response type
|
376
|
+
reply_type = line[0, 1]
|
377
|
+
reply_args = line.slice(1..-3) # remove type character and \r\n
|
378
|
+
case reply_type
|
379
|
+
|
380
|
+
#e.g. -MISSING
|
381
|
+
when MINUS
|
382
|
+
@redis_callbacks.shift # throw away the cb?
|
383
|
+
if @err_cb
|
384
|
+
@err_cb.call(reply_args)
|
385
|
+
else
|
386
|
+
err = RedisError.new
|
387
|
+
err.code = reply_args
|
388
|
+
raise err, "Redis server returned error code: #{err.code}"
|
389
|
+
end
|
390
|
+
|
391
|
+
# e.g. +OK
|
392
|
+
when PLUS
|
393
|
+
dispatch_response(reply_args)
|
394
|
+
|
395
|
+
# e.g. $3\r\nabc\r\n
|
396
|
+
# 'bulk' is more complex because it could be part of multi-bulk
|
397
|
+
when DOLLAR
|
398
|
+
data_len = Integer(reply_args)
|
399
|
+
if data_len == -1 # expect no data; return nil
|
400
|
+
if @multibulk_n > 0 # we're in the middle of a multibulk reply
|
401
|
+
@values << nil
|
402
|
+
if @values.size == @multibulk_n # DING, we're done
|
403
|
+
dispatch_response(@values)
|
404
|
+
@values = []
|
405
|
+
@multibulk_n = 0
|
406
|
+
end
|
407
|
+
else
|
408
|
+
dispatch_response(nil)
|
409
|
+
end
|
410
|
+
elsif @buffer.size >= data_len + 2 # buffer is full of expected data
|
411
|
+
if @multibulk_n > 0 # we're in the middle of a multibulk reply
|
412
|
+
@values << @buffer.slice!(0, data_len)
|
413
|
+
if @values.size == @multibulk_n # DING, we're done
|
414
|
+
dispatch_response(@values)
|
415
|
+
@values = []
|
416
|
+
@multibulk_n = 0
|
417
|
+
end
|
418
|
+
else # not multibulk
|
419
|
+
value = @buffer.slice!(0, data_len)
|
420
|
+
dispatch_response(value)
|
421
|
+
end
|
422
|
+
@buffer.slice!(0,2) # tossing \r\n
|
423
|
+
else # buffer isn't full or nil
|
424
|
+
# FYI, ParseError puts command back on head of buffer, waits for
|
425
|
+
# more data complete buffer
|
426
|
+
raise ParserError
|
427
|
+
end
|
428
|
+
|
429
|
+
#e.g. :8
|
430
|
+
when COLON
|
431
|
+
dispatch_response(Integer(reply_args))
|
432
|
+
|
433
|
+
#e.g. *2\r\n$1\r\na\r\n$1\r\nb\r\n
|
434
|
+
when ASTERISK
|
435
|
+
@multibulk_n = Integer(reply_args)
|
436
|
+
dispatch_response(nil) if @multibulk_n == -1 || @multibulk_n == 0
|
437
|
+
|
438
|
+
# Whu?
|
439
|
+
else
|
440
|
+
raise ProtocolError, "reply type not recognized: #{line.strip}"
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|