hsdq 0.7.0
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 +7 -0
- data/.gitignore +25 -0
- data/.travis.yml +3 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +26 -0
- data/Rakefile +1 -0
- data/bin/console +15 -0
- data/bin/setup +7 -0
- data/config/hsdq_your_class.yml,example +63 -0
- data/hsdq.gemspec +32 -0
- data/lib/hsdq.rb +38 -0
- data/lib/hsdq/admin.rb +60 -0
- data/lib/hsdq/connectors.rb +40 -0
- data/lib/hsdq/listener.rb +102 -0
- data/lib/hsdq/receiver.rb +269 -0
- data/lib/hsdq/sender.rb +124 -0
- data/lib/hsdq/session.rb +59 -0
- data/lib/hsdq/setting.rb +130 -0
- data/lib/hsdq/shared.rb +42 -0
- data/lib/hsdq/thread_store.rb +49 -0
- data/lib/hsdq/threadpool.rb +58 -0
- data/lib/hsdq/utilities.rb +20 -0
- data/lib/hsdq/version.rb +7 -0
- data/readme.md +178 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d34ed2f5f158452f0d837271297827400158f56f
|
4
|
+
data.tar.gz: 4129432a2dc752ddfc8d924b6d0d5e98d695099d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 652311c93022e42f60b897c529c002a17c04bf83839e2a1d0cf4b720de7d613fe85312b90c28041ad51a56447ec54cf0a40bb69376c8899120bcc55aca56c3d5
|
7
|
+
data.tar.gz: adf4969580bb7075c767b7358c7c6de60d2a75e5682cbbb77177f8ed30ddb01704b00d72fa54e9fd1a4aeaa7292cb9a95a5aa2bd18af475bc7bfd870beebd5e3
|
data/.gitignore
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
.rspec
|
2
|
+
vendor/bundle
|
3
|
+
log/*
|
4
|
+
tmp/*
|
5
|
+
coverage
|
6
|
+
spec/tmp/*
|
7
|
+
**.orig
|
8
|
+
rerun.txt
|
9
|
+
.byebug_history
|
10
|
+
.ruby_gemset
|
11
|
+
.ruby_version
|
12
|
+
|
13
|
+
# Ignore bundler config.
|
14
|
+
.bundle
|
15
|
+
Gemfile.lock
|
16
|
+
|
17
|
+
# ignore Rubymine files
|
18
|
+
.idea/
|
19
|
+
|
20
|
+
.rvmrc
|
21
|
+
.DS_Store
|
22
|
+
*.save
|
23
|
+
*.swp
|
24
|
+
*.mp4
|
25
|
+
*.ogv
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
This file is part of HSDQ
|
2
|
+
|
3
|
+
Copyright 2013 Yves Lucas,
|
4
|
+
|
5
|
+
Authorisation is granted under MIT license as long as this copyright and mention are not removed
|
6
|
+
|
7
|
+
MIT License
|
8
|
+
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
a copy of this software and associated documentation files (the
|
11
|
+
"Software"), to deal in the Software without restriction, including
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be
|
18
|
+
included in all copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "debugger"
|
5
|
+
require_relative "../lib/hsdq"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# This file should be in your Rails app config folder, or for script / non Rails app be accessible
|
2
|
+
# by your app
|
3
|
+
# different host / port can be used to split the different usage
|
4
|
+
# Redis is mono threaded so the same server can also run different instances on different ports
|
5
|
+
# session can be separate for different group of applications
|
6
|
+
# You need one separate file for each hsdq class
|
7
|
+
# below setup for 2 instances of Redis:
|
8
|
+
# - port 6379 with 2 databases (message db 1 and admin db 0)
|
9
|
+
# - port 6380 for session
|
10
|
+
# any app/script/class that need to exchange messages together should share the same setup, it is
|
11
|
+
# strongly suggested to use same setup for all apps at the beginning.
|
12
|
+
# The flexibility of this setup file is for scaling and sharding the busses.
|
13
|
+
:development:
|
14
|
+
:redis:
|
15
|
+
:message:
|
16
|
+
:host: 127.0.0.1
|
17
|
+
:port: 6379
|
18
|
+
:db: 1
|
19
|
+
:admin:
|
20
|
+
:host: 127.0.0.1
|
21
|
+
:port: 6379
|
22
|
+
:db: 2
|
23
|
+
:session:
|
24
|
+
:host: 127.0.0.1
|
25
|
+
:port: 6380
|
26
|
+
:db: 1
|
27
|
+
:exceptions: true
|
28
|
+
:threaded: false
|
29
|
+
:timeout: 10
|
30
|
+
:test:
|
31
|
+
:redis:
|
32
|
+
:message:
|
33
|
+
:host: 127.0.0.1
|
34
|
+
:port: 6379
|
35
|
+
:db: 2
|
36
|
+
:admin:
|
37
|
+
:host: 127.0.0.1
|
38
|
+
:port: 6379
|
39
|
+
:db: 2
|
40
|
+
:session:
|
41
|
+
:host: 127.0.0.1
|
42
|
+
:port: 6379
|
43
|
+
:db: 2
|
44
|
+
:exceptions: true
|
45
|
+
:threaded: false
|
46
|
+
:timeout: 10
|
47
|
+
:production:
|
48
|
+
:redis:
|
49
|
+
:message:
|
50
|
+
:host: 127.0.0.1
|
51
|
+
:port: 6395
|
52
|
+
:db: 5
|
53
|
+
:admin:
|
54
|
+
:host: 127.0.0.1
|
55
|
+
:port: 6395
|
56
|
+
:db: 5
|
57
|
+
:session:
|
58
|
+
:host: 127.0.0.1
|
59
|
+
:port: 6395
|
60
|
+
:db: 5
|
61
|
+
:exceptions: false
|
62
|
+
:threaded: true
|
63
|
+
:timeout: 10
|
data/hsdq.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hsdq/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hsdq"
|
8
|
+
spec.version = Hsdq::VERSION
|
9
|
+
spec.authors = ["Yves Lucas"]
|
10
|
+
spec.email = ["hsdq@ylucas.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Hsdq, High Speed Distributed Queue for message bus.}
|
13
|
+
spec.description = %q{Hsdq: Light weight and distributed, Hsdq allow message passing distributed applications to exchange requests and data at high speed, work in parallel and scale horizontaly.}
|
14
|
+
spec.homepage = "https://github.com/ylucas/hsdq"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|examples)/}) }
|
18
|
+
spec.bindir = "bin"
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "redis", "~> 3.3"
|
22
|
+
spec.add_runtime_dependency "json", "~> 2.0"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "shoulda-matchers"
|
28
|
+
spec.add_development_dependency "yard"
|
29
|
+
spec.add_development_dependency "redcarpet"
|
30
|
+
spec.add_development_dependency "simplecov"
|
31
|
+
spec.add_development_dependency "pry-byebug"
|
32
|
+
end
|
data/lib/hsdq.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Hsdq: is a High Speed Distributed message Queue built on top of Redis.
|
2
|
+
# @see Readme.md
|
3
|
+
#
|
4
|
+
# This software Copyright 2013-2016 Yves Lucas
|
5
|
+
# License MIT, see LICENSE.txt
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'redis'
|
9
|
+
require 'json'
|
10
|
+
require 'securerandom'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
require_relative "hsdq/shared"
|
14
|
+
require_relative "hsdq/utilities"
|
15
|
+
require_relative "hsdq/connectors"
|
16
|
+
require_relative "hsdq/listener"
|
17
|
+
require_relative "hsdq/sender"
|
18
|
+
require_relative "hsdq/setting"
|
19
|
+
require_relative "hsdq/receiver"
|
20
|
+
require_relative "hsdq/thread_store"
|
21
|
+
require_relative "hsdq/session"
|
22
|
+
require_relative "hsdq/threadpool"
|
23
|
+
require_relative "hsdq/admin"
|
24
|
+
|
25
|
+
module Hsdq
|
26
|
+
include Shared
|
27
|
+
include Utilities
|
28
|
+
include Connectors
|
29
|
+
include Listener
|
30
|
+
include Sender
|
31
|
+
include Setting
|
32
|
+
include Receiver
|
33
|
+
include ThreadStore
|
34
|
+
include Session
|
35
|
+
include Threadpool
|
36
|
+
include Admin
|
37
|
+
|
38
|
+
end
|
data/lib/hsdq/admin.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Hsdq
|
2
|
+
module Admin
|
3
|
+
|
4
|
+
def listener_id(listener_id=nil)
|
5
|
+
@listener_id = listener_id if listener_id
|
6
|
+
@listener_id
|
7
|
+
end
|
8
|
+
|
9
|
+
def listener_version(listener_version=nil)
|
10
|
+
@listener_version = listener_version if listener_version
|
11
|
+
@listener_version
|
12
|
+
end
|
13
|
+
|
14
|
+
def admin_channel
|
15
|
+
"#{environment}_#{channel}_admin"
|
16
|
+
end
|
17
|
+
|
18
|
+
def admin_versionned_channel
|
19
|
+
"#{admin_channel}__#{listener_version}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def admin_id_channel
|
23
|
+
"#{admin_channel}__#{listener_id}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def admin_channels
|
27
|
+
@admin_channels ||= %w(admin )
|
28
|
+
end
|
29
|
+
|
30
|
+
def admin_listener
|
31
|
+
begin
|
32
|
+
p "starting admin channels #{admin_channel}, #{admin_versionned_channel}"
|
33
|
+
cx_admin.subscribe(admin_channel, admin_versionned_channel) do |on|
|
34
|
+
on.message do |a_channel, admin_message_j|
|
35
|
+
p "received admin message: #{admin_message_j} from admin channel #{admin_channel} "
|
36
|
+
process_admin_message(admin_channel, admin_message_j)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
rescue Redis::BaseConnectionError => e
|
40
|
+
p e.inspect
|
41
|
+
sleep(1)
|
42
|
+
retry
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_admin_message(_admin_channel, admin_message_j)
|
47
|
+
admin_message = JSON.parse(admin_message_j) rescue {'params' => {'task' => ""}}
|
48
|
+
task = admin_message['params'] ? admin_message['params']['task'] : ""
|
49
|
+
case task
|
50
|
+
when 'stop'
|
51
|
+
hsdq_stop!
|
52
|
+
when 'start'
|
53
|
+
hsdq_start!
|
54
|
+
when 'exit'
|
55
|
+
hsdq_exit!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
module Hsdq
|
4
|
+
|
5
|
+
# This module contains the logic for different connection to the redis layer.
|
6
|
+
#
|
7
|
+
# They can be connected to the same Redis instance, but it is recommended to use different
|
8
|
+
# database connections in order to segregate the different usages.
|
9
|
+
#
|
10
|
+
# In production of large applications you should use different instances for the different layers.
|
11
|
+
# The configuration files are named after your class name hsdq_yourclass.yml
|
12
|
+
module Connectors
|
13
|
+
|
14
|
+
# Establish the listener connection.
|
15
|
+
# IMPORTANT this connection is blocked by the listener and must not be used elsewhere
|
16
|
+
# @return [Redis connection] For the listener exclusively
|
17
|
+
def cx_listener
|
18
|
+
@cx_listener ||= Redis.new cx_opts[:message]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Establish an unblocked connection for the sender and also pulling data from Redis
|
22
|
+
# @return [Redis connection] This connection is used to send messages as well as to retrieve
|
23
|
+
# data from the message hash
|
24
|
+
def cx_data
|
25
|
+
@cx_data ||= Redis.new cx_opts[:message]
|
26
|
+
end
|
27
|
+
|
28
|
+
# establish an unblocked connection for the session layer
|
29
|
+
# @return [Redis connection] reserved for storing and retrieving the sessions data
|
30
|
+
def cx_session
|
31
|
+
@cx_session ||= Redis.new cx_opts[:session]
|
32
|
+
end
|
33
|
+
|
34
|
+
# establish a connection for the admin channel pub/sub
|
35
|
+
# @return [Redis connection] reserved for the admin commands
|
36
|
+
def cx_admin
|
37
|
+
@cx_admin ||= Redis.new cx_opts[:admin]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "redis"
|
2
|
+
require_relative "connectors"
|
3
|
+
require_relative "utilities"
|
4
|
+
|
5
|
+
module Hsdq
|
6
|
+
# This module is holding the methods for the class listener.
|
7
|
+
# The listener is connected to the Redis instance with the blocking cx_listener connection
|
8
|
+
#
|
9
|
+
# It is popping the "Spark" (ephemeral part of the message) from the list.
|
10
|
+
# When a spark is popped, it is validated and processed in the receiver module.
|
11
|
+
module Listener
|
12
|
+
include Connectors
|
13
|
+
|
14
|
+
# Start hsdq to listen to channel.
|
15
|
+
# @param [String] channel The channel the hsdq class will be listening
|
16
|
+
# @param [Hash] options The hsdq class option from the config file and/or additional parameters passed
|
17
|
+
def hsdq_start(channel, options)
|
18
|
+
hsdq_add_options(options) if options
|
19
|
+
hsdq_start!
|
20
|
+
hsdq_loop(channel)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set the flag to stop processing the queue
|
24
|
+
# @return [Boolean] false
|
25
|
+
def hsdq_stop!
|
26
|
+
@hsdq_running = false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set the flag to allow processing the queue
|
30
|
+
# @return [Boolean] true
|
31
|
+
def hsdq_start!
|
32
|
+
@hsdq_running = true
|
33
|
+
end
|
34
|
+
|
35
|
+
# Flag allowing or not the processing of the queue
|
36
|
+
# @return [Boolean] true when listening is allowed
|
37
|
+
def hsdq_running?
|
38
|
+
@hsdq_running = true if @hsdq_running.nil?
|
39
|
+
@hsdq_running
|
40
|
+
end
|
41
|
+
|
42
|
+
# Opposite or hsdq_running.
|
43
|
+
# @see hsdq_running?
|
44
|
+
# @return [Boolean] true when listening is not allowed
|
45
|
+
def hsdq_stopped?
|
46
|
+
!hsdq_running?
|
47
|
+
end
|
48
|
+
|
49
|
+
# When true allow the listener to start
|
50
|
+
# When set to false, the listener exit the listening loop. This is mostly to exit gracefully the program
|
51
|
+
# The listener needs to be restarted specifically, if need to run again
|
52
|
+
# @return [Boolean] true when allowing the listener to stay in listening mode
|
53
|
+
def hsdq_alive?
|
54
|
+
@hsdq_alive = true if @hsdq_alive.nil?
|
55
|
+
@hsdq_alive
|
56
|
+
end
|
57
|
+
|
58
|
+
# Flag break the listening loop if true.
|
59
|
+
# @return [Boolean]
|
60
|
+
def hsdq_exit?
|
61
|
+
@hsdq_exit
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set exit to force the listening loop to exit.
|
65
|
+
def hsdq_exit!
|
66
|
+
@hsdq_exit = true
|
67
|
+
end
|
68
|
+
|
69
|
+
# stops the listening loop
|
70
|
+
def kill_alive!
|
71
|
+
@hsdq_alive = false
|
72
|
+
end
|
73
|
+
|
74
|
+
# Start the listener
|
75
|
+
# :nocov:
|
76
|
+
def start_listener(options={})
|
77
|
+
Thread.new { hsdq_start(channel, options) }
|
78
|
+
Thread.new { admin_listener }
|
79
|
+
end
|
80
|
+
# :nocov:
|
81
|
+
|
82
|
+
private
|
83
|
+
# Listening loop. Listen to the channel using a blocking left pop.
|
84
|
+
# The timeout allow the idle process to watch at regular interval if there is an admin command.
|
85
|
+
# The process is listeing only if there is available thread to be started in the pool
|
86
|
+
def hsdq_loop(channel)
|
87
|
+
p "starting listening channel #{channel}"
|
88
|
+
while hsdq_alive?
|
89
|
+
if (allow_new_threads? || !hsdq_opts[:threaded]) && hsdq_running?
|
90
|
+
raw_spark = cx_listener.blpop(channel, hsdq_opts[:timeout] )
|
91
|
+
hsdq_ignit raw_spark, hsdq_opts if raw_spark
|
92
|
+
else
|
93
|
+
# :nocov:
|
94
|
+
sleep 0.01 # occur only when hsdq_max_threads is reached
|
95
|
+
# :nocov:
|
96
|
+
end
|
97
|
+
kill_alive! if hsdq_exit?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|