phobos 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![Phobos](https://raw.githubusercontent.com/klarna/phobos/master/logo.png)
|
2
2
|
|
3
|
-
[![
|
4
|
-
[![
|
3
|
+
[![Build Status](https://travis-ci.org/klarna/phobos.svg?branch=master)](https://travis-ci.org/klarna/phobos)
|
4
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/2d00845fc6e7e83df6e7/maintainability)](https://codeclimate.com/github/klarna/phobos/maintainability)
|
5
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/2d00845fc6e7e83df6e7/test_coverage)](https://codeclimate.com/github/klarna/phobos/test_coverage)
|
6
|
+
[![Chat with us on Discord](https://discordapp.com/api/guilds/379938130326847488/widget.png)](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
|