phobos 1.4.2 → 1.5.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 +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +5 -0
- data/README.md +41 -10
- data/bin/console +4 -4
- data/circle.yml +6 -6
- data/config/phobos.yml.example +4 -0
- data/docker-compose.yml +18 -0
- data/lib/phobos.rb +7 -3
- data/lib/phobos/actions/process_batch.rb +61 -0
- data/lib/phobos/actions/process_message.rb +26 -0
- data/lib/phobos/executor.rb +12 -2
- data/lib/phobos/handler.rb +4 -0
- data/lib/phobos/instrumentation.rb +12 -6
- data/lib/phobos/listener.rb +26 -67
- data/lib/phobos/test.rb +1 -0
- data/lib/phobos/test/helper.rb +23 -0
- data/lib/phobos/version.rb +1 -1
- data/phobos.gemspec +4 -2
- data/utils/create-topic.sh +3 -7
- metadata +9 -7
- data/utils/env.sh +0 -11
- data/utils/kafka.sh +0 -43
- data/utils/start-all.sh +0 -9
- data/utils/stop-all.sh +0 -9
- data/utils/zk.sh +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 576b1c85a5d3f389caac3e09a2c1738bb2743eda
|
4
|
+
data.tar.gz: 5fc77dbf88b6bfc824245cb58c9a34558562d379
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74ff72c5bd479ee5e495d8da7ccc97c2bbe998c5ef7d601ce245a1b5e0a0054c5327c1cb100798dfb0832956fe91c8611f2aa66735d3af90e06b8dc8a915d713
|
7
|
+
data.tar.gz: 9dbf97945d5bb802fec64af350af7749273e30b9fd000e1ce435b1bfb2780b5f2dfc99d03c3a4cb3eaa1fa8e8a53b3e16ea75431c5057381f7d0993b22cb04b6
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.4.1
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
6
|
|
7
|
+
## 1.5.0 (2017-10-25)
|
8
|
+
|
9
|
+
- [enhancement] Add `before_consume` callback to support single point of decoding a message.
|
10
|
+
- [enhancement] Add module `Phobos::Test::Helper` for testing, to test consumers with minimal setup required
|
11
|
+
|
7
12
|
## 1.4.2 (2017-09-29)
|
8
13
|
|
9
14
|
- [bugfix] Async publishing always delivers messages #33
|
data/README.md
CHANGED
@@ -29,6 +29,7 @@ With Phobos by your side, all this becomes smooth sailing.
|
|
29
29
|
1. [Instrumentation](#usage-instrumentation)
|
30
30
|
1. [Plugins](#plugins)
|
31
31
|
1. [Development](#development)
|
32
|
+
1. [Test](#test)
|
32
33
|
|
33
34
|
## <a name="installation"></a> Installation
|
34
35
|
|
@@ -120,7 +121,7 @@ $ phobos start -c /var/configs/my.yml -b /opt/apps/boot.rb
|
|
120
121
|
|
121
122
|
Messages from Kafka are consumed using __handlers__. You can use Phobos __executors__ or include it in your own project [as a library](#usage-as-library), but __handlers__ will always be used. To create a handler class, simply include the module `Phobos::Handler`. This module allows Phobos to manage the life cycle of your handler.
|
122
123
|
|
123
|
-
A handler
|
124
|
+
A handler is required to implement the method `#consume(payload, metadata)`.
|
124
125
|
|
125
126
|
Instances of your handler will be created for every message, so keep a constructor without arguments. If `consume` raises an exception, Phobos will retry the message indefinitely, applying the back off configuration presented in the configuration file. The `metadata` hash will contain a key called `retry_count` with the current number of retries for this message. To skip a message, simply return from `#consume`.
|
126
127
|
|
@@ -162,6 +163,19 @@ class MyHandler
|
|
162
163
|
end
|
163
164
|
```
|
164
165
|
|
166
|
+
Finally, it is also possible to preprocess the message payload before consuming it using the `before_consume` hook which is invoked before `.around_consume` and `#consume`. The result of this operation will be assigned to payload, so it is important to return the modified payload. This can be very useful, for example if you want a single point of decoding Avro messages and want the payload as a hash instead of a binary.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class MyHandler
|
170
|
+
include Phobos::Handler
|
171
|
+
|
172
|
+
def before_consume(payload)
|
173
|
+
# optionally preprocess payload
|
174
|
+
payload
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
165
179
|
Take a look at the examples folder for some ideas.
|
166
180
|
|
167
181
|
The hander life cycle can be illustrated as:
|
@@ -170,7 +184,7 @@ The hander life cycle can be illustrated as:
|
|
170
184
|
|
171
185
|
or optionally,
|
172
186
|
|
173
|
-
`.start` -> `.around_consume` [ `#consume` ] -> `.stop`
|
187
|
+
`.start` -> `#before_consume` -> `.around_consume` [ `#consume` ] -> `.stop`
|
174
188
|
|
175
189
|
### <a name="usage-producing-messages-to-kafka"></a> Producing messages to Kafka
|
176
190
|
|
@@ -303,7 +317,7 @@ __producer__ provides configurations for all producers created over the applicat
|
|
303
317
|
|
304
318
|
__consumer__ provides configurations for all consumer groups created over the application. All [options supported by `ruby-kafka`][ruby-kafka-consumer] can be provided.
|
305
319
|
|
306
|
-
__backoff__ Phobos provides automatic retries for your handlers, if an exception is raised the listener will retry following the back off configured here
|
320
|
+
__backoff__ Phobos provides automatic retries for your handlers, if an exception is raised the listener will retry following the back off configured here. Backoff can also be configured per listener.
|
307
321
|
|
308
322
|
__listeners__ is the list of listeners configured, each listener represents a consumers group
|
309
323
|
|
@@ -403,20 +417,37 @@ List of gems that enhance Phobos:
|
|
403
417
|
## <a name="development"></a> Development
|
404
418
|
|
405
419
|
After checking out the repo:
|
406
|
-
* make sure docker is installed and running
|
420
|
+
* make sure `docker` is installed and running (for windows and mac this also includes `docker-compose`).
|
421
|
+
* Linux: make sure `docker-compose` is installed and running.
|
407
422
|
* run `bin/setup` to install dependencies
|
408
|
-
* run `
|
409
|
-
* run `rspec` to run the tests
|
423
|
+
* run `docker-compose up` to start the required kafka containers in a window
|
424
|
+
* run `rspec` to run the tests in another window
|
410
425
|
|
411
426
|
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
412
427
|
|
413
428
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
414
429
|
|
415
|
-
|
430
|
+
## <a name="test"></a> Test
|
416
431
|
|
417
|
-
|
418
|
-
|
419
|
-
|
432
|
+
Phobos exports a spec helper that can help you test your consumer. The Phobos lifecycle will conveniently be activated for you with minimal setup required.
|
433
|
+
|
434
|
+
* `process_message(handler:, payload:, metadata:, encoding: nil)` - Invokes your handler with payload and metadata, using a dummy listener (encoding is optional).
|
435
|
+
|
436
|
+
```ruby
|
437
|
+
require 'spec_helper'
|
438
|
+
|
439
|
+
describe MyConsumer do
|
440
|
+
let(:payload) { 'foo' }
|
441
|
+
let(:metadata) { 'foo' }
|
442
|
+
|
443
|
+
it 'consumes my message' do
|
444
|
+
expect(described_class).to receive(:around_consume).with(payload, metadata).once.and_call_original
|
445
|
+
expect_any_instance_of(described_class).to receive(:before_consume).with(payload).once.and_call_original
|
446
|
+
expect_any_instance_of(described_class).to receive(:consume).with(payload, metadata).once.and_call_original
|
447
|
+
|
448
|
+
process_message(handler: described_class, payload: payload, metadata: metadata)
|
449
|
+
end
|
450
|
+
end
|
420
451
|
```
|
421
452
|
|
422
453
|
## Contributing
|
data/bin/console
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'irb'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'phobos'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
8
9
|
|
9
10
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require
|
11
|
+
# require 'pry'
|
11
12
|
# Pry.start
|
12
13
|
|
13
14
|
config_path = ENV['CONFIG_PATH'] || (File.exist?('config/phobos.yml') ? 'config/phobos.yml' : 'config/phobos.yml.example')
|
14
15
|
Phobos.configure(config_path)
|
15
16
|
|
16
|
-
require "irb"
|
17
17
|
IRB.start
|
data/circle.yml
CHANGED
@@ -8,20 +8,20 @@ machine:
|
|
8
8
|
CI: true
|
9
9
|
DEFAULT_TIMEOUT: 20
|
10
10
|
ruby:
|
11
|
-
version: 2.
|
11
|
+
version: 2.4.1
|
12
12
|
|
13
13
|
dependencies:
|
14
14
|
pre:
|
15
15
|
- docker -v
|
16
|
-
- docker pull ches/kafka:0.
|
17
|
-
- docker pull jplock/zookeeper:3.4.
|
18
|
-
- gem install bundler -v 1.
|
16
|
+
- docker pull ches/kafka:0.10.2.1
|
17
|
+
- docker pull jplock/zookeeper:3.4.10
|
18
|
+
- gem install bundler -v 1.13.2
|
19
19
|
- bundle install
|
20
20
|
|
21
21
|
test:
|
22
22
|
override:
|
23
|
-
- docker run -d -p 2003:2181 --name zookeeper jplock/zookeeper:3.4.
|
24
|
-
- docker run -d -p 9092:9092 --name kafka -e KAFKA_BROKER_ID=0 -e KAFKA_ADVERTISED_HOST_NAME=localhost -e KAFKA_ADVERTISED_PORT=9092 -e ZOOKEEPER_CONNECTION_STRING=zookeeper:2181 --link zookeeper:zookeeper ches/kafka:0.
|
23
|
+
- docker run -d -p 2003:2181 --name zookeeper jplock/zookeeper:3.4.10; sleep 5
|
24
|
+
- docker run -d -p 9092:9092 --name kafka -e KAFKA_BROKER_ID=0 -e KAFKA_ADVERTISED_HOST_NAME=localhost -e KAFKA_ADVERTISED_PORT=9092 -e ZOOKEEPER_CONNECTION_STRING=zookeeper:2181 --link zookeeper:zookeeper ches/kafka:0.10.2.1; sleep 5
|
25
25
|
- bundle exec rspec -r rspec_junit_formatter --format RspecJunitFormatter -o $CIRCLE_TEST_REPORTS/rspec/unit.xml
|
26
26
|
post:
|
27
27
|
- cp log/*.log $CIRCLE_ARTIFACTS/ || true
|
data/config/phobos.yml.example
CHANGED
@@ -95,3 +95,7 @@ listeners:
|
|
95
95
|
# Apply this encoding to the message payload, if blank it uses the original encoding. This property accepts values
|
96
96
|
# defined by the ruby Encoding class (https://ruby-doc.org/core-2.3.0/Encoding.html). Ex: UTF_8, ASCII_8BIT, etc
|
97
97
|
force_encoding:
|
98
|
+
# Use this if custom backoff is required for a listener
|
99
|
+
backoff:
|
100
|
+
min_ms: 500
|
101
|
+
max_ms: 10000
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
version: '2'
|
2
|
+
services:
|
3
|
+
zookeeper:
|
4
|
+
image: jplock/zookeeper:3.4.10
|
5
|
+
ports:
|
6
|
+
- 2181:2181
|
7
|
+
|
8
|
+
kafka:
|
9
|
+
depends_on:
|
10
|
+
- zookeeper
|
11
|
+
image: ches/kafka:0.10.2.1
|
12
|
+
ports:
|
13
|
+
- 9092:9092
|
14
|
+
environment:
|
15
|
+
- KAFKA_BROKER_ID=0
|
16
|
+
- KAFKA_ADVERTISED_HOST_NAME=localhost
|
17
|
+
- KAFKA_ADVERTISED_PORT=9092
|
18
|
+
- ZOOKEEPER_CONNECTION_STRING=zookeeper:2181
|
data/lib/phobos.rb
CHANGED
@@ -9,12 +9,15 @@ require 'concurrent'
|
|
9
9
|
require 'exponential_backoff'
|
10
10
|
require 'kafka'
|
11
11
|
require 'logging'
|
12
|
+
require 'erb'
|
12
13
|
|
13
14
|
require 'phobos/deep_struct'
|
14
15
|
require 'phobos/version'
|
15
16
|
require 'phobos/instrumentation'
|
16
17
|
require 'phobos/errors'
|
17
18
|
require 'phobos/listener'
|
19
|
+
require 'phobos/actions/process_batch'
|
20
|
+
require 'phobos/actions/process_message'
|
18
21
|
require 'phobos/producer'
|
19
22
|
require 'phobos/handler'
|
20
23
|
require 'phobos/echo_handler'
|
@@ -40,9 +43,10 @@ module Phobos
|
|
40
43
|
Kafka.new(config.kafka.to_hash.merge(logger: @ruby_kafka_logger))
|
41
44
|
end
|
42
45
|
|
43
|
-
def create_exponential_backoff
|
44
|
-
|
45
|
-
|
46
|
+
def create_exponential_backoff(backoff_config = nil)
|
47
|
+
backoff_config ||= Phobos.config.backoff.to_hash
|
48
|
+
min = backoff_config[:min_ms] / 1000.0
|
49
|
+
max = backoff_config[:max_ms] / 1000.0
|
46
50
|
ExponentialBackoff.new(min, max).tap { |backoff| backoff.randomize_factor = rand }
|
47
51
|
end
|
48
52
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Phobos
|
2
|
+
module Actions
|
3
|
+
class ProcessBatch
|
4
|
+
include Phobos::Instrumentation
|
5
|
+
|
6
|
+
def initialize(listener:, batch:, listener_metadata:)
|
7
|
+
@listener = listener
|
8
|
+
@batch = batch
|
9
|
+
@listener_metadata = listener_metadata
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
@batch.messages.each do |message|
|
14
|
+
backoff = @listener.create_exponential_backoff
|
15
|
+
metadata = @listener_metadata.merge(
|
16
|
+
key: message.key,
|
17
|
+
partition: message.partition,
|
18
|
+
offset: message.offset,
|
19
|
+
retry_count: 0
|
20
|
+
)
|
21
|
+
|
22
|
+
begin
|
23
|
+
instrument('listener.process_message', metadata) do |metadata|
|
24
|
+
time_elapsed = measure do
|
25
|
+
Phobos::Actions::ProcessMessage.new(
|
26
|
+
listener: @listener,
|
27
|
+
message: message,
|
28
|
+
metadata: metadata,
|
29
|
+
encoding: @listener.encoding
|
30
|
+
).execute
|
31
|
+
end
|
32
|
+
metadata.merge!(time_elapsed: time_elapsed)
|
33
|
+
end
|
34
|
+
rescue => e
|
35
|
+
retry_count = metadata[:retry_count]
|
36
|
+
interval = backoff.interval_at(retry_count).round(2)
|
37
|
+
|
38
|
+
error = {
|
39
|
+
waiting_time: interval,
|
40
|
+
exception_class: e.class.name,
|
41
|
+
exception_message: e.message,
|
42
|
+
backtrace: e.backtrace
|
43
|
+
}
|
44
|
+
|
45
|
+
instrument('listener.retry_handler_error', error.merge(metadata)) do
|
46
|
+
Phobos.logger.error do
|
47
|
+
{ message: "error processing message, waiting #{interval}s" }.merge(error).merge(metadata)
|
48
|
+
end
|
49
|
+
|
50
|
+
sleep interval
|
51
|
+
metadata.merge!(retry_count: retry_count + 1)
|
52
|
+
end
|
53
|
+
|
54
|
+
raise Phobos::AbortError if @listener.should_stop?
|
55
|
+
retry
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Phobos
|
2
|
+
module Actions
|
3
|
+
class ProcessMessage
|
4
|
+
def initialize(listener:, message:, metadata:, encoding:)
|
5
|
+
@listener = listener
|
6
|
+
@message = message
|
7
|
+
@metadata = metadata
|
8
|
+
@encoding = encoding
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
payload = force_encoding(@message.value)
|
13
|
+
decoded_payload = @listener.handler_class.new.before_consume(payload)
|
14
|
+
@listener.handler_class.around_consume(decoded_payload, @metadata) do
|
15
|
+
@listener.handler_class.new.consume(decoded_payload, @metadata)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def force_encoding(value)
|
22
|
+
@encoding ? value.force_encoding(@encoding) : value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/phobos/executor.rb
CHANGED
@@ -1,7 +1,17 @@
|
|
1
1
|
module Phobos
|
2
2
|
class Executor
|
3
3
|
include Phobos::Instrumentation
|
4
|
-
LISTENER_OPTS = %i(
|
4
|
+
LISTENER_OPTS = %i(
|
5
|
+
handler
|
6
|
+
group_id
|
7
|
+
topic
|
8
|
+
min_bytes
|
9
|
+
max_wait_time
|
10
|
+
force_encoding
|
11
|
+
start_from_beginning
|
12
|
+
max_bytes_per_partition
|
13
|
+
backoff
|
14
|
+
).freeze
|
5
15
|
|
6
16
|
def initialize
|
7
17
|
@threads = Concurrent::Array.new
|
@@ -49,7 +59,7 @@ module Phobos
|
|
49
59
|
|
50
60
|
def run_listener(listener)
|
51
61
|
retry_count = 0
|
52
|
-
backoff =
|
62
|
+
backoff = listener.create_exponential_backoff
|
53
63
|
|
54
64
|
begin
|
55
65
|
listener.start
|
data/lib/phobos/handler.rb
CHANGED
@@ -2,12 +2,6 @@ module Phobos
|
|
2
2
|
module Instrumentation
|
3
3
|
NAMESPACE = 'phobos'
|
4
4
|
|
5
|
-
def instrument(event, extra = {})
|
6
|
-
ActiveSupport::Notifications.instrument("#{NAMESPACE}.#{event}", extra) do |extra|
|
7
|
-
yield(extra) if block_given?
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
5
|
def self.subscribe(event)
|
12
6
|
ActiveSupport::Notifications.subscribe("#{NAMESPACE}.#{event}") do |*args|
|
13
7
|
yield ActiveSupport::Notifications::Event.new(*args) if block_given?
|
@@ -17,5 +11,17 @@ module Phobos
|
|
17
11
|
def self.unsubscribe(subscriber)
|
18
12
|
ActiveSupport::Notifications.unsubscribe(subscriber)
|
19
13
|
end
|
14
|
+
|
15
|
+
def instrument(event, extra = {})
|
16
|
+
ActiveSupport::Notifications.instrument("#{NAMESPACE}.#{event}", extra) do |extra|
|
17
|
+
yield(extra) if block_given?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def measure
|
22
|
+
start = Time.now.utc
|
23
|
+
yield if block_given?
|
24
|
+
(Time.now.utc - start).round(3)
|
25
|
+
end
|
20
26
|
end
|
21
27
|
end
|
data/lib/phobos/listener.rb
CHANGED
@@ -6,12 +6,17 @@ module Phobos
|
|
6
6
|
DEFAULT_MAX_BYTES_PER_PARTITION = 524288 # 512 KB
|
7
7
|
|
8
8
|
attr_reader :group_id, :topic, :id
|
9
|
+
attr_reader :handler_class, :encoding
|
9
10
|
|
10
|
-
def initialize(handler:, group_id:, topic:, min_bytes: nil,
|
11
|
+
def initialize(handler:, group_id:, topic:, min_bytes: nil,
|
12
|
+
max_wait_time: nil, force_encoding: nil,
|
13
|
+
start_from_beginning: true, backoff: nil,
|
14
|
+
max_bytes_per_partition: DEFAULT_MAX_BYTES_PER_PARTITION)
|
11
15
|
@id = SecureRandom.hex[0...6]
|
12
16
|
@handler_class = handler
|
13
17
|
@group_id = group_id
|
14
18
|
@topic = topic
|
19
|
+
@backoff = backoff
|
15
20
|
@subscribe_opts = {
|
16
21
|
start_from_beginning: start_from_beginning,
|
17
22
|
max_bytes_per_partition: max_bytes_per_partition
|
@@ -47,12 +52,18 @@ module Phobos
|
|
47
52
|
}.merge(listener_metadata)
|
48
53
|
|
49
54
|
instrument('listener.process_batch', batch_metadata) do |batch_metadata|
|
50
|
-
time_elapsed = measure
|
55
|
+
time_elapsed = measure do
|
56
|
+
Phobos::Actions::ProcessBatch.new(
|
57
|
+
listener: self,
|
58
|
+
batch: batch,
|
59
|
+
listener_metadata: listener_metadata
|
60
|
+
).execute
|
61
|
+
end
|
51
62
|
batch_metadata.merge!(time_elapsed: time_elapsed)
|
52
63
|
Phobos.logger.info { Hash(message: 'Committed offset').merge(batch_metadata) }
|
53
64
|
end
|
54
65
|
|
55
|
-
return if
|
66
|
+
return if should_stop?
|
56
67
|
end
|
57
68
|
|
58
69
|
# Abort is an exception to prevent the consumer from committing the offset.
|
@@ -62,9 +73,7 @@ module Phobos
|
|
62
73
|
#
|
63
74
|
rescue Kafka::ProcessingError, Phobos::AbortError
|
64
75
|
instrument('listener.retry_aborted', listener_metadata) do
|
65
|
-
Phobos.logger.info
|
66
|
-
{message: 'Retry loop aborted, listener is shutting down'}.merge(listener_metadata)
|
67
|
-
end
|
76
|
+
Phobos.logger.info({ message: 'Retry loop aborted, listener is shutting down' }.merge(listener_metadata))
|
68
77
|
end
|
69
78
|
end
|
70
79
|
|
@@ -80,14 +89,14 @@ module Phobos
|
|
80
89
|
end
|
81
90
|
|
82
91
|
@kafka_client.close
|
83
|
-
if
|
92
|
+
if should_stop?
|
84
93
|
Phobos.logger.info { Hash(message: 'Listener stopped').merge(listener_metadata) }
|
85
94
|
end
|
86
95
|
end
|
87
96
|
end
|
88
97
|
|
89
98
|
def stop
|
90
|
-
return if
|
99
|
+
return if should_stop?
|
91
100
|
instrument('listener.stopping', listener_metadata) do
|
92
101
|
Phobos.logger.info { Hash(message: 'Listener stopping').merge(listener_metadata) }
|
93
102
|
@consumer&.stop
|
@@ -95,73 +104,23 @@ module Phobos
|
|
95
104
|
end
|
96
105
|
end
|
97
106
|
|
98
|
-
|
99
|
-
|
100
|
-
def listener_metadata
|
101
|
-
{ listener_id: id, group_id: group_id, topic: topic }
|
107
|
+
def create_exponential_backoff
|
108
|
+
Phobos.create_exponential_backoff(@backoff)
|
102
109
|
end
|
103
110
|
|
104
|
-
def
|
105
|
-
|
106
|
-
backoff = Phobos.create_exponential_backoff
|
107
|
-
metadata = listener_metadata.merge(
|
108
|
-
key: message.key,
|
109
|
-
partition: message.partition,
|
110
|
-
offset: message.offset,
|
111
|
-
retry_count: 0
|
112
|
-
)
|
113
|
-
|
114
|
-
begin
|
115
|
-
instrument('listener.process_message', metadata) do |metadata|
|
116
|
-
time_elapsed = measure { process_message(message, metadata) }
|
117
|
-
metadata.merge!(time_elapsed: time_elapsed)
|
118
|
-
end
|
119
|
-
rescue => e
|
120
|
-
retry_count = metadata[:retry_count]
|
121
|
-
interval = backoff.interval_at(retry_count).round(2)
|
122
|
-
|
123
|
-
error = {
|
124
|
-
waiting_time: interval,
|
125
|
-
exception_class: e.class.name,
|
126
|
-
exception_message: e.message,
|
127
|
-
backtrace: e.backtrace
|
128
|
-
}
|
129
|
-
|
130
|
-
instrument('listener.retry_handler_error', error.merge(metadata)) do
|
131
|
-
Phobos.logger.error do
|
132
|
-
{message: "error processing message, waiting #{interval}s"}.merge(error).merge(metadata)
|
133
|
-
end
|
134
|
-
|
135
|
-
sleep interval
|
136
|
-
metadata.merge!(retry_count: retry_count + 1)
|
137
|
-
end
|
138
|
-
|
139
|
-
raise Phobos::AbortError if @signal_to_stop
|
140
|
-
retry
|
141
|
-
end
|
142
|
-
end
|
111
|
+
def should_stop?
|
112
|
+
@signal_to_stop == true
|
143
113
|
end
|
144
114
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
115
|
+
private
|
116
|
+
|
117
|
+
def listener_metadata
|
118
|
+
{ listener_id: id, group_id: group_id, topic: topic }
|
150
119
|
end
|
151
120
|
|
152
121
|
def create_kafka_consumer
|
153
122
|
configs = Phobos.config.consumer_hash.select { |k| KAFKA_CONSUMER_OPTS.include?(k) }
|
154
|
-
@kafka_client.consumer({group_id: group_id}.merge(configs))
|
155
|
-
end
|
156
|
-
|
157
|
-
def force_encoding(value)
|
158
|
-
@encoding ? value.force_encoding(@encoding) : value
|
159
|
-
end
|
160
|
-
|
161
|
-
def measure
|
162
|
-
start = Time.now.utc
|
163
|
-
yield if block_given?
|
164
|
-
(Time.now.utc - start).round(3)
|
123
|
+
@kafka_client.consumer({ group_id: group_id }.merge(configs))
|
165
124
|
end
|
166
125
|
|
167
126
|
def compact(hash)
|
data/lib/phobos/test.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'phobos/test/helper'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Phobos
|
2
|
+
module Test
|
3
|
+
module Helper
|
4
|
+
KafkaMessage = Struct.new(:value)
|
5
|
+
|
6
|
+
def process_message(handler:, payload:, metadata:, force_encoding: nil)
|
7
|
+
listener = Phobos::Listener.new(
|
8
|
+
handler: handler,
|
9
|
+
group_id: 'test-group',
|
10
|
+
topic: 'test-topic',
|
11
|
+
force_encoding: force_encoding
|
12
|
+
)
|
13
|
+
|
14
|
+
Phobos::Actions::ProcessMessage.new(
|
15
|
+
listener: listener,
|
16
|
+
message: KafkaMessage.new(payload),
|
17
|
+
metadata: metadata,
|
18
|
+
encoding: listener.encoding
|
19
|
+
).execute
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/phobos/version.rb
CHANGED
data/phobos.gemspec
CHANGED
@@ -12,7 +12,8 @@ Gem::Specification.new do |spec|
|
|
12
12
|
'Sergey Evstifeev',
|
13
13
|
'Thiago R. Colucci',
|
14
14
|
'Martin Svalin',
|
15
|
-
'Francisco Juan'
|
15
|
+
'Francisco Juan',
|
16
|
+
'Tommy Gustafsson'
|
16
17
|
]
|
17
18
|
spec.email = [
|
18
19
|
'ornelas.tulio@gmail.com',
|
@@ -20,7 +21,8 @@ Gem::Specification.new do |spec|
|
|
20
21
|
'sergey.evstifeev@gmail.com',
|
21
22
|
'ticolucci@gmail.com',
|
22
23
|
'martin@lite.nu',
|
23
|
-
'francisco.juan@gmail.com'
|
24
|
+
'francisco.juan@gmail.com',
|
25
|
+
'tommydgustafsson@gmail.com'
|
24
26
|
]
|
25
27
|
|
26
28
|
spec.summary = %q{Simplifying Kafka for ruby apps}
|
data/utils/create-topic.sh
CHANGED
@@ -1,17 +1,13 @@
|
|
1
1
|
#!/bin/bash
|
2
2
|
set -eu
|
3
3
|
|
4
|
-
UTILS_DIR=$(dirname $0)
|
5
|
-
source ${UTILS_DIR}/env.sh
|
6
|
-
|
7
|
-
ZK_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' zookeeper)
|
8
4
|
TOPIC=${TOPIC:='test'}
|
9
5
|
PARTITIONS=${PARTITIONS:=2}
|
10
6
|
|
11
7
|
echo "creating topic ${TOPIC}, partitions ${PARTITIONS}"
|
12
|
-
docker run --rm
|
13
|
-
--create \
|
8
|
+
docker-compose run --rm -e PARTITIONS=$PARTITIONS -e TOPIC=$TOPIC kafka kafka-topics.sh --create \
|
14
9
|
--topic $TOPIC \
|
15
10
|
--replication-factor 1 \
|
16
11
|
--partitions $PARTITIONS \
|
17
|
-
--zookeeper
|
12
|
+
--zookeeper zookeeper:2181 \
|
13
|
+
2>/dev/null
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phobos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Túlio Ornelas
|
@@ -10,10 +10,11 @@ authors:
|
|
10
10
|
- Thiago R. Colucci
|
11
11
|
- Martin Svalin
|
12
12
|
- Francisco Juan
|
13
|
+
- Tommy Gustafsson
|
13
14
|
autorequire:
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
|
-
date: 2017-
|
17
|
+
date: 2017-10-26 00:00:00.000000000 Z
|
17
18
|
dependencies:
|
18
19
|
- !ruby/object:Gem::Dependency
|
19
20
|
name: bundler
|
@@ -235,6 +236,7 @@ email:
|
|
235
236
|
- ticolucci@gmail.com
|
236
237
|
- martin@lite.nu
|
237
238
|
- francisco.juan@gmail.com
|
239
|
+
- tommydgustafsson@gmail.com
|
238
240
|
executables:
|
239
241
|
- phobos
|
240
242
|
extensions: []
|
@@ -254,10 +256,13 @@ files:
|
|
254
256
|
- bin/setup
|
255
257
|
- circle.yml
|
256
258
|
- config/phobos.yml.example
|
259
|
+
- docker-compose.yml
|
257
260
|
- examples/handler_saving_events_database.rb
|
258
261
|
- examples/handler_using_async_producer.rb
|
259
262
|
- examples/publishing_messages_without_consumer.rb
|
260
263
|
- lib/phobos.rb
|
264
|
+
- lib/phobos/actions/process_batch.rb
|
265
|
+
- lib/phobos/actions/process_message.rb
|
261
266
|
- lib/phobos/cli.rb
|
262
267
|
- lib/phobos/cli/runner.rb
|
263
268
|
- lib/phobos/cli/start.rb
|
@@ -269,15 +274,12 @@ files:
|
|
269
274
|
- lib/phobos/instrumentation.rb
|
270
275
|
- lib/phobos/listener.rb
|
271
276
|
- lib/phobos/producer.rb
|
277
|
+
- lib/phobos/test.rb
|
278
|
+
- lib/phobos/test/helper.rb
|
272
279
|
- lib/phobos/version.rb
|
273
280
|
- logo.png
|
274
281
|
- phobos.gemspec
|
275
282
|
- utils/create-topic.sh
|
276
|
-
- utils/env.sh
|
277
|
-
- utils/kafka.sh
|
278
|
-
- utils/start-all.sh
|
279
|
-
- utils/stop-all.sh
|
280
|
-
- utils/zk.sh
|
281
283
|
homepage: https://github.com/klarna/phobos
|
282
284
|
licenses:
|
283
285
|
- Apache License Version 2.0
|
data/utils/env.sh
DELETED
data/utils/kafka.sh
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
#!/bin/bash
|
2
|
-
set -eu
|
3
|
-
|
4
|
-
source $(dirname $0)/env.sh
|
5
|
-
|
6
|
-
start() {
|
7
|
-
[ $FORCE_PULL = 'true' ] && docker pull $KAFKA_IMAGE:$KAFKA_IMAGE_VERSION
|
8
|
-
ZK_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' zookeeper)
|
9
|
-
|
10
|
-
docker run \
|
11
|
-
-d \
|
12
|
-
-p 9092:9092 \
|
13
|
-
--name kafka \
|
14
|
-
-e KAFKA_BROKER_ID=0 \
|
15
|
-
-e KAFKA_ADVERTISED_HOST_NAME=localhost \
|
16
|
-
-e KAFKA_ADVERTISED_PORT=9092 \
|
17
|
-
-e ZOOKEEPER_CONNECTION_STRING=zookeeper:2181 \
|
18
|
-
--link zookeeper:zookeeper \
|
19
|
-
$KAFKA_IMAGE:$KAFKA_IMAGE_VERSION
|
20
|
-
|
21
|
-
# The following statement waits until kafka is up and running
|
22
|
-
docker exec kafka bash -c "JMX_PORT=9998 ./bin/kafka-topics.sh --zookeeper $ZK_IP:2181 --list 2> /dev/null"
|
23
|
-
[ $? != '0' ] && echo "[kafka] failed to start"
|
24
|
-
}
|
25
|
-
|
26
|
-
stop() {
|
27
|
-
docker stop kafka > /dev/null 2>&1 || true
|
28
|
-
docker rm kafka > /dev/null 2>&1 || true
|
29
|
-
}
|
30
|
-
|
31
|
-
case "$1" in
|
32
|
-
start)
|
33
|
-
echo "[kafka] starting $KAFKA_IMAGE:$KAFKA_IMAGE_VERSION"
|
34
|
-
stop
|
35
|
-
start
|
36
|
-
echo "[kafka] started"
|
37
|
-
;;
|
38
|
-
stop)
|
39
|
-
printf "[kafka] stopping... "
|
40
|
-
stop
|
41
|
-
echo "Done"
|
42
|
-
;;
|
43
|
-
esac
|
data/utils/start-all.sh
DELETED
data/utils/stop-all.sh
DELETED
data/utils/zk.sh
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
#!/bin/bash
|
2
|
-
set -eu
|
3
|
-
|
4
|
-
UTILS_DIR=$(dirname $0)
|
5
|
-
source ${UTILS_DIR}/env.sh
|
6
|
-
|
7
|
-
start() {
|
8
|
-
[ $FORCE_PULL = 'true' ] && docker pull $ZK_IMAGE:$ZK_IMAGE_VERSION
|
9
|
-
|
10
|
-
docker run \
|
11
|
-
-d \
|
12
|
-
-p 2181:2181 \
|
13
|
-
--name zookeeper \
|
14
|
-
$ZK_IMAGE:$ZK_IMAGE_VERSION
|
15
|
-
|
16
|
-
sleep 3
|
17
|
-
}
|
18
|
-
|
19
|
-
stop() {
|
20
|
-
docker stop zookeeper > /dev/null 2>&1 || true
|
21
|
-
docker rm zookeeper > /dev/null 2>&1 || true
|
22
|
-
}
|
23
|
-
|
24
|
-
case "$1" in
|
25
|
-
start)
|
26
|
-
echo "[zookeeper] starting $ZK_IMAGE:$ZK_IMAGE_VERSION"
|
27
|
-
stop
|
28
|
-
start
|
29
|
-
echo "[zookeeper] started"
|
30
|
-
;;
|
31
|
-
stop)
|
32
|
-
printf "[zookeeper] stopping... "
|
33
|
-
stop
|
34
|
-
echo "Done"
|
35
|
-
;;
|
36
|
-
esac
|