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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb70893978620f96f7701d003b06812d0b1de18c
4
- data.tar.gz: 739355b288a4be2a3e54cc116a3193ec7edddff1
3
+ metadata.gz: 5cdb316a95fe95813079a1b059cd8221d1eabfec
4
+ data.tar.gz: 00057ddbb5ea2c6c810f292edf04a680439125f4
5
5
  SHA512:
6
- metadata.gz: dda680d37cf8846741fa624db5e0b3d656f2dc3e7a2f1d14a347b04185f648bf81dfaf7e57b37df6a22507815c547a837efb1fabaf5e31600d7deb03652844f0
7
- data.tar.gz: 32afd0caa0ff7b1510c4158344b1e164576eb4971e4778b5f3568410dd7d04fdc1bfaad6822229ca43883e5ee59c11a4b6140dcd800abe27e3701dfa471fee67
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::Config.setup_procs << -> {
27
- Hutch::Schedule.connect(Hutch.broker)
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
 
@@ -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 = ["wyatt pan"]
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"
@@ -56,6 +56,11 @@ module ActiveJob
56
56
  define_method :process do |job_data|
57
57
  ActiveJob::Base.execute(job_data)
58
58
  end
59
+
60
+ # inspect name
61
+ define_method :inspect do
62
+ "#{job.queue_name}-anonymous-consumer"
63
+ end
59
64
  end
60
65
  end
61
66
  end
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
- props = { expiration: interval.in_milliseconds.to_i }
22
- Hutch::Schedule.publish(enqueue_routing_key, message, props)
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
- raise "Not implement"
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
@@ -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!
@@ -1,5 +1,5 @@
1
1
  module Hutch
2
2
  module Schedule
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
- - wyatt pan
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