remq 0.0.1a
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 +5 -0
- data/.gitmodules +3 -0
- data/LICENSE +22 -0
- data/Readme.md +49 -0
- data/lib/remq.rb +52 -0
- data/lib/remq/coder.rb +30 -0
- data/lib/remq/multi_json_coder.rb +39 -0
- data/lib/remq/script.rb +27 -0
- data/lib/remq/version.rb +3 -0
- data/remq.gemspec +34 -0
- data/spec/remq_spec.rb +67 -0
- data/vendor/remq/Readme.md +41 -0
- data/vendor/remq/scripts/consume.lua +45 -0
- data/vendor/remq/scripts/publish.lua +12 -0
- data/vendor/remq/scripts/purge.lua +18 -0
- metadata +110 -0
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Evan Owen
|
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,49 @@
|
|
1
|
+
# Remq-rb
|
2
|
+
|
3
|
+
A Ruby client library for [Remq](https://github.com/kainosnoema/remq), a [Redis](http://redis.io)-based protocol for building fast, persistent pub/sub message queues.
|
4
|
+
|
5
|
+
NOTE: In early-stage development, API not locked.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
**Producer:**
|
10
|
+
|
11
|
+
``` rb
|
12
|
+
require 'remq'
|
13
|
+
|
14
|
+
remq = Remq.new
|
15
|
+
|
16
|
+
message = { event: 'signup', account_id: 694 }
|
17
|
+
id = remq.publish('events.accounts', message)
|
18
|
+
```
|
19
|
+
|
20
|
+
**Pub/sub consumer (messages lost during failure):**
|
21
|
+
|
22
|
+
``` rb
|
23
|
+
require 'remq'
|
24
|
+
|
25
|
+
remq = Remq.new
|
26
|
+
|
27
|
+
# TODO: not implemented yet
|
28
|
+
```
|
29
|
+
|
30
|
+
**Polling consumer with cursor (resumes post-failure):**
|
31
|
+
|
32
|
+
``` rb
|
33
|
+
require 'remq'
|
34
|
+
|
35
|
+
remq = Remq.new
|
36
|
+
|
37
|
+
# TODO: not implemented yet
|
38
|
+
```
|
39
|
+
|
40
|
+
**Purging old messages:**
|
41
|
+
|
42
|
+
``` rb
|
43
|
+
|
44
|
+
require 'remq'
|
45
|
+
|
46
|
+
remq = Remq.new
|
47
|
+
|
48
|
+
# TODO: not implemented yet
|
49
|
+
```
|
data/lib/remq.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'remq/script'
|
3
|
+
require 'remq/multi_json_coder'
|
4
|
+
|
5
|
+
class Remq
|
6
|
+
|
7
|
+
attr :redis, :namespace, :scripts, :coder
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@redis = options[:redis] || Redis.new(options)
|
11
|
+
@namespace = 'remq'
|
12
|
+
@scripts = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def publish(channel, message)
|
16
|
+
channel = channel.join('.') if channel.is_a?(Array)
|
17
|
+
msg, utc_seconds = coder.encode(message), Time.now.to_i
|
18
|
+
id = eval_script(:publish, namespace, channel, msg, utc_seconds)
|
19
|
+
id.nil? ? nil : id.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def consume(channel, options = {})
|
23
|
+
cursor, limit = options[:cursor] || 0, options[:limit] || 1000
|
24
|
+
msgs_ids = eval_script(:consume, namespace, channel, cursor, limit)
|
25
|
+
msgs = {}
|
26
|
+
msgs_ids.each_index { |i|
|
27
|
+
if i % 2 == 0
|
28
|
+
msgs[msgs_ids[i+1]] = coder.decode(msgs_ids[i])
|
29
|
+
end
|
30
|
+
}
|
31
|
+
msgs
|
32
|
+
end
|
33
|
+
|
34
|
+
def coder
|
35
|
+
@coder ||= MultiJsonCoder.new
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def eval_script(name, *args)
|
41
|
+
script(name).eval(@redis, *args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def script(name)
|
45
|
+
@scripts[name.to_sym] ||= Script.new(File.read(script_path(name)))
|
46
|
+
end
|
47
|
+
|
48
|
+
def script_path(name)
|
49
|
+
File.expand_path("../vendor/remq/scripts/#{name}.lua", File.dirname(__FILE__))
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/remq/coder.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
# borrowed from Resque::Coder. Thanks @defunkt!
|
3
|
+
|
4
|
+
class Remq
|
5
|
+
class EncodeException < StandardError; end
|
6
|
+
class DecodeException < StandardError; end
|
7
|
+
|
8
|
+
class Coder
|
9
|
+
# Given a Ruby object, returns a string suitable for storage in a
|
10
|
+
# queue.
|
11
|
+
def encode(object)
|
12
|
+
raise EncodeException
|
13
|
+
end
|
14
|
+
|
15
|
+
# alias for encode
|
16
|
+
def dump(object)
|
17
|
+
encode(object)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Given a string, returns a Ruby object.
|
21
|
+
def decode(object)
|
22
|
+
raise DecodeException
|
23
|
+
end
|
24
|
+
|
25
|
+
# alias for decode
|
26
|
+
def load(object)
|
27
|
+
decode(object)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'remq/coder'
|
3
|
+
|
4
|
+
# borrowed from Resque::MultiJsonCoder. Thanks @defunkt!
|
5
|
+
|
6
|
+
if MultiJson.respond_to?(:adapter)
|
7
|
+
raise "Please install the yajl-ruby or json gem" if MultiJson.adapter.to_s == 'MultiJson::Adapters::OkJson'
|
8
|
+
elsif MultiJson.respond_to?(:engine)
|
9
|
+
raise "Please install the yajl-ruby or json gem" if MultiJson.engine.to_s == 'MultiJson::Engines::OkJson'
|
10
|
+
end
|
11
|
+
|
12
|
+
class Remq
|
13
|
+
class MultiJsonCoder < Coder
|
14
|
+
class EncodeException < StandardError; end
|
15
|
+
class DecodeException < StandardError; end
|
16
|
+
|
17
|
+
def encode(object)
|
18
|
+
if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
|
19
|
+
MultiJson.dump object
|
20
|
+
else
|
21
|
+
MultiJson.encode object
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def decode(object)
|
26
|
+
return unless object
|
27
|
+
|
28
|
+
begin
|
29
|
+
if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
|
30
|
+
MultiJson.load object
|
31
|
+
else
|
32
|
+
MultiJson.decode object
|
33
|
+
end
|
34
|
+
rescue ::MultiJson::DecodeError => e
|
35
|
+
raise DecodeException, e.message, e.backtrace
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/remq/script.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
class Remq
|
4
|
+
class Script
|
5
|
+
|
6
|
+
attr :source, :sha
|
7
|
+
|
8
|
+
def initialize(source)
|
9
|
+
@source = source
|
10
|
+
end
|
11
|
+
|
12
|
+
def eval(redis, *args)
|
13
|
+
redis.evalsha(sha, [], [*args])
|
14
|
+
rescue => e
|
15
|
+
if e.message =~ /NOSCRIPT/
|
16
|
+
redis.eval(source, [], [*args])
|
17
|
+
else
|
18
|
+
raise
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def sha
|
23
|
+
@sha ||= Digest::SHA1.hexdigest source
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/lib/remq/version.rb
ADDED
data/remq.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$LOAD_PATH.unshift 'lib'
|
2
|
+
|
3
|
+
require 'remq/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "remq"
|
7
|
+
s.version = Remq::VERSION
|
8
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
9
|
+
s.summary = "A Remq client library for Ruby."
|
10
|
+
s.homepage = "http://github.com/kainosnoema/remq"
|
11
|
+
s.email = "kainosnoema@gmail.com"
|
12
|
+
s.authors = [ "Evan Owen" ]
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.files += Dir.glob('vendor/**/*')
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
|
18
|
+
s.add_dependency "redis", "~> 3.0.1"
|
19
|
+
s.add_dependency "multi_json", "~> 1.0"
|
20
|
+
|
21
|
+
s.add_development_dependency "rake"
|
22
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
23
|
+
|
24
|
+
s.description = <<description
|
25
|
+
Remq is a Redis-based protocol for building fast, persistent
|
26
|
+
pub/sub message queues.
|
27
|
+
|
28
|
+
The Remq protocol is defined by a collection of Lua scripts
|
29
|
+
(located at https://github.com/kainosnoema/remq) which effectively
|
30
|
+
turn Redis into a capable message queue broker for fast inter-service
|
31
|
+
communication. The Remq Ruby client library is built on top of these
|
32
|
+
scripts, making it easy to build fast, persisted pub/sub message queues.
|
33
|
+
description
|
34
|
+
end
|
data/spec/remq_spec.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'remq'
|
2
|
+
|
3
|
+
describe Remq do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
subject { Remq.new(db: 4).tap { |r| r.redis.flushdb } }
|
7
|
+
end
|
8
|
+
|
9
|
+
after :each do
|
10
|
+
subject.redis.flushdb
|
11
|
+
end
|
12
|
+
|
13
|
+
context "class" do
|
14
|
+
describe ".new" do
|
15
|
+
it "creates a Redis client when options hash missing" do
|
16
|
+
Remq.new.redis.should be_a(Redis)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "takes a Redis client as an option" do
|
20
|
+
Remq.new(redis: (redis = Redis.new)).redis.should eql redis
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".publish" do
|
26
|
+
it "publishes a message to the given channel and returns an id" do
|
27
|
+
id = subject.publish('events.things', { test: 'one' })
|
28
|
+
id.should be_a String
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe ".consume" do
|
33
|
+
it "consumes messages published to the given channel" do
|
34
|
+
subject.publish('events.things', { 'test' => 'one' })
|
35
|
+
subject.publish('events.things', { 'test' => 'two' })
|
36
|
+
subject.publish('events.things', { 'test' => 'three' })
|
37
|
+
|
38
|
+
msgs = subject.consume('events.things')
|
39
|
+
msgs.should have(3).items
|
40
|
+
msgs[msgs.keys[0]].should eql({ 'test' => 'one' })
|
41
|
+
msgs[msgs.keys[2]].should eql({ 'test' => 'three' })
|
42
|
+
end
|
43
|
+
|
44
|
+
it "limits the messages returned to value given in the :limit option" do
|
45
|
+
subject.publish('events.things', { 'test' => 'one' })
|
46
|
+
subject.publish('events.things', { 'test' => 'two' })
|
47
|
+
subject.publish('events.things', { 'test' => 'three' })
|
48
|
+
|
49
|
+
msgs = subject.consume('events.things', limit: 2)
|
50
|
+
msgs.should have(2).items
|
51
|
+
msgs[msgs.keys[0]].should eql({ 'test' => 'one' })
|
52
|
+
msgs[msgs.keys[1]].should eql({ 'test' => 'two' })
|
53
|
+
msgs[msgs.keys[2]].should be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns messages published since the id given in the :cursor option" do
|
57
|
+
cursor = subject.publish('events.things', { 'test' => 'one' })
|
58
|
+
subject.publish('events.things', { 'test' => 'two' })
|
59
|
+
subject.publish('events.things', { 'test' => 'three' })
|
60
|
+
|
61
|
+
msgs = subject.consume('events.things', cursor: cursor)
|
62
|
+
msgs.should have(2).items
|
63
|
+
msgs[msgs.keys[0]].should eql({ 'test' => 'two' })
|
64
|
+
msgs[msgs.keys[1]].should eql({ 'test' => 'three' })
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Remq
|
2
|
+
|
3
|
+
Remq (pronounced 'rem-que') is two things: (1) A [Redis](http://redis.io)-based protocol defined by a collection of Lua scripts (this project) which effectively turn Redis into a capable message queue broker for fast inter-service communication. (2) Multiple client libraries using these scripts for building fast, persisted pub/sub message queues.
|
4
|
+
|
5
|
+
- Producers publish any string to a message channel and receive a unique message-id
|
6
|
+
- Consumers subscribe to message channels via polling with a cursor (allowing resume), or via Redis pub/sub
|
7
|
+
- Consumers can subscribe to multible queues at once using Redis key globbing (ie. `'events.*'`)
|
8
|
+
- Able to sustain ~15k messages/sec on loopback interface (1 producer -> 1 consumer)
|
9
|
+
- Consistent performance if Redis has enough memory (tested up to ~15m messages, 3GB in memory)
|
10
|
+
- Purge channels of old messages periodically to maintain performance
|
11
|
+
|
12
|
+
NOTE: In early-stage development, API not locked.
|
13
|
+
|
14
|
+
## Client Libraries
|
15
|
+
|
16
|
+
- Node.js: [remq-node](https://github.com/kainosnoema/remq-node) (`npm install remq`)
|
17
|
+
- Ruby: [remq-rb](https://github.com/kainosnoema/remq-rb) (`gem install remq`)
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
This project includes just the core Lua scripts that define the Remq protocol. To use Remq to build a message queue, install Redis along with one or more of the client libraries listed above.
|
22
|
+
|
23
|
+
Raw Redis syntax:
|
24
|
+
|
25
|
+
**Producer:**
|
26
|
+
``` sh
|
27
|
+
redis> EVAL <publish.lua> 0 namespace channel message utcseconds
|
28
|
+
# returns a unique message id
|
29
|
+
```
|
30
|
+
|
31
|
+
**Consumer:**
|
32
|
+
``` sh
|
33
|
+
redis> EVAL <consume.lua> 0 namespace channel cursor limit
|
34
|
+
# returns each message followed by its id, just like ZRANGEBYSCORE
|
35
|
+
```
|
36
|
+
|
37
|
+
**Purge:**
|
38
|
+
``` sh
|
39
|
+
redis> EVAL <purge.lua> 0 namespace channel <BEFORE id (or) KEEP count>
|
40
|
+
# returns the count of messages purged
|
41
|
+
```
|
@@ -0,0 +1,45 @@
|
|
1
|
+
local namespace, channel, cursor, limit = ARGV[1], ARGV[2], ARGV[3], ARGV[4]
|
2
|
+
|
3
|
+
limit = math.min(limit, 3999) -- 3999 is the limit of unpack()
|
4
|
+
|
5
|
+
local channel_key = namespace .. ':channel:' .. channel
|
6
|
+
|
7
|
+
-- for results from multiple channels, we'll merge them into a single set
|
8
|
+
local union_key
|
9
|
+
if string.find(channel_key, '*') then
|
10
|
+
-- if the pattern matches multiple keys, we have to merge the keys
|
11
|
+
-- we could use zunionstore here, but it wouldn't be optimal for very large sets
|
12
|
+
local matched_keys = redis.call('keys', channel_key)
|
13
|
+
if #matched_keys > 1 then
|
14
|
+
union_key = channel_key .. '@' .. redis.call('get', namespace .. ':id')
|
15
|
+
for i,key in ipairs(matched_keys) do
|
16
|
+
local msgs_ids = redis.call('zrangebyscore', key, '(' .. cursor, '+inf', 'WITHSCORES', 'LIMIT', 0, limit)
|
17
|
+
if #msgs_ids > 0 then
|
18
|
+
-- `zadd` takes scores first, so we have to reverse
|
19
|
+
local len, reversed = #msgs_ids, {}
|
20
|
+
for i = len, 1, -1 do reversed[len - i + 1] = msgs_ids[i] end
|
21
|
+
redis.call('zadd', union_key, unpack(reversed))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
channel_key = union_key
|
25
|
+
else
|
26
|
+
channel_key = matched_keys[1]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
-- as long as we have a channel key, get the messages and add wrap with them with ids
|
31
|
+
local msgs = {}
|
32
|
+
if channel_key ~= nil then
|
33
|
+
msgs = redis.call('zrangebyscore', channel_key, '(' .. cursor, '+inf', 'WITHSCORES', 'LIMIT', 0, limit)
|
34
|
+
-- zset decimal precision isn't great enough to retain utc seconds, so we have to round
|
35
|
+
for i,key in ipairs(msgs) do
|
36
|
+
if i % 2 == 0 then msgs[i] = string.format("%.10f", msgs[i]) end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
-- if we've merged multiple channels, remove the union key
|
41
|
+
if union_key ~= nil then
|
42
|
+
redis.call('del', union_key)
|
43
|
+
end
|
44
|
+
|
45
|
+
return msgs
|
@@ -0,0 +1,12 @@
|
|
1
|
+
local namespace, channel, msg, utc_sec = ARGV[1], ARGV[2], ARGV[3], ARGV[4]
|
2
|
+
local channel_key = namespace .. ':channel:' .. channel
|
3
|
+
|
4
|
+
-- ids are an incrementing integer followed by UTC time as a decimal value
|
5
|
+
local id = redis.call('incr', namespace .. ':id') .. '.' .. (utc_sec or 0)
|
6
|
+
|
7
|
+
redis.call('zadd', channel_key, id, msg)
|
8
|
+
|
9
|
+
redis.call('publish', channel_key, msg)
|
10
|
+
redis.call('publish', namespace .. ':stats:' .. channel, id)
|
11
|
+
|
12
|
+
return id
|
@@ -0,0 +1,18 @@
|
|
1
|
+
local namespace, channel, cmd, value = ARGV[1], ARGV[2], ARGV[3], ARGV[4]
|
2
|
+
local channel_key = namespace .. ':channel:' .. channel
|
3
|
+
|
4
|
+
local matched_keys = { channel_key }
|
5
|
+
if string.find(channel_key, '*') then
|
6
|
+
matched_keys = redis.call('keys', channel_key)
|
7
|
+
end
|
8
|
+
|
9
|
+
local purged = 0
|
10
|
+
for i,key in ipairs(matched_keys) do
|
11
|
+
if cmd == 'BEFORE' then
|
12
|
+
purged = purged + redis.call('zremrangebyscore', key, '-inf', '(' .. value)
|
13
|
+
elseif cmd == 'KEEP' then
|
14
|
+
purged = purged + redis.call('zremrangebyrank', key, 0, 0 - (value - 1))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
return purged
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: remq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1a
|
5
|
+
prerelease: 5
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Evan Owen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: &70122011105460 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70122011105460
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: multi_json
|
27
|
+
requirement: &70122011104960 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70122011104960
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70122011104580 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70122011104580
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &70122011103980 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.6'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70122011103980
|
58
|
+
description: ! " Remq is a Redis-based protocol for building fast, persistent\n
|
59
|
+
\ pub/sub message queues.\n\n The Remq protocol is defined by a collection
|
60
|
+
of Lua scripts\n (located at https://github.com/kainosnoema/remq) which effectively\n
|
61
|
+
\ turn Redis into a capable message queue broker for fast inter-service\n communication.
|
62
|
+
The Remq Ruby client library is built on top of these\n scripts, making it easy
|
63
|
+
to build fast, persisted pub/sub message queues.\n"
|
64
|
+
email: kainosnoema@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .gitmodules
|
71
|
+
- LICENSE
|
72
|
+
- Readme.md
|
73
|
+
- lib/remq.rb
|
74
|
+
- lib/remq/coder.rb
|
75
|
+
- lib/remq/multi_json_coder.rb
|
76
|
+
- lib/remq/script.rb
|
77
|
+
- lib/remq/version.rb
|
78
|
+
- remq.gemspec
|
79
|
+
- spec/remq_spec.rb
|
80
|
+
- vendor/remq/Readme.md
|
81
|
+
- vendor/remq/scripts/consume.lua
|
82
|
+
- vendor/remq/scripts/publish.lua
|
83
|
+
- vendor/remq/scripts/purge.lua
|
84
|
+
homepage: http://github.com/kainosnoema/remq
|
85
|
+
licenses: []
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>'
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.3.1
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.11
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: A Remq client library for Ruby.
|
108
|
+
test_files:
|
109
|
+
- spec/remq_spec.rb
|
110
|
+
has_rdoc:
|