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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +13 -0
  3. data/.env +1 -0
  4. data/.gitignore +16 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +26 -0
  7. data/.rubocop_common.yml +29 -0
  8. data/.rubocop_todo.yml +7 -0
  9. data/.rubosync.yml +2 -0
  10. data/.ruby-version +1 -0
  11. data/.travis.yml +37 -0
  12. data/CHANGELOG.md +170 -0
  13. data/Dockerfile +14 -0
  14. data/Gemfile +8 -0
  15. data/LICENSE.txt +176 -0
  16. data/README.md +699 -0
  17. data/Rakefile +8 -0
  18. data/bin/console +19 -0
  19. data/bin/phobos +10 -0
  20. data/bin/setup +8 -0
  21. data/config/phobos.yml.example +137 -0
  22. data/docker-compose.yml +28 -0
  23. data/examples/handler_saving_events_database.rb +51 -0
  24. data/examples/handler_using_async_producer.rb +17 -0
  25. data/examples/publishing_messages_without_consumer.rb +82 -0
  26. data/lib/phobos/actions/process_batch.rb +35 -0
  27. data/lib/phobos/actions/process_batch_inline.rb +61 -0
  28. data/lib/phobos/actions/process_message.rb +49 -0
  29. data/lib/phobos/batch_handler.rb +23 -0
  30. data/lib/phobos/batch_message.rb +21 -0
  31. data/lib/phobos/cli.rb +69 -0
  32. data/lib/phobos/cli/runner.rb +48 -0
  33. data/lib/phobos/cli/start.rb +71 -0
  34. data/lib/phobos/constants.rb +33 -0
  35. data/lib/phobos/deep_struct.rb +39 -0
  36. data/lib/phobos/echo_handler.rb +11 -0
  37. data/lib/phobos/errors.rb +6 -0
  38. data/lib/phobos/executor.rb +103 -0
  39. data/lib/phobos/handler.rb +23 -0
  40. data/lib/phobos/instrumentation.rb +25 -0
  41. data/lib/phobos/listener.rb +192 -0
  42. data/lib/phobos/log.rb +23 -0
  43. data/lib/phobos/processor.rb +67 -0
  44. data/lib/phobos/producer.rb +171 -0
  45. data/lib/phobos/test.rb +3 -0
  46. data/lib/phobos/test/helper.rb +29 -0
  47. data/lib/phobos/version.rb +5 -0
  48. data/lib/phobos_temp_fork.rb +175 -0
  49. data/logo.png +0 -0
  50. data/phobos.gemspec +69 -0
  51. data/phobos_boot.rb +31 -0
  52. data/utils/create-topic.sh +13 -0
  53. metadata +308 -0
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
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
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
5
+
6
+ require 'phobos'
7
+ require 'phobos/cli'
8
+
9
+ STDOUT.sync = true
10
+ Phobos::CLI::Commands.start(ARGV)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -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