phobos_temp_fork 0.0.1
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 +7 -0
- data/.dockerignore +13 -0
- data/.env +1 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +26 -0
- data/.rubocop_common.yml +29 -0
- data/.rubocop_todo.yml +7 -0
- data/.rubosync.yml +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +37 -0
- data/CHANGELOG.md +170 -0
- data/Dockerfile +14 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +176 -0
- data/README.md +699 -0
- data/Rakefile +8 -0
- data/bin/console +19 -0
- data/bin/phobos +10 -0
- data/bin/setup +8 -0
- data/config/phobos.yml.example +137 -0
- data/docker-compose.yml +28 -0
- data/examples/handler_saving_events_database.rb +51 -0
- data/examples/handler_using_async_producer.rb +17 -0
- data/examples/publishing_messages_without_consumer.rb +82 -0
- data/lib/phobos/actions/process_batch.rb +35 -0
- data/lib/phobos/actions/process_batch_inline.rb +61 -0
- data/lib/phobos/actions/process_message.rb +49 -0
- data/lib/phobos/batch_handler.rb +23 -0
- data/lib/phobos/batch_message.rb +21 -0
- data/lib/phobos/cli.rb +69 -0
- data/lib/phobos/cli/runner.rb +48 -0
- data/lib/phobos/cli/start.rb +71 -0
- data/lib/phobos/constants.rb +33 -0
- data/lib/phobos/deep_struct.rb +39 -0
- data/lib/phobos/echo_handler.rb +11 -0
- data/lib/phobos/errors.rb +6 -0
- data/lib/phobos/executor.rb +103 -0
- data/lib/phobos/handler.rb +23 -0
- data/lib/phobos/instrumentation.rb +25 -0
- data/lib/phobos/listener.rb +192 -0
- data/lib/phobos/log.rb +23 -0
- data/lib/phobos/processor.rb +67 -0
- data/lib/phobos/producer.rb +171 -0
- data/lib/phobos/test.rb +3 -0
- data/lib/phobos/test/helper.rb +29 -0
- data/lib/phobos/version.rb +5 -0
- data/lib/phobos_temp_fork.rb +175 -0
- data/logo.png +0 -0
- data/phobos.gemspec +69 -0
- data/phobos_boot.rb +31 -0
- data/utils/create-topic.sh +13 -0
- metadata +308 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'irb'
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'phobos'
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
12
|
+
# require 'pry'
|
13
|
+
# Pry.start
|
14
|
+
|
15
|
+
config_path = ENV['CONFIG_PATH'] ||
|
16
|
+
(File.exist?('config/phobos.yml') ? 'config/phobos.yml' : 'config/phobos.yml.example')
|
17
|
+
Phobos.configure(config_path)
|
18
|
+
|
19
|
+
IRB.start
|
data/bin/phobos
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
logger:
|
2
|
+
# Optional log file, set to false or remove to disable it
|
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
|
7
|
+
level: info
|
8
|
+
# Comment the block to disable ruby-kafka logs
|
9
|
+
ruby_kafka:
|
10
|
+
level: error
|
11
|
+
|
12
|
+
kafka:
|
13
|
+
# identifier for this application
|
14
|
+
client_id: phobos
|
15
|
+
# timeout setting for connecting to brokers
|
16
|
+
connect_timeout:
|
17
|
+
# timeout setting for socket connections
|
18
|
+
socket_timeout:
|
19
|
+
# PEM encoded CA cert to use with an SSL connection (string)
|
20
|
+
ssl_ca_cert:
|
21
|
+
# PEM encoded client cert to use with an SSL connection (string)
|
22
|
+
# Must be used in combination with ssl_client_cert_key
|
23
|
+
ssl_client_cert:
|
24
|
+
# PEM encoded client cert key to use with an SSL connection (string)
|
25
|
+
# Must be used in combination with ssl_client_cert
|
26
|
+
ssl_client_cert_key:
|
27
|
+
# list of brokers used to initialize the client ("port:protocol")
|
28
|
+
seed_brokers:
|
29
|
+
- localhost:9092
|
30
|
+
|
31
|
+
producer:
|
32
|
+
# number of seconds a broker can wait for replicas to acknowledge
|
33
|
+
# a write before responding with a timeout
|
34
|
+
ack_timeout: 5
|
35
|
+
# number of replicas that must acknowledge a write, or `:all`
|
36
|
+
# if all in-sync replicas must acknowledge
|
37
|
+
required_acks: :all
|
38
|
+
# number of retries that should be attempted before giving up sending
|
39
|
+
# messages to the cluster. Does not include the original attempt
|
40
|
+
max_retries: 2
|
41
|
+
# number of seconds to wait between retries
|
42
|
+
retry_backoff: 1
|
43
|
+
# number of messages allowed in the buffer before new writes will
|
44
|
+
# raise {BufferOverflow} exceptions
|
45
|
+
max_buffer_size: 1000
|
46
|
+
# maximum size of the buffer in bytes. Attempting to produce messages
|
47
|
+
# when the buffer reaches this size will result in {BufferOverflow} being raised
|
48
|
+
max_buffer_bytesize: 10000000
|
49
|
+
# name of the compression codec to use, or nil if no compression should be performed.
|
50
|
+
# Valid codecs: `:snappy` and `:gzip`
|
51
|
+
compression_codec:
|
52
|
+
# number of messages that needs to be in a message set before it should be compressed.
|
53
|
+
# Note that message sets are per-partition rather than per-topic or per-producer
|
54
|
+
compression_threshold: 1
|
55
|
+
# maximum number of messages allowed in the queue. Only used for async_producer
|
56
|
+
max_queue_size: 1000
|
57
|
+
# if greater than zero, the number of buffered messages that will automatically
|
58
|
+
# trigger a delivery. Only used for async_producer
|
59
|
+
delivery_threshold: 0
|
60
|
+
# if greater than zero, the number of seconds between automatic message
|
61
|
+
# deliveries. Only used for async_producer
|
62
|
+
delivery_interval: 0
|
63
|
+
# Set this to true to keep the producer connection between publish calls.
|
64
|
+
# This can speed up subsequent messages by around 30%, but it does mean
|
65
|
+
# that you need to manually call sync_producer_shutdown before exiting,
|
66
|
+
# similar to async_producer_shutdown.
|
67
|
+
persistent_connections: false
|
68
|
+
# kafka here supports the same parameters as the top-level, allowing custom connection
|
69
|
+
# configuration details for producers
|
70
|
+
kafka:
|
71
|
+
connect_timeout: 120
|
72
|
+
|
73
|
+
consumer:
|
74
|
+
# number of seconds after which, if a client hasn't contacted the Kafka cluster,
|
75
|
+
# it will be kicked out of the group
|
76
|
+
session_timeout: 30
|
77
|
+
# interval between offset commits, in seconds
|
78
|
+
offset_commit_interval: 10
|
79
|
+
# number of messages that can be processed before their offsets are committed.
|
80
|
+
# If zero, offset commits are not triggered by message processing
|
81
|
+
offset_commit_threshold: 0
|
82
|
+
# the time period that committed offsets will be retained, in seconds. Defaults to the broker setting.
|
83
|
+
offset_retention_time:
|
84
|
+
# interval between heartbeats; must be less than the session window
|
85
|
+
heartbeat_interval: 10
|
86
|
+
# kafka here supports the same parameters as the top-level, allowing custom connection
|
87
|
+
# configuration details for consumers
|
88
|
+
kafka:
|
89
|
+
connect_timeout: 130
|
90
|
+
|
91
|
+
backoff:
|
92
|
+
min_ms: 1000
|
93
|
+
max_ms: 60000
|
94
|
+
|
95
|
+
listeners:
|
96
|
+
- handler: Phobos::EchoHandler
|
97
|
+
topic: test
|
98
|
+
# id of the group that the consumer should join
|
99
|
+
group_id: test-1
|
100
|
+
# Number of threads created for this listener, each thread will behave as an independent consumer.
|
101
|
+
# They don't share any state
|
102
|
+
max_concurrency: 1
|
103
|
+
# Once the consumer group has checkpointed its progress in the topic's partitions,
|
104
|
+
# the consumers will always start from the checkpointed offsets, regardless of config
|
105
|
+
# As such, this setting only applies when the consumer initially starts consuming from a topic
|
106
|
+
start_from_beginning: true
|
107
|
+
# maximum amount of data fetched from a single partition at a time
|
108
|
+
max_bytes_per_partition: 524288 # 512 KB
|
109
|
+
# Minimum number of bytes to read before returning messages from the server; if `max_wait_time` is reached, this is ignored.
|
110
|
+
min_bytes: 1
|
111
|
+
# Maximum duration of time to wait before returning messages from the server, in seconds
|
112
|
+
max_wait_time: 5
|
113
|
+
# Apply this encoding to the message payload, if blank it uses the original encoding. This property accepts values
|
114
|
+
# defined by the ruby Encoding class (https://ruby-doc.org/core-2.3.0/Encoding.html). Ex: UTF_8, ASCII_8BIT, etc
|
115
|
+
force_encoding:
|
116
|
+
# Specify the delivery method for a listener.
|
117
|
+
# Possible values: [`message`, `batch` (default)]
|
118
|
+
# - `message` will yield individual messages from Ruby Kafka using `each_message` and will commit/heartbeat at every consumed message.
|
119
|
+
# This is overall a bit slower than using batch, but easier to configure.
|
120
|
+
# - `batch` will yield batches from Ruby Kafka using `each_batch`, and commit at every consumed batch. It will
|
121
|
+
# still heartbeat after every message if necessary (using the heartbeat_interval, below).
|
122
|
+
# - `inline_batch` also uses `each_batch`, but will pass the entire batch to your handler instead
|
123
|
+
# of one message at a time. To use this method, you should include Phobos::BatchHandler
|
124
|
+
# instead of Phobos::Handler so that you can make use of the `consume_batch` etc. methods.
|
125
|
+
# Note: Ultimately commit/heartbeart will depend on the offset commit options and the heartbeat interval.
|
126
|
+
delivery: batch
|
127
|
+
# Use this if custom backoff is required for a listener
|
128
|
+
backoff:
|
129
|
+
min_ms: 500
|
130
|
+
max_ms: 10000
|
131
|
+
# session_timeout, offset_commit_interval, offset_commit_threshold, offset_retention_time, and heartbeat_interval
|
132
|
+
# can be customized per listener if desired
|
133
|
+
session_timeout: 30
|
134
|
+
offset_commit_interval: 15
|
135
|
+
offset_commit_threshold: 5
|
136
|
+
offset_retention_time: 172800
|
137
|
+
heartbeat_interval: 20
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
version: '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
|
+
|
15
|
+
zookeeper:
|
16
|
+
image: wurstmeister/zookeeper:latest
|
17
|
+
ports:
|
18
|
+
- 2181:2181
|
19
|
+
|
20
|
+
kafka:
|
21
|
+
depends_on:
|
22
|
+
- zookeeper
|
23
|
+
image: wurstmeister/kafka:0.11.0.1
|
24
|
+
ports:
|
25
|
+
- 9092:9092
|
26
|
+
environment:
|
27
|
+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
28
|
+
KAFKA_BROKER_ID: 0
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# This example assumes that you want to save all events in your database for
|
5
|
+
# recovery purposes. The consumer will process the message and perform other
|
6
|
+
# operations, this implementation assumes a generic way to save the events.
|
7
|
+
#
|
8
|
+
# Setup your database connection using `phobos_boot.rb`. Remember to Setup
|
9
|
+
# a hook to disconnect, e.g: `at_exit { Database.disconnect! }`
|
10
|
+
#
|
11
|
+
class HandlerSavingEventsDatabase
|
12
|
+
include Phobos::Handler
|
13
|
+
include Phobos::Producer
|
14
|
+
|
15
|
+
def self.around_consume(payload, _metadata)
|
16
|
+
#
|
17
|
+
# Let's assume `::from_message` will initialize our object with `payload`
|
18
|
+
#
|
19
|
+
event = Model::Event.from_message(payload)
|
20
|
+
|
21
|
+
#
|
22
|
+
# If event already exists in the database, skip this message
|
23
|
+
#
|
24
|
+
return if event.exists?
|
25
|
+
|
26
|
+
Model::Event.transaction do
|
27
|
+
#
|
28
|
+
# Executes `#consume` method
|
29
|
+
#
|
30
|
+
new_values = yield
|
31
|
+
|
32
|
+
#
|
33
|
+
# `#consume` method can return additional data (up to your code)
|
34
|
+
#
|
35
|
+
event.update_with_new_attributes(new_values)
|
36
|
+
|
37
|
+
#
|
38
|
+
# Let's assume the event is just initialized and now is the time to save it
|
39
|
+
#
|
40
|
+
event.save!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def consume(payload, _metadata)
|
45
|
+
#
|
46
|
+
# Process the event, it might index it to elasticsearch or notify other
|
47
|
+
# system, you should process your message inside this method.
|
48
|
+
#
|
49
|
+
{ new_vale: payload.length % 3 }
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# This example assumes you want to process the event and publish another
|
5
|
+
# one to kafka. A new event is always published thus we want to use the async producer
|
6
|
+
# to better use our resources and to speed up the process
|
7
|
+
#
|
8
|
+
class HandlerUsingAsyncProducer
|
9
|
+
include Phobos::Handler
|
10
|
+
include Phobos::Producer
|
11
|
+
|
12
|
+
PUBLISH_TO 'another-topic'
|
13
|
+
|
14
|
+
def consume(payload, _metadata)
|
15
|
+
producer.async_publish(PUBLISH_TO, "#{payload}-#{rand}")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# This example assumes you want to create a threaded kafka generator which
|
5
|
+
# publish a stream of kafka messages without consuming them. It also shows
|
6
|
+
# what happens when you produce more messages than the producer can handle.
|
7
|
+
#
|
8
|
+
require 'bundler/setup'
|
9
|
+
require 'json'
|
10
|
+
require 'phobos'
|
11
|
+
|
12
|
+
TOPIC = 'test-partitions'
|
13
|
+
|
14
|
+
Phobos.configure('config/phobos.yml')
|
15
|
+
|
16
|
+
class MyProducer
|
17
|
+
include Phobos::Producer
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Trapping signals to properly stop this generator
|
22
|
+
#
|
23
|
+
@stop = false
|
24
|
+
[:INT, :TERM, :QUIT].each do |signal|
|
25
|
+
Signal.trap(signal) do
|
26
|
+
puts 'Stopping'
|
27
|
+
@stop = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Thread.new do
|
32
|
+
begin
|
33
|
+
total = 1
|
34
|
+
|
35
|
+
loop do
|
36
|
+
break if @stop
|
37
|
+
|
38
|
+
key = SecureRandom.uuid
|
39
|
+
payload = Time.now.utc.to_json
|
40
|
+
|
41
|
+
begin
|
42
|
+
# Producer will use phobos configuration to create a kafka client and
|
43
|
+
# a producer and it will bind both to the current thread, so it's safe
|
44
|
+
# to call class methods here
|
45
|
+
#
|
46
|
+
MyProducer
|
47
|
+
.producer
|
48
|
+
.async_publish(topic: TOPIC, payload: payload, key: key)
|
49
|
+
|
50
|
+
puts "produced #{key}, total: #{total}"
|
51
|
+
|
52
|
+
# Since this is very simplistic code, we are going to generate more messages than
|
53
|
+
# the producer can write to Kafka. Eventually we'll get some buffer overflows
|
54
|
+
#
|
55
|
+
rescue Kafka::BufferOverflow => e
|
56
|
+
puts '| waiting'
|
57
|
+
sleep(1)
|
58
|
+
retry
|
59
|
+
end
|
60
|
+
|
61
|
+
total += 1
|
62
|
+
end
|
63
|
+
ensure
|
64
|
+
#
|
65
|
+
# Before we stop we must shutdown the async producer to ensure that all messages
|
66
|
+
# are delivered
|
67
|
+
#
|
68
|
+
MyProducer
|
69
|
+
.producer
|
70
|
+
.async_producer_shutdown
|
71
|
+
|
72
|
+
#
|
73
|
+
# Since no client was configured (we can do this with
|
74
|
+
# `MyProducer.producer.configure_kafka_client`)
|
75
|
+
# we must get the auto generated one and close it properly
|
76
|
+
#
|
77
|
+
MyProducer
|
78
|
+
.producer
|
79
|
+
.kafka_client
|
80
|
+
.close
|
81
|
+
end
|
82
|
+
end.join
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phobos
|
4
|
+
module Actions
|
5
|
+
class ProcessBatch
|
6
|
+
include Phobos::Instrumentation
|
7
|
+
|
8
|
+
attr_reader :metadata
|
9
|
+
|
10
|
+
def initialize(listener:, batch:, listener_metadata:)
|
11
|
+
@listener = listener
|
12
|
+
@batch = batch
|
13
|
+
@listener_metadata = listener_metadata
|
14
|
+
@metadata = listener_metadata.merge(
|
15
|
+
batch_size: batch.messages.count,
|
16
|
+
partition: batch.partition,
|
17
|
+
offset_lag: batch.offset_lag
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute
|
22
|
+
instrument('listener.process_batch', @metadata) do |_metadata|
|
23
|
+
@batch.messages.each do |message|
|
24
|
+
Phobos::Actions::ProcessMessage.new(
|
25
|
+
listener: @listener,
|
26
|
+
message: message,
|
27
|
+
listener_metadata: @listener_metadata
|
28
|
+
).execute
|
29
|
+
@listener.consumer.trigger_heartbeat
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'phobos/batch_message'
|
4
|
+
require 'phobos/processor'
|
5
|
+
|
6
|
+
module Phobos
|
7
|
+
module Actions
|
8
|
+
class ProcessBatchInline
|
9
|
+
include Phobos::Processor
|
10
|
+
|
11
|
+
attr_reader :metadata
|
12
|
+
|
13
|
+
def initialize(listener:, batch:, metadata:)
|
14
|
+
@listener = listener
|
15
|
+
@batch = batch
|
16
|
+
@listener = listener
|
17
|
+
@batch = batch
|
18
|
+
@metadata = metadata.merge(
|
19
|
+
batch_size: batch.messages.count,
|
20
|
+
partition: batch.partition,
|
21
|
+
offset_lag: batch.offset_lag,
|
22
|
+
retry_count: 0
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute
|
27
|
+
batch = @batch.messages.map { |message| instantiate_batch_message(message) }
|
28
|
+
|
29
|
+
begin
|
30
|
+
process_batch(batch)
|
31
|
+
rescue StandardError => e
|
32
|
+
handle_error(e, 'listener.retry_handler_error_batch',
|
33
|
+
"error processing inline batch, waiting #{backoff_interval}s")
|
34
|
+
retry
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def instantiate_batch_message(message)
|
41
|
+
Phobos::BatchMessage.new(
|
42
|
+
key: message.key,
|
43
|
+
partition: message.partition,
|
44
|
+
offset: message.offset,
|
45
|
+
payload: force_encoding(message.value),
|
46
|
+
headers: message.headers
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def process_batch(batch)
|
51
|
+
instrument('listener.process_batch_inline', @metadata) do |_metadata|
|
52
|
+
handler = @listener.handler_class.new
|
53
|
+
|
54
|
+
handler.around_consume_batch(batch, @metadata) do |around_batch, around_metadata|
|
55
|
+
handler.consume_batch(around_batch, around_metadata)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|