phobos 1.5.0 → 1.6.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/.dockerignore +13 -0
- data/.env +1 -0
- data/.travis.yml +35 -0
- data/CHANGELOG.md +66 -42
- data/Dockerfile +3 -2
- data/README.md +26 -2
- data/config/phobos.yml.example +13 -0
- data/docker-compose.yml +12 -0
- data/lib/phobos.rb +15 -3
- data/lib/phobos/actions/process_batch.rb +17 -42
- data/lib/phobos/actions/process_message.rb +55 -7
- data/lib/phobos/cli.rb +7 -0
- data/lib/phobos/cli/start.rb +33 -5
- data/lib/phobos/executor.rb +1 -0
- data/lib/phobos/listener.rb +32 -23
- data/lib/phobos/version.rb +1 -1
- data/phobos.gemspec +5 -7
- metadata +25 -45
- data/circle.yml +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05b2968d8c1fc02f229055c4163bd4c4472a9c1c
|
4
|
+
data.tar.gz: cca82af11ae5d8aa4996a8802bc516eda2fd8b4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61cb3f4940c594967b2c0c20a8ef3e7ac22c811baaea4dd8b2aa482c76aa7c4ae2d93d4a1986b91fc4a6b4b7d6cdae66cc63e23278646b20dbed5346cfa3f31d
|
7
|
+
data.tar.gz: a5fbf1313da2817be54c5213a64d0e3e9220d621fc1efd9230cc9285c926d8041a72badf4133679065a888891c8f54da04c80e94d62e9f792542c3c76d02ebb6
|
data/.dockerignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
sudo: required
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.4.1
|
5
|
+
|
6
|
+
services:
|
7
|
+
- docker
|
8
|
+
|
9
|
+
env:
|
10
|
+
global:
|
11
|
+
- DEFAULT_TIMEOUT=20
|
12
|
+
- CC_TEST_REPORTER_ID=ce08ec7297e0161c25b6b0c86e7bd4b9eb85b15de9e2a17fe419b8439aa40def
|
13
|
+
|
14
|
+
before_install:
|
15
|
+
- env
|
16
|
+
- docker-compose --version
|
17
|
+
- docker --version
|
18
|
+
- docker-compose config
|
19
|
+
- docker-compose up -d --force-recreate kafka zookeeper
|
20
|
+
- docker-compose build test
|
21
|
+
|
22
|
+
before_script:
|
23
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
24
|
+
- chmod +x ./cc-test-reporter
|
25
|
+
- if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then ./cc-test-reporter before-build || echo "Skipping CC coverage before-build"; fi
|
26
|
+
- mkdir coverage/
|
27
|
+
- touch ./coverage/.resultset.json
|
28
|
+
|
29
|
+
script:
|
30
|
+
- docker-compose run --rm test
|
31
|
+
|
32
|
+
after_script:
|
33
|
+
- cat ./coverage/.resultset.json | sed "s|/opt/phobos|$PWD|" > ./coverage/.newresultset.json
|
34
|
+
- cp ./coverage/.newresultset.json ./coverage/.resultset.json
|
35
|
+
- if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT || echo "Skipping CC coverage after-build"; fi
|
data/CHANGELOG.md
CHANGED
@@ -4,45 +4,69 @@ 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
|
-
##
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
-
|
19
|
-
-
|
20
|
-
|
21
|
-
## 1.
|
22
|
-
|
23
|
-
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
7
|
+
## UNRELEASED
|
8
|
+
|
9
|
+
## [1.6.0] - 2017-11-16
|
10
|
+
### Added
|
11
|
+
- Support for outputting logs as json #50
|
12
|
+
- Make configuration, especially of listeners, more flexible. #31
|
13
|
+
- Phobos Discord chat
|
14
|
+
- Support for consuming `each_message` instead of `each_batch` via the delivery listener option. #21
|
15
|
+
- Instantiate a single handler class instance and use that both for `consume` and `before_consume`. #47
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- Pin ruby-kafka version to < 0.5.0 #48
|
19
|
+
- Update changelog to follow the [Keep a Changelog](http://keepachangelog.com/) structure
|
20
|
+
|
21
|
+
## [1.5.0] - 2017-10-25
|
22
|
+
### Added
|
23
|
+
- Add `before_consume` callback to support single point of decoding a message klarna/phobos_db_checkpoint#34
|
24
|
+
- Add `Phobos::Test::Helper` for testing, to test consumers with minimal setup required
|
25
|
+
|
26
|
+
### Changed
|
27
|
+
- Allow configuration of backoff per listener #35
|
28
|
+
- Move container orchestration into docker-compose
|
29
|
+
- Update docker images #38
|
30
|
+
|
31
|
+
### Fixed
|
32
|
+
- Make specs run locally #36
|
33
|
+
|
34
|
+
## [1.4.2] - 2017-09-29
|
35
|
+
### Fixed
|
36
|
+
- Async publishing always delivers messages #33
|
37
|
+
|
38
|
+
## [1.4.1] - 2017-08-22
|
39
|
+
### Added
|
40
|
+
- Update dev dependencies to fix warnings for the new unified Integer class
|
41
|
+
|
42
|
+
### Fixed
|
43
|
+
- Include the error `Kafka::ProcessingError` into the abort block
|
44
|
+
|
45
|
+
## [1.4.0] - 2017-08-21
|
46
|
+
### Added
|
47
|
+
- Support for hash provided settings #30
|
48
|
+
|
49
|
+
## [1.3.0] - 2017-06-15
|
50
|
+
### Added
|
51
|
+
- Support for ERB syntax in Phobos config file #26
|
52
|
+
|
53
|
+
## [1.2.1] - 2016-10-12
|
54
|
+
### Fixed
|
55
|
+
- Ensure JSON layout for log files
|
56
|
+
|
57
|
+
## [1.2.0] - 2016-10-10
|
58
|
+
### Added
|
59
|
+
- Log file can be disabled #20
|
60
|
+
- Property (time_elapsed) available for notifications `listener.process_message` and `listener.process_batch` #24
|
61
|
+
- Option to configure ruby-kafka logger #23
|
62
|
+
|
63
|
+
## [1.1.0] - 2016-09-02
|
64
|
+
### Added
|
65
|
+
- Removed Hashie as a dependency #12
|
66
|
+
- Allow configuring consumers min_bytes & max_wait_time #15
|
67
|
+
- Allow configuring producers max_queue_size, delivery_threshold & delivery_interval #16
|
68
|
+
- Allow configuring force_encoding for message payload #18
|
69
|
+
|
70
|
+
## [1.0.0] - 2016-08-08
|
71
|
+
### Added
|
72
|
+
- Published on Github!
|
data/Dockerfile
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
FROM ruby:2.
|
1
|
+
FROM ruby:2.4.1-alpine
|
2
2
|
|
3
3
|
RUN apk update && apk upgrade && \
|
4
4
|
apk add --no-cache bash git openssh build-base
|
5
|
+
RUN gem install bundler -v 1.16.0
|
5
6
|
|
6
7
|
WORKDIR /opt/phobos
|
7
8
|
|
8
9
|
ADD Gemfile Gemfile
|
9
10
|
ADD phobos.gemspec phobos.gemspec
|
10
11
|
ADD lib/phobos/version.rb lib/phobos/version.rb
|
12
|
+
RUN bundle install
|
11
13
|
|
12
|
-
RUN ["bundle", "install"]
|
13
14
|
ADD . .
|
data/README.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|

|
2
2
|
|
3
|
-
[](https://travis-ci.org/klarna/phobos)
|
4
|
+
[](https://codeclimate.com/github/klarna/phobos/maintainability)
|
5
|
+
[](https://codeclimate.com/github/klarna/phobos/test_coverage)
|
6
|
+
[](https://discord.gg/rfMUBVD)
|
5
7
|
|
6
8
|
# Phobos
|
7
9
|
|
@@ -117,6 +119,14 @@ By default, the __start__ command will look for the configuration file at `confi
|
|
117
119
|
```sh
|
118
120
|
$ phobos start -c /var/configs/my.yml -b /opt/apps/boot.rb
|
119
121
|
```
|
122
|
+
|
123
|
+
You may also choose to configure phobos with a hash from within your boot file.
|
124
|
+
In this case, disable loading the config file with the `--skip-config` option:
|
125
|
+
|
126
|
+
```sh
|
127
|
+
$ phobos start -b /opt/apps/boot.rb --skip-config
|
128
|
+
```
|
129
|
+
|
120
130
|
### <a name="usage-consuming-messages-from-kafka"></a> Consuming messages from Kafka
|
121
131
|
|
122
132
|
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.
|
@@ -325,6 +335,20 @@ __listeners__ is the list of listeners configured, each listener represents a co
|
|
325
335
|
[ruby-kafka-consumer]: http://www.rubydoc.info/gems/ruby-kafka/Kafka%2FClient%3Aconsumer
|
326
336
|
[ruby-kafka-producer]: http://www.rubydoc.info/gems/ruby-kafka/Kafka%2FClient%3Aproducer
|
327
337
|
|
338
|
+
#### Additional listener configuration
|
339
|
+
|
340
|
+
In some cases it's useful to share _most_ of the configuration between
|
341
|
+
multiple phobos processes, but have each process run different listeners. In
|
342
|
+
that case, a separate yaml file can be created and loaded with the `-l` flag.
|
343
|
+
Example:
|
344
|
+
|
345
|
+
```sh
|
346
|
+
$ phobos start -c /var/configs/my.yml -l /var/configs/additional_listeners.yml
|
347
|
+
```
|
348
|
+
|
349
|
+
Note that the config file _must_ still specify a listeners section, though it
|
350
|
+
can be empty.
|
351
|
+
|
328
352
|
### <a name="usage-instrumentation"></a> Instrumentation
|
329
353
|
|
330
354
|
Some operations are instrumented using [Active Support Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html).
|
data/config/phobos.yml.example
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
logger:
|
2
2
|
# Optional log file, set to false or remove to disable it
|
3
3
|
file: log/phobos.log
|
4
|
+
# Optional output format for stdout, default is false (human readable).
|
5
|
+
# Set to true to enable json output.
|
6
|
+
stdout_json: false
|
4
7
|
level: info
|
5
8
|
# Comment the block to disable ruby-kafka logs
|
6
9
|
ruby_kafka:
|
@@ -95,6 +98,16 @@ listeners:
|
|
95
98
|
# Apply this encoding to the message payload, if blank it uses the original encoding. This property accepts values
|
96
99
|
# defined by the ruby Encoding class (https://ruby-doc.org/core-2.3.0/Encoding.html). Ex: UTF_8, ASCII_8BIT, etc
|
97
100
|
force_encoding:
|
101
|
+
# Specify the delivery method for a listener.
|
102
|
+
# Possible values: [`message`, `batch` (default)]
|
103
|
+
# - `message` will yield individual messages from Ruby Kafka using `each_message` and will commit/heartbeat at every consumed message.
|
104
|
+
# This is overall a bit slower than using batch, but easier to configure.
|
105
|
+
# - `batch` will yield batches from Ruby Kafka using `each_batch`, and commit/heartbeat at every consumed batch.
|
106
|
+
# Due to implementation in Ruby Kafka, it should be noted that when configuring large batch sizes in combination with taking long time to consume messages,
|
107
|
+
# your consumers might get kicked from the Kafka cluster for not sending a heartbeat within the expected interval.
|
108
|
+
# Take this into consideration when determining your configuration.
|
109
|
+
# Note: Ultimately commit/heartbeart will depend on the offset commit options and the heartbeat interval.
|
110
|
+
delivery: batch
|
98
111
|
# Use this if custom backoff is required for a listener
|
99
112
|
backoff:
|
100
113
|
min_ms: 500
|
data/docker-compose.yml
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
version: '2'
|
2
2
|
services:
|
3
|
+
test:
|
4
|
+
depends_on:
|
5
|
+
- kafka
|
6
|
+
build:
|
7
|
+
context: .
|
8
|
+
network_mode: service:kafka
|
9
|
+
environment:
|
10
|
+
- "DEFAULT_TIMEOUT=${DEFAULT_TIMEOUT}"
|
11
|
+
command: rspec
|
12
|
+
volumes:
|
13
|
+
- ./coverage:/opt/phobos/coverage
|
14
|
+
|
3
15
|
zookeeper:
|
4
16
|
image: jplock/zookeeper:3.4.10
|
5
17
|
ports:
|
data/lib/phobos.rb
CHANGED
@@ -35,10 +35,16 @@ module Phobos
|
|
35
35
|
@config = DeepStruct.new(fetch_settings(configuration))
|
36
36
|
@config.class.send(:define_method, :producer_hash) { Phobos.config.producer&.to_hash }
|
37
37
|
@config.class.send(:define_method, :consumer_hash) { Phobos.config.consumer&.to_hash }
|
38
|
+
@config.listeners ||= []
|
38
39
|
configure_logger
|
39
40
|
logger.info { Hash(message: 'Phobos configured', env: ENV['RACK_ENV']) }
|
40
41
|
end
|
41
42
|
|
43
|
+
def add_listeners(listeners_configuration)
|
44
|
+
listeners_config = DeepStruct.new(fetch_settings(listeners_configuration))
|
45
|
+
@config.listeners += listeners_config.listeners
|
46
|
+
end
|
47
|
+
|
42
48
|
def create_kafka_client
|
43
49
|
Kafka.new(config.kafka.to_hash.merge(logger: @ruby_kafka_logger))
|
44
50
|
end
|
@@ -54,8 +60,14 @@ module Phobos
|
|
54
60
|
log_file = config.logger.file
|
55
61
|
ruby_kafka = config.logger.ruby_kafka
|
56
62
|
date_pattern = '%Y-%m-%dT%H:%M:%S:%L%zZ'
|
57
|
-
|
58
|
-
|
63
|
+
json_layout = Logging.layouts.json(date_pattern: date_pattern)
|
64
|
+
|
65
|
+
stdout_layout = if config.logger.stdout_json == true
|
66
|
+
json_layout
|
67
|
+
else
|
68
|
+
Logging.layouts.pattern(date_pattern: date_pattern)
|
69
|
+
end
|
70
|
+
|
59
71
|
appenders = [Logging.appenders.stdout(layout: stdout_layout)]
|
60
72
|
|
61
73
|
Logging.backtrace(true)
|
@@ -63,7 +75,7 @@ module Phobos
|
|
63
75
|
|
64
76
|
if log_file
|
65
77
|
FileUtils.mkdir_p(File.dirname(log_file))
|
66
|
-
appenders << Logging.appenders.file(log_file, layout:
|
78
|
+
appenders << Logging.appenders.file(log_file, layout: json_layout)
|
67
79
|
end
|
68
80
|
|
69
81
|
@ruby_kafka_logger = nil
|
@@ -3,57 +3,32 @@ module Phobos
|
|
3
3
|
class ProcessBatch
|
4
4
|
include Phobos::Instrumentation
|
5
5
|
|
6
|
+
attr_reader :metadata
|
7
|
+
|
6
8
|
def initialize(listener:, batch:, listener_metadata:)
|
7
9
|
@listener = listener
|
8
10
|
@batch = batch
|
9
11
|
@listener_metadata = listener_metadata
|
12
|
+
@metadata = listener_metadata.merge(
|
13
|
+
batch_size: batch.messages.count,
|
14
|
+
partition: batch.partition,
|
15
|
+
offset_lag: batch.offset_lag
|
16
|
+
)
|
10
17
|
end
|
11
18
|
|
12
19
|
def execute
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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)
|
20
|
+
instrument('listener.process_batch', @metadata) do |metadata|
|
21
|
+
time_elapsed = measure do
|
22
|
+
@batch.messages.each do |message|
|
23
|
+
Phobos::Actions::ProcessMessage.new(
|
24
|
+
listener: @listener,
|
25
|
+
message: message,
|
26
|
+
listener_metadata: @listener_metadata
|
27
|
+
).execute
|
52
28
|
end
|
53
|
-
|
54
|
-
raise Phobos::AbortError if @listener.should_stop?
|
55
|
-
retry
|
56
29
|
end
|
30
|
+
|
31
|
+
metadata.merge!(time_elapsed: time_elapsed)
|
57
32
|
end
|
58
33
|
end
|
59
34
|
end
|
@@ -1,25 +1,73 @@
|
|
1
1
|
module Phobos
|
2
2
|
module Actions
|
3
3
|
class ProcessMessage
|
4
|
-
|
4
|
+
include Phobos::Instrumentation
|
5
|
+
|
6
|
+
attr_reader :metadata
|
7
|
+
|
8
|
+
def initialize(listener:, message:, listener_metadata:)
|
5
9
|
@listener = listener
|
6
10
|
@message = message
|
7
|
-
@
|
8
|
-
@
|
11
|
+
@listener_metadata = listener_metadata
|
12
|
+
@metadata = listener_metadata.merge(
|
13
|
+
key: message.key,
|
14
|
+
partition: message.partition,
|
15
|
+
offset: message.offset,
|
16
|
+
retry_count: 0
|
17
|
+
)
|
9
18
|
end
|
10
19
|
|
11
20
|
def execute
|
21
|
+
backoff = @listener.create_exponential_backoff
|
12
22
|
payload = force_encoding(@message.value)
|
13
|
-
|
14
|
-
|
15
|
-
|
23
|
+
|
24
|
+
begin
|
25
|
+
process_message(payload)
|
26
|
+
rescue => e
|
27
|
+
retry_count = @metadata[:retry_count]
|
28
|
+
interval = backoff.interval_at(retry_count).round(2)
|
29
|
+
|
30
|
+
error = {
|
31
|
+
waiting_time: interval,
|
32
|
+
exception_class: e.class.name,
|
33
|
+
exception_message: e.message,
|
34
|
+
backtrace: e.backtrace
|
35
|
+
}
|
36
|
+
|
37
|
+
instrument('listener.retry_handler_error', error.merge(@metadata)) do
|
38
|
+
Phobos.logger.error do
|
39
|
+
{ message: "error processing message, waiting #{interval}s" }.merge(error).merge(@metadata)
|
40
|
+
end
|
41
|
+
|
42
|
+
sleep interval
|
43
|
+
end
|
44
|
+
|
45
|
+
raise Phobos::AbortError if @listener.should_stop?
|
46
|
+
|
47
|
+
@metadata.merge!(retry_count: retry_count + 1)
|
48
|
+
retry
|
16
49
|
end
|
17
50
|
end
|
18
51
|
|
19
52
|
private
|
20
53
|
|
21
54
|
def force_encoding(value)
|
22
|
-
@encoding ? value.force_encoding(@encoding) : value
|
55
|
+
@listener.encoding ? value.force_encoding(@listener.encoding) : value
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_message(payload)
|
59
|
+
instrument('listener.process_message', @metadata) do |metadata|
|
60
|
+
time_elapsed = measure do
|
61
|
+
handler = @listener.handler_class.new
|
62
|
+
preprocessed_payload = handler.before_consume(payload)
|
63
|
+
|
64
|
+
@listener.handler_class.around_consume(preprocessed_payload, @metadata) do
|
65
|
+
handler.consume(preprocessed_payload, @metadata)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
metadata.merge!(time_elapsed: time_elapsed)
|
70
|
+
end
|
23
71
|
end
|
24
72
|
end
|
25
73
|
end
|
data/lib/phobos/cli.rb
CHANGED
@@ -49,6 +49,13 @@ module Phobos
|
|
49
49
|
aliases: ['-b'],
|
50
50
|
banner: 'File path to load application specific code',
|
51
51
|
default: 'phobos_boot.rb'
|
52
|
+
option :listeners,
|
53
|
+
aliases: ['-l'],
|
54
|
+
banner: 'Separate listeners config file (optional)'
|
55
|
+
option :skip_config,
|
56
|
+
default: false,
|
57
|
+
type: :boolean,
|
58
|
+
banner: 'Skip config file'
|
52
59
|
def start
|
53
60
|
Phobos::CLI::Start.new(options).execute
|
54
61
|
end
|
data/lib/phobos/cli/start.rb
CHANGED
@@ -4,14 +4,28 @@ module Phobos
|
|
4
4
|
module CLI
|
5
5
|
class Start
|
6
6
|
def initialize(options)
|
7
|
-
|
7
|
+
unless options[:skip_config]
|
8
|
+
@config_file = File.expand_path(options[:config])
|
9
|
+
end
|
8
10
|
@boot_file = File.expand_path(options[:boot])
|
11
|
+
|
12
|
+
if options[:listeners]
|
13
|
+
@listeners_file = File.expand_path(options[:listeners])
|
14
|
+
end
|
9
15
|
end
|
10
16
|
|
11
17
|
def execute
|
12
|
-
|
13
|
-
|
18
|
+
if config_file
|
19
|
+
validate_config_file!
|
20
|
+
Phobos.configure(config_file)
|
21
|
+
end
|
22
|
+
|
14
23
|
load_boot_file
|
24
|
+
|
25
|
+
if listeners_file
|
26
|
+
Phobos.add_listeners(listeners_file)
|
27
|
+
end
|
28
|
+
|
15
29
|
validate_listeners!
|
16
30
|
|
17
31
|
Phobos::CLI::Runner.new.run!
|
@@ -19,7 +33,7 @@ module Phobos
|
|
19
33
|
|
20
34
|
private
|
21
35
|
|
22
|
-
attr_reader :config_file, :boot_file
|
36
|
+
attr_reader :config_file, :boot_file, :listeners_file
|
23
37
|
|
24
38
|
def validate_config_file!
|
25
39
|
unless File.exist?(config_file)
|
@@ -29,13 +43,27 @@ module Phobos
|
|
29
43
|
end
|
30
44
|
|
31
45
|
def validate_listeners!
|
32
|
-
Phobos.config.listeners.
|
46
|
+
Phobos.config.listeners.each do |listener|
|
47
|
+
handler_class = listener.handler
|
48
|
+
|
33
49
|
begin
|
34
50
|
handler_class.constantize
|
35
51
|
rescue NameError
|
36
52
|
Phobos::CLI.logger.error { Hash(message: "Handler '#{handler_class}' not defined") }
|
37
53
|
exit(1)
|
38
54
|
end
|
55
|
+
|
56
|
+
delivery = listener.delivery
|
57
|
+
if delivery.nil?
|
58
|
+
Phobos::CLI.logger.warn do
|
59
|
+
Hash(message: "Delivery option should be specified, defaulting to 'batch' - specify this option to silence this message")
|
60
|
+
end
|
61
|
+
elsif !Listener::DELIVERY_OPTS.include?(delivery)
|
62
|
+
Phobos::CLI.logger.error do
|
63
|
+
Hash(message: "Invalid delivery option '#{delivery}'. Please specify one of: #{Listener::DELIVERY_OPTS.join(', ')}")
|
64
|
+
end
|
65
|
+
exit(1)
|
66
|
+
end
|
39
67
|
end
|
40
68
|
end
|
41
69
|
|
data/lib/phobos/executor.rb
CHANGED
data/lib/phobos/listener.rb
CHANGED
@@ -4,6 +4,7 @@ module Phobos
|
|
4
4
|
|
5
5
|
KAFKA_CONSUMER_OPTS = %i(session_timeout offset_commit_interval offset_commit_threshold heartbeat_interval).freeze
|
6
6
|
DEFAULT_MAX_BYTES_PER_PARTITION = 524288 # 512 KB
|
7
|
+
DELIVERY_OPTS = %w[batch message].freeze
|
7
8
|
|
8
9
|
attr_reader :group_id, :topic, :id
|
9
10
|
attr_reader :handler_class, :encoding
|
@@ -11,12 +12,14 @@ module Phobos
|
|
11
12
|
def initialize(handler:, group_id:, topic:, min_bytes: nil,
|
12
13
|
max_wait_time: nil, force_encoding: nil,
|
13
14
|
start_from_beginning: true, backoff: nil,
|
15
|
+
delivery: 'batch',
|
14
16
|
max_bytes_per_partition: DEFAULT_MAX_BYTES_PER_PARTITION)
|
15
17
|
@id = SecureRandom.hex[0...6]
|
16
18
|
@handler_class = handler
|
17
19
|
@group_id = group_id
|
18
20
|
@topic = topic
|
19
21
|
@backoff = backoff
|
22
|
+
@delivery = delivery.to_s
|
20
23
|
@subscribe_opts = {
|
21
24
|
start_from_beginning: start_from_beginning,
|
22
25
|
max_bytes_per_partition: max_bytes_per_partition
|
@@ -42,29 +45,7 @@ module Phobos
|
|
42
45
|
end
|
43
46
|
|
44
47
|
begin
|
45
|
-
@
|
46
|
-
batch_metadata = {
|
47
|
-
batch_size: batch.messages.count,
|
48
|
-
partition: batch.partition,
|
49
|
-
offset_lag: batch.offset_lag,
|
50
|
-
# the offset of the most recent message in the partition
|
51
|
-
highwater_mark_offset: batch.highwater_mark_offset
|
52
|
-
}.merge(listener_metadata)
|
53
|
-
|
54
|
-
instrument('listener.process_batch', batch_metadata) do |batch_metadata|
|
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
|
62
|
-
batch_metadata.merge!(time_elapsed: time_elapsed)
|
63
|
-
Phobos.logger.info { Hash(message: 'Committed offset').merge(batch_metadata) }
|
64
|
-
end
|
65
|
-
|
66
|
-
return if should_stop?
|
67
|
-
end
|
48
|
+
@delivery == 'batch' ? consume_each_batch : consume_each_message
|
68
49
|
|
69
50
|
# Abort is an exception to prevent the consumer from committing the offset.
|
70
51
|
# Since "listener" had a message being retried while "stop" was called
|
@@ -95,6 +76,34 @@ module Phobos
|
|
95
76
|
end
|
96
77
|
end
|
97
78
|
|
79
|
+
def consume_each_batch
|
80
|
+
@consumer.each_batch(@consumer_opts) do |batch|
|
81
|
+
batch_processor = Phobos::Actions::ProcessBatch.new(
|
82
|
+
listener: self,
|
83
|
+
batch: batch,
|
84
|
+
listener_metadata: listener_metadata
|
85
|
+
)
|
86
|
+
|
87
|
+
batch_processor.execute
|
88
|
+
Phobos.logger.info { Hash(message: 'Committed offset').merge(batch_processor.metadata) }
|
89
|
+
return if should_stop?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def consume_each_message
|
94
|
+
@consumer.each_message(@consumer_opts) do |message|
|
95
|
+
message_processor = Phobos::Actions::ProcessMessage.new(
|
96
|
+
listener: self,
|
97
|
+
message: message,
|
98
|
+
listener_metadata: listener_metadata
|
99
|
+
)
|
100
|
+
|
101
|
+
message_processor.execute
|
102
|
+
Phobos.logger.info { Hash(message: 'Committed offset').merge(message_processor.metadata) }
|
103
|
+
return if should_stop?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
98
107
|
def stop
|
99
108
|
return if should_stop?
|
100
109
|
instrument('listener.stopping', listener_metadata) do
|
data/lib/phobos/version.rb
CHANGED
data/phobos.gemspec
CHANGED
@@ -44,16 +44,14 @@ Gem::Specification.new do |spec|
|
|
44
44
|
spec.require_paths = ['lib']
|
45
45
|
spec.required_ruby_version = '>= 2.3'
|
46
46
|
|
47
|
-
spec.add_development_dependency 'bundler'
|
47
|
+
spec.add_development_dependency 'bundler'
|
48
48
|
spec.add_development_dependency 'rake', '~> 10.0'
|
49
|
-
spec.add_development_dependency 'rspec', '~> 3.
|
50
|
-
spec.add_development_dependency 'pry-byebug'
|
51
|
-
spec.add_development_dependency '
|
52
|
-
spec.add_development_dependency 'simplecov', '~> 0.14.1'
|
53
|
-
spec.add_development_dependency 'coveralls', '~> 0.8.21'
|
49
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
50
|
+
spec.add_development_dependency 'pry-byebug'
|
51
|
+
spec.add_development_dependency 'simplecov'
|
54
52
|
spec.add_development_dependency 'timecop'
|
55
53
|
|
56
|
-
spec.add_dependency 'ruby-kafka', '>= 0.3.14'
|
54
|
+
spec.add_dependency 'ruby-kafka', '>= 0.3.14', '< 0.5.0'
|
57
55
|
spec.add_dependency 'concurrent-ruby', '>= 1.0.2'
|
58
56
|
spec.add_dependency 'concurrent-ruby-ext', '>= 1.0.2'
|
59
57
|
spec.add_dependency 'activesupport', '>= 4.0.0'
|
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.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Túlio Ornelas
|
@@ -14,22 +14,22 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date: 2017-
|
17
|
+
date: 2017-11-16 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: bundler
|
21
21
|
requirement: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
|
-
- - "
|
23
|
+
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: '
|
25
|
+
version: '0'
|
26
26
|
type: :development
|
27
27
|
prerelease: false
|
28
28
|
version_requirements: !ruby/object:Gem::Requirement
|
29
29
|
requirements:
|
30
|
-
- - "
|
30
|
+
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '0'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: rake
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,70 +50,42 @@ dependencies:
|
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: '3.
|
53
|
+
version: '3.0'
|
54
54
|
type: :development
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version: '3.
|
60
|
+
version: '3.0'
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
62
|
name: pry-byebug
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
|
-
- - "
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: 3.4.3
|
68
|
-
type: :development
|
69
|
-
prerelease: false
|
70
|
-
version_requirements: !ruby/object:Gem::Requirement
|
71
|
-
requirements:
|
72
|
-
- - "~>"
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
version: 3.4.3
|
75
|
-
- !ruby/object:Gem::Dependency
|
76
|
-
name: rspec_junit_formatter
|
77
|
-
requirement: !ruby/object:Gem::Requirement
|
78
|
-
requirements:
|
79
|
-
- - '='
|
65
|
+
- - ">="
|
80
66
|
- !ruby/object:Gem::Version
|
81
|
-
version: 0
|
67
|
+
version: '0'
|
82
68
|
type: :development
|
83
69
|
prerelease: false
|
84
70
|
version_requirements: !ruby/object:Gem::Requirement
|
85
71
|
requirements:
|
86
|
-
- -
|
72
|
+
- - ">="
|
87
73
|
- !ruby/object:Gem::Version
|
88
|
-
version: 0
|
74
|
+
version: '0'
|
89
75
|
- !ruby/object:Gem::Dependency
|
90
76
|
name: simplecov
|
91
77
|
requirement: !ruby/object:Gem::Requirement
|
92
78
|
requirements:
|
93
|
-
- - "
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
version: 0.14.1
|
96
|
-
type: :development
|
97
|
-
prerelease: false
|
98
|
-
version_requirements: !ruby/object:Gem::Requirement
|
99
|
-
requirements:
|
100
|
-
- - "~>"
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
version: 0.14.1
|
103
|
-
- !ruby/object:Gem::Dependency
|
104
|
-
name: coveralls
|
105
|
-
requirement: !ruby/object:Gem::Requirement
|
106
|
-
requirements:
|
107
|
-
- - "~>"
|
79
|
+
- - ">="
|
108
80
|
- !ruby/object:Gem::Version
|
109
|
-
version: 0
|
81
|
+
version: '0'
|
110
82
|
type: :development
|
111
83
|
prerelease: false
|
112
84
|
version_requirements: !ruby/object:Gem::Requirement
|
113
85
|
requirements:
|
114
|
-
- - "
|
86
|
+
- - ">="
|
115
87
|
- !ruby/object:Gem::Version
|
116
|
-
version: 0
|
88
|
+
version: '0'
|
117
89
|
- !ruby/object:Gem::Dependency
|
118
90
|
name: timecop
|
119
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -135,6 +107,9 @@ dependencies:
|
|
135
107
|
- - ">="
|
136
108
|
- !ruby/object:Gem::Version
|
137
109
|
version: 0.3.14
|
110
|
+
- - "<"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 0.5.0
|
138
113
|
type: :runtime
|
139
114
|
prerelease: false
|
140
115
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -142,6 +117,9 @@ dependencies:
|
|
142
117
|
- - ">="
|
143
118
|
- !ruby/object:Gem::Version
|
144
119
|
version: 0.3.14
|
120
|
+
- - "<"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 0.5.0
|
145
123
|
- !ruby/object:Gem::Dependency
|
146
124
|
name: concurrent-ruby
|
147
125
|
requirement: !ruby/object:Gem::Requirement
|
@@ -242,9 +220,12 @@ executables:
|
|
242
220
|
extensions: []
|
243
221
|
extra_rdoc_files: []
|
244
222
|
files:
|
223
|
+
- ".dockerignore"
|
224
|
+
- ".env"
|
245
225
|
- ".gitignore"
|
246
226
|
- ".rspec"
|
247
227
|
- ".ruby-version"
|
228
|
+
- ".travis.yml"
|
248
229
|
- CHANGELOG.md
|
249
230
|
- Dockerfile
|
250
231
|
- Gemfile
|
@@ -254,7 +235,6 @@ files:
|
|
254
235
|
- bin/console
|
255
236
|
- bin/phobos
|
256
237
|
- bin/setup
|
257
|
-
- circle.yml
|
258
238
|
- config/phobos.yml.example
|
259
239
|
- docker-compose.yml
|
260
240
|
- examples/handler_saving_events_database.rb
|
data/circle.yml
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
machine:
|
2
|
-
pre:
|
3
|
-
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
|
4
|
-
services:
|
5
|
-
- docker
|
6
|
-
environment:
|
7
|
-
LOG_LEVEL: DEBUG
|
8
|
-
CI: true
|
9
|
-
DEFAULT_TIMEOUT: 20
|
10
|
-
ruby:
|
11
|
-
version: 2.4.1
|
12
|
-
|
13
|
-
dependencies:
|
14
|
-
pre:
|
15
|
-
- docker -v
|
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
|
-
- bundle install
|
20
|
-
|
21
|
-
test:
|
22
|
-
override:
|
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
|
-
- bundle exec rspec -r rspec_junit_formatter --format RspecJunitFormatter -o $CIRCLE_TEST_REPORTS/rspec/unit.xml
|
26
|
-
post:
|
27
|
-
- cp log/*.log $CIRCLE_ARTIFACTS/ || true
|