joffice_redis 0.1.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.
- 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
|