hutch-schedule 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +67 -3
- data/hutch-schedule.gemspec +2 -1
- data/lib/active_job/queue_adapters/hutch_adapter.rb +5 -0
- data/lib/hutch/enqueue.rb +13 -5
- data/lib/hutch/error_handlers/max_retry.rb +41 -6
- data/lib/hutch/schedule.rb +2 -0
- data/lib/hutch/schedule/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5cdb316a95fe95813079a1b059cd8221d1eabfec
|
4
|
+
data.tar.gz: 00057ddbb5ea2c6c810f292edf04a680439125f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 693efd110acf103a7d6513bfab197460f051676a1266c33a75f12936870b44c31fdaa1b67b20cd63adde014063a9d33eb522e3de26c24dce6628a41fd2c3fa39
|
7
|
+
data.tar.gz: 0b4cd7093767aba2b28128ca4fed1d8579354f23082a220c769e4f7ed707a9404e9363aaedf6efa041254a156de3eef3cbfb1e98c9ec5fe328a7223f60d9c76e
|
data/README.md
CHANGED
@@ -23,9 +23,8 @@ Or install it yourself as:
|
|
23
23
|
Use the code below to initialize the Hutch::Schedule
|
24
24
|
|
25
25
|
```ruby
|
26
|
-
Hutch
|
27
|
-
|
28
|
-
}
|
26
|
+
Hutch.connect
|
27
|
+
Hutch::Schedule.connect
|
29
28
|
```
|
30
29
|
|
31
30
|
They will do something below:
|
@@ -34,6 +33,71 @@ They will do something below:
|
|
34
33
|
2. Declear an queue named `<hutch>_schedule_queue` and with some params:
|
35
34
|
- Set `x-dead-letter-exchange: <hutch>`: let queue republish message to default <hutch> exchange.
|
36
35
|
- Set `x-message-ttl: <30.days>`: to avoid the queue is to large, because there is no consumer with this queue.
|
36
|
+
3. If ActiveJob is loaded. it will use `ActiveJob::Base.descendants` to register all ActiveJob class to one-job-per-consumer to Hutch::Consumer
|
37
|
+
|
38
|
+
|
39
|
+
### Error Retry
|
40
|
+
If you want use error retry, then:
|
41
|
+
|
42
|
+
1. Add `Hutch::ErrorHandlers::MaxRetry` to `Hutch::Config.error_handlers` like below
|
43
|
+
```ruby
|
44
|
+
Hutch::Config.error_handlers << Hutch::ErrorHandlers::MaxRetry.new
|
45
|
+
```
|
46
|
+
|
47
|
+
2. Let `Hutch::Consumer` to include `Hutch::Enqueue` and setup `attempts`
|
48
|
+
```ruby
|
49
|
+
class PlanConsumer
|
50
|
+
include Hutch::Consumer
|
51
|
+
include Hutch::Enqueue
|
52
|
+
|
53
|
+
attempts 3
|
54
|
+
consume 'abc.plan'
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
Error retry will use ActiveJob `exponentially_longer` algorithm `(executes**4) + 2` seconds
|
59
|
+
|
60
|
+
|
61
|
+
## Rails
|
62
|
+
|
63
|
+
### Work with Hutch it`s self
|
64
|
+
Add an `hutch.rb` to `conf/initializers`:
|
65
|
+
```ruby
|
66
|
+
# reuse Hutch config.yaml file
|
67
|
+
Hutch::Config.load_from_file(Rails.root.join('config', 'config.yaml'))
|
68
|
+
# replace error_handlers with Hutch::ErrorHandlers::MaxRetry
|
69
|
+
Hutch::Config.error_handlers = [Hutch::ErrorHandlers::MaxRetry.new]
|
70
|
+
# Init Hutch
|
71
|
+
Hutch.connect
|
72
|
+
# Init Hutch::Schedule
|
73
|
+
Hutch::Schedule.connect
|
74
|
+
```
|
75
|
+
|
76
|
+
Then you can enqueue message in Rails console like below:
|
77
|
+
```ruby
|
78
|
+
PlanConsumer.enqueue(a: 1)
|
79
|
+
# or schedule message
|
80
|
+
PlanConsumer.enqueue_in(5.seconds, a: 1)
|
81
|
+
```
|
82
|
+
|
83
|
+
### Work with ActiveJob
|
84
|
+
```ruby
|
85
|
+
class EmailJob < ApplicationJob
|
86
|
+
queue_as :email
|
87
|
+
|
88
|
+
retry_on StandardError, wait: :exponentially_longer
|
89
|
+
|
90
|
+
def perform(user_id)
|
91
|
+
user = User.find(user_id)
|
92
|
+
user.send_email
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# in rails console, you can
|
97
|
+
EmailJob.perform_later(user.id)
|
98
|
+
# or
|
99
|
+
EmailJob.set(wait: 5.seconds).perform_later(user.id)
|
100
|
+
```
|
37
101
|
|
38
102
|
## Development
|
39
103
|
|
data/hutch-schedule.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'hutch/schedule/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "hutch-schedule"
|
8
8
|
spec.version = Hutch::Schedule::VERSION
|
9
|
-
spec.authors = ["
|
9
|
+
spec.authors = ["Wyatt pan"]
|
10
10
|
spec.email = ["wppurking@gmail.com"]
|
11
11
|
|
12
12
|
spec.summary = %q{Add Schedule and Error Retry To Hutch.}
|
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.require_paths = ["lib"]
|
23
23
|
|
24
24
|
spec.add_runtime_dependency 'hutch', '~> 0.24'
|
25
|
+
spec.add_runtime_dependency "multi_json"
|
25
26
|
|
26
27
|
spec.add_development_dependency "activejob"
|
27
28
|
spec.add_development_dependency "bundler", "~> 1.14"
|
data/lib/hutch/enqueue.rb
CHANGED
@@ -17,16 +17,16 @@ module Hutch
|
|
17
17
|
# publish message at a delay times
|
18
18
|
# interval: delay interval
|
19
19
|
# message: publish message
|
20
|
-
def enqueue_in(interval, message)
|
21
|
-
|
22
|
-
Hutch::Schedule.publish(enqueue_routing_key, message,
|
20
|
+
def enqueue_in(interval, message, props = {})
|
21
|
+
properties = props.merge(expiration: interval.in_milliseconds.to_i)
|
22
|
+
Hutch::Schedule.publish(enqueue_routing_key, message, properties)
|
23
23
|
end
|
24
24
|
|
25
25
|
# delay at exatly time point
|
26
|
-
def enqueue_at(time, message)
|
26
|
+
def enqueue_at(time, message, props = {})
|
27
27
|
# if time is early then now then just delay 1 second
|
28
28
|
interval = [(time.utc - Time.now.utc), 1.second].max
|
29
|
-
enqueue_in(interval, message)
|
29
|
+
enqueue_in(interval, message, props)
|
30
30
|
end
|
31
31
|
|
32
32
|
# routing_key: the purpose is to send message to hutch exchange and then routing to the correct queue,
|
@@ -35,6 +35,14 @@ module Hutch
|
|
35
35
|
raise "Routing Keys is not set!" if routing_keys.size < 1
|
36
36
|
routing_keys.to_a.last
|
37
37
|
end
|
38
|
+
|
39
|
+
def attempts(times)
|
40
|
+
@max_retries = [times, 0].max
|
41
|
+
end
|
42
|
+
|
43
|
+
def max_attempts
|
44
|
+
@max_retries || 0
|
45
|
+
end
|
38
46
|
end
|
39
47
|
end
|
40
48
|
end
|
@@ -1,15 +1,14 @@
|
|
1
1
|
require 'hutch/logging'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
2
3
|
|
3
4
|
module Hutch
|
4
5
|
module ErrorHandlers
|
6
|
+
|
7
|
+
# When reach the Max Attempts, republish this message to RabbitMQ,
|
8
|
+
# And persisted the properties[:headers] to tell RabbitMQ the `x-dead.count`
|
5
9
|
class MaxRetry
|
6
10
|
include Logging
|
7
11
|
|
8
|
-
# TODO: Need to be implement.
|
9
|
-
# 1. 获取 hutch 本身记录的 x-death 中的错误次数
|
10
|
-
# 2. 从每一个 consumer 身上寻找 max_retry 的次数, 不超过则进行延迟重试
|
11
|
-
# 3. 根据错误次数计算类似 active_job 的 exponentially_longer 延迟时间
|
12
|
-
#
|
13
12
|
# properties.headers example:
|
14
13
|
# {
|
15
14
|
# "x-death": [
|
@@ -37,7 +36,43 @@ module Hutch
|
|
37
36
|
# ]
|
38
37
|
# }
|
39
38
|
def handle(properties, payload, consumer, ex)
|
40
|
-
|
39
|
+
unless consumer.ancestors.include?(Hutch::Enqueue)
|
40
|
+
logger.warn("Consumer: #{consumer} is not include Hutch::Enqueue can`t use #enqueue_in`")
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
|
44
|
+
prop_headers = properties[:headers] || {}
|
45
|
+
attempts = failure_count(prop_headers, consumer) + 1
|
46
|
+
if attempts <= consumer.max_attempts
|
47
|
+
logger.debug("retrying, count=#{attempts}, headers:#{prop_headers}")
|
48
|
+
# execute_times = attempts - 1
|
49
|
+
consumer.enqueue_in(retry_delay(attempts - 1), MultiJson.decode(payload), { headers: prop_headers })
|
50
|
+
else
|
51
|
+
logger.debug("failing, retry_count=#{attempts}, ex:#{ex}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def retry_delay(executes)
|
56
|
+
(executes**4) + 2
|
57
|
+
end
|
58
|
+
|
59
|
+
def failure_count(headers, consumer)
|
60
|
+
if headers.nil? || headers['x-death'].nil?
|
61
|
+
0
|
62
|
+
else
|
63
|
+
x_death_array = headers['x-death'].select do |x_death|
|
64
|
+
# http://ruby-doc.org/stdlib-2.2.3/libdoc/set/rdoc/Set.html#method-i-intersect-3F
|
65
|
+
(x_death['routing-keys'].presence || []).to_set.intersect?(consumer.routing_keys)
|
66
|
+
end
|
67
|
+
|
68
|
+
if x_death_array.count > 0 && x_death_array.first['count']
|
69
|
+
# Newer versions of RabbitMQ return headers with a count key
|
70
|
+
x_death_array.inject(0) { |sum, x_death| sum + x_death['count'] }
|
71
|
+
else
|
72
|
+
# Older versions return a separate x-death header for each failure
|
73
|
+
x_death_array.count
|
74
|
+
end
|
75
|
+
end
|
41
76
|
end
|
42
77
|
end
|
43
78
|
end
|
data/lib/hutch/schedule.rb
CHANGED
@@ -2,6 +2,7 @@ require 'active_support/core_ext/module/delegation'
|
|
2
2
|
require 'active_support/core_ext/object/blank'
|
3
3
|
require 'hutch'
|
4
4
|
require 'hutch/enqueue'
|
5
|
+
require 'hutch/error_handlers/max_retry'
|
5
6
|
require 'hutch/schedule/core'
|
6
7
|
|
7
8
|
# If ActiveJob is requried then required the adapter
|
@@ -16,6 +17,7 @@ module Hutch
|
|
16
17
|
module Schedule
|
17
18
|
|
18
19
|
def self.connect(broker = Hutch.broker)
|
20
|
+
raise "Please invoke Hutch.connect before Hutch::Schedule.connect, Hutch::Schedule need Hutch.broker" unless Hutch.connected?
|
19
21
|
return if core.present?
|
20
22
|
@core = Hutch::Schedule::Core.new(broker)
|
21
23
|
@core.connect!
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hutch-schedule
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Wyatt pan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.24'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: multi_json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: activejob
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|