karafka 1.4.13 → 2.0.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
- checksums.yaml.gz.sig +3 -3
- data/.github/workflows/ci.yml +85 -30
- data/.ruby-version +1 -1
- data/CHANGELOG.md +268 -7
- data/CONTRIBUTING.md +10 -19
- data/Gemfile +6 -0
- data/Gemfile.lock +44 -87
- data/LICENSE +17 -0
- data/LICENSE-COMM +89 -0
- data/LICENSE-LGPL +165 -0
- data/README.md +44 -48
- data/bin/benchmarks +85 -0
- data/bin/create_token +22 -0
- data/bin/integrations +237 -0
- data/bin/karafka +4 -0
- data/bin/scenario +29 -0
- data/bin/stress_many +13 -0
- data/bin/stress_one +13 -0
- data/bin/wait_for_kafka +20 -0
- data/certs/karafka-pro.pem +11 -0
- data/config/errors.yml +55 -40
- data/docker-compose.yml +39 -3
- data/karafka.gemspec +11 -17
- data/lib/active_job/karafka.rb +21 -0
- data/lib/active_job/queue_adapters/karafka_adapter.rb +26 -0
- data/lib/karafka/active_job/consumer.rb +26 -0
- data/lib/karafka/active_job/dispatcher.rb +38 -0
- data/lib/karafka/active_job/job_extensions.rb +34 -0
- data/lib/karafka/active_job/job_options_contract.rb +21 -0
- data/lib/karafka/active_job/routing/extensions.rb +31 -0
- data/lib/karafka/app.rb +15 -20
- data/lib/karafka/base_consumer.rb +181 -31
- data/lib/karafka/cli/base.rb +4 -4
- data/lib/karafka/cli/info.rb +43 -9
- data/lib/karafka/cli/install.rb +19 -10
- data/lib/karafka/cli/server.rb +17 -42
- data/lib/karafka/cli.rb +4 -11
- data/lib/karafka/connection/client.rb +385 -90
- data/lib/karafka/connection/listener.rb +246 -38
- data/lib/karafka/connection/listeners_batch.rb +24 -0
- data/lib/karafka/connection/messages_buffer.rb +84 -0
- data/lib/karafka/connection/pauses_manager.rb +46 -0
- data/lib/karafka/connection/raw_messages_buffer.rb +101 -0
- data/lib/karafka/connection/rebalance_manager.rb +78 -0
- data/lib/karafka/contracts/base.rb +17 -0
- data/lib/karafka/contracts/config.rb +88 -11
- data/lib/karafka/contracts/consumer_group.rb +21 -189
- data/lib/karafka/contracts/consumer_group_topic.rb +34 -11
- data/lib/karafka/contracts/server_cli_options.rb +19 -18
- data/lib/karafka/contracts.rb +1 -1
- data/lib/karafka/env.rb +46 -0
- data/lib/karafka/errors.rb +21 -21
- data/lib/karafka/helpers/async.rb +33 -0
- data/lib/karafka/helpers/colorize.rb +20 -0
- data/lib/karafka/helpers/multi_delegator.rb +2 -2
- data/lib/karafka/instrumentation/callbacks/error.rb +40 -0
- data/lib/karafka/instrumentation/callbacks/statistics.rb +41 -0
- data/lib/karafka/instrumentation/logger_listener.rb +164 -0
- data/lib/karafka/instrumentation/monitor.rb +13 -61
- data/lib/karafka/instrumentation/notifications.rb +52 -0
- data/lib/karafka/instrumentation/proctitle_listener.rb +3 -3
- data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -0
- data/lib/karafka/instrumentation/vendors/datadog/listener.rb +232 -0
- data/lib/karafka/instrumentation.rb +21 -0
- data/lib/karafka/licenser.rb +75 -0
- data/lib/karafka/messages/batch_metadata.rb +45 -0
- data/lib/karafka/messages/builders/batch_metadata.rb +40 -0
- data/lib/karafka/messages/builders/message.rb +39 -0
- data/lib/karafka/messages/builders/messages.rb +32 -0
- data/lib/karafka/{params/params.rb → messages/message.rb} +7 -12
- data/lib/karafka/messages/messages.rb +64 -0
- data/lib/karafka/{params → messages}/metadata.rb +4 -6
- data/lib/karafka/messages/seek.rb +9 -0
- data/lib/karafka/patches/rdkafka/consumer.rb +22 -0
- data/lib/karafka/pro/active_job/consumer.rb +46 -0
- data/lib/karafka/pro/active_job/dispatcher.rb +61 -0
- data/lib/karafka/pro/active_job/job_options_contract.rb +32 -0
- data/lib/karafka/pro/base_consumer.rb +82 -0
- data/lib/karafka/pro/contracts/base.rb +21 -0
- data/lib/karafka/pro/contracts/consumer_group.rb +34 -0
- data/lib/karafka/pro/contracts/consumer_group_topic.rb +33 -0
- data/lib/karafka/pro/loader.rb +76 -0
- data/lib/karafka/pro/performance_tracker.rb +80 -0
- data/lib/karafka/pro/processing/coordinator.rb +72 -0
- data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +37 -0
- data/lib/karafka/pro/processing/jobs_builder.rb +32 -0
- data/lib/karafka/pro/processing/partitioner.rb +60 -0
- data/lib/karafka/pro/processing/scheduler.rb +56 -0
- data/lib/karafka/pro/routing/builder_extensions.rb +30 -0
- data/lib/karafka/pro/routing/topic_extensions.rb +38 -0
- data/lib/karafka/pro.rb +13 -0
- data/lib/karafka/process.rb +1 -0
- data/lib/karafka/processing/coordinator.rb +88 -0
- data/lib/karafka/processing/coordinators_buffer.rb +54 -0
- data/lib/karafka/processing/executor.rb +118 -0
- data/lib/karafka/processing/executors_buffer.rb +88 -0
- data/lib/karafka/processing/jobs/base.rb +51 -0
- data/lib/karafka/processing/jobs/consume.rb +42 -0
- data/lib/karafka/processing/jobs/revoked.rb +22 -0
- data/lib/karafka/processing/jobs/shutdown.rb +23 -0
- data/lib/karafka/processing/jobs_builder.rb +29 -0
- data/lib/karafka/processing/jobs_queue.rb +144 -0
- data/lib/karafka/processing/partitioner.rb +22 -0
- data/lib/karafka/processing/result.rb +29 -0
- data/lib/karafka/processing/scheduler.rb +22 -0
- data/lib/karafka/processing/worker.rb +88 -0
- data/lib/karafka/processing/workers_batch.rb +27 -0
- data/lib/karafka/railtie.rb +113 -0
- data/lib/karafka/routing/builder.rb +15 -24
- data/lib/karafka/routing/consumer_group.rb +11 -19
- data/lib/karafka/routing/consumer_mapper.rb +1 -2
- data/lib/karafka/routing/router.rb +1 -1
- data/lib/karafka/routing/subscription_group.rb +53 -0
- data/lib/karafka/routing/subscription_groups_builder.rb +53 -0
- data/lib/karafka/routing/topic.rb +61 -24
- data/lib/karafka/routing/topics.rb +38 -0
- data/lib/karafka/runner.rb +51 -0
- data/lib/karafka/serialization/json/deserializer.rb +6 -15
- data/lib/karafka/server.rb +67 -26
- data/lib/karafka/setup/config.rb +147 -175
- data/lib/karafka/status.rb +14 -5
- data/lib/karafka/templates/example_consumer.rb.erb +16 -0
- data/lib/karafka/templates/karafka.rb.erb +15 -51
- data/lib/karafka/time_trackers/base.rb +19 -0
- data/lib/karafka/time_trackers/pause.rb +92 -0
- data/lib/karafka/time_trackers/poll.rb +65 -0
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +38 -17
- data.tar.gz.sig +0 -0
- metadata +118 -120
- metadata.gz.sig +0 -0
- data/MIT-LICENCE +0 -18
- data/lib/karafka/assignment_strategies/round_robin.rb +0 -13
- data/lib/karafka/attributes_map.rb +0 -63
- data/lib/karafka/backends/inline.rb +0 -16
- data/lib/karafka/base_responder.rb +0 -226
- data/lib/karafka/cli/flow.rb +0 -48
- data/lib/karafka/cli/missingno.rb +0 -19
- data/lib/karafka/code_reloader.rb +0 -67
- data/lib/karafka/connection/api_adapter.rb +0 -158
- data/lib/karafka/connection/batch_delegator.rb +0 -55
- data/lib/karafka/connection/builder.rb +0 -23
- data/lib/karafka/connection/message_delegator.rb +0 -36
- data/lib/karafka/consumers/batch_metadata.rb +0 -10
- data/lib/karafka/consumers/callbacks.rb +0 -71
- data/lib/karafka/consumers/includer.rb +0 -64
- data/lib/karafka/consumers/responders.rb +0 -24
- data/lib/karafka/consumers/single_params.rb +0 -15
- data/lib/karafka/contracts/responder_usage.rb +0 -54
- data/lib/karafka/fetcher.rb +0 -42
- data/lib/karafka/helpers/class_matcher.rb +0 -88
- data/lib/karafka/helpers/config_retriever.rb +0 -46
- data/lib/karafka/helpers/inflector.rb +0 -26
- data/lib/karafka/instrumentation/stdout_listener.rb +0 -140
- data/lib/karafka/params/batch_metadata.rb +0 -26
- data/lib/karafka/params/builders/batch_metadata.rb +0 -30
- data/lib/karafka/params/builders/params.rb +0 -38
- data/lib/karafka/params/builders/params_batch.rb +0 -25
- data/lib/karafka/params/params_batch.rb +0 -60
- data/lib/karafka/patches/ruby_kafka.rb +0 -47
- data/lib/karafka/persistence/client.rb +0 -29
- data/lib/karafka/persistence/consumers.rb +0 -45
- data/lib/karafka/persistence/topics.rb +0 -48
- data/lib/karafka/responders/builder.rb +0 -36
- data/lib/karafka/responders/topic.rb +0 -55
- data/lib/karafka/routing/topic_mapper.rb +0 -53
- data/lib/karafka/serialization/json/serializer.rb +0 -31
- data/lib/karafka/setup/configurators/water_drop.rb +0 -36
- data/lib/karafka/templates/application_responder.rb.erb +0 -11
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'active_job'
|
|
5
|
+
require_relative 'queue_adapters/karafka_adapter'
|
|
6
|
+
|
|
7
|
+
module ActiveJob
|
|
8
|
+
# Namespace for usage simplification outside of Rails where Railtie will not kick in.
|
|
9
|
+
# That way a require 'active_job/karafka' should be enough to use it
|
|
10
|
+
module Karafka
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# We extend routing builder by adding a simple wrapper for easier jobs topics defining
|
|
15
|
+
# This needs to be extended here as it is going to be used in karafka routes, hence doing that in
|
|
16
|
+
# the railtie initializer would be too late
|
|
17
|
+
::Karafka::Routing::Builder.include ::Karafka::ActiveJob::Routing::Extensions
|
|
18
|
+
::Karafka::Routing::Proxy.include ::Karafka::ActiveJob::Routing::Extensions
|
|
19
|
+
rescue LoadError
|
|
20
|
+
# We extend ActiveJob stuff in the railtie
|
|
21
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob components to allow for jobs consumption with Karafka
|
|
4
|
+
module ActiveJob
|
|
5
|
+
# ActiveJob queue adapters
|
|
6
|
+
module QueueAdapters
|
|
7
|
+
# Karafka adapter for enqueuing jobs
|
|
8
|
+
# This is here for ease of integration with ActiveJob.
|
|
9
|
+
class KarafkaAdapter
|
|
10
|
+
# Enqueues the job using the configured dispatcher
|
|
11
|
+
#
|
|
12
|
+
# @param job [Object] job that should be enqueued
|
|
13
|
+
def enqueue(job)
|
|
14
|
+
::Karafka::App.config.internal.active_job.dispatcher.call(job)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Raises info, that Karafka backend does not support scheduling jobs
|
|
18
|
+
#
|
|
19
|
+
# @param _job [Object] job we cannot enqueue
|
|
20
|
+
# @param _timestamp [Time] time when job should run
|
|
21
|
+
def enqueue_at(_job, _timestamp)
|
|
22
|
+
raise NotImplementedError, 'This queueing backend does not support scheduling jobs.'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module ActiveJob
|
|
5
|
+
# This is the consumer for ActiveJob that eats the messages enqueued with it one after another.
|
|
6
|
+
# It marks the offset after each message, so we make sure, none of the jobs is executed twice
|
|
7
|
+
class Consumer < ::Karafka::BaseConsumer
|
|
8
|
+
# Executes the ActiveJob logic
|
|
9
|
+
# @note ActiveJob does not support batches, so we just run one message after another
|
|
10
|
+
def consume
|
|
11
|
+
messages.each do |message|
|
|
12
|
+
break if Karafka::App.stopping?
|
|
13
|
+
|
|
14
|
+
::ActiveJob::Base.execute(
|
|
15
|
+
# We technically speaking could set this as deserializer and reference it from the
|
|
16
|
+
# message instead of using the `#raw_payload`. This is not done on purpose to simplify
|
|
17
|
+
# the ActiveJob setup here
|
|
18
|
+
::ActiveSupport::JSON.decode(message.raw_payload)
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
mark_as_consumed(message)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module ActiveJob
|
|
5
|
+
# Dispatcher that sends the ActiveJob job to a proper topic based on the queue name
|
|
6
|
+
class Dispatcher
|
|
7
|
+
# Defaults for dispatching
|
|
8
|
+
# The can be updated by using `#karafka_options` on the job
|
|
9
|
+
DEFAULTS = {
|
|
10
|
+
dispatch_method: :produce_async
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
private_constant :DEFAULTS
|
|
14
|
+
|
|
15
|
+
# @param job [ActiveJob::Base] job
|
|
16
|
+
def call(job)
|
|
17
|
+
::Karafka.producer.public_send(
|
|
18
|
+
fetch_option(job, :dispatch_method, DEFAULTS),
|
|
19
|
+
topic: job.queue_name,
|
|
20
|
+
payload: ::ActiveSupport::JSON.encode(job.serialize)
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# @param job [ActiveJob::Base] job
|
|
27
|
+
# @param key [Symbol] key we want to fetch
|
|
28
|
+
# @param defaults [Hash]
|
|
29
|
+
# @return [Object] options we are interested in
|
|
30
|
+
def fetch_option(job, key, defaults)
|
|
31
|
+
job
|
|
32
|
+
.class
|
|
33
|
+
.karafka_options
|
|
34
|
+
.fetch(key, defaults.fetch(key))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module ActiveJob
|
|
5
|
+
# Allows for setting karafka specific options in ActiveJob jobs
|
|
6
|
+
module JobExtensions
|
|
7
|
+
class << self
|
|
8
|
+
# Defines all the needed accessors and sets defaults
|
|
9
|
+
# @param klass [ActiveJob::Base] active job base
|
|
10
|
+
def extended(klass)
|
|
11
|
+
klass.class_attribute :_karafka_options
|
|
12
|
+
klass._karafka_options = {}
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param new_options [Hash] additional options that allow for jobs Karafka related options
|
|
17
|
+
# customization
|
|
18
|
+
# @return [Hash] karafka options
|
|
19
|
+
def karafka_options(new_options = {})
|
|
20
|
+
return _karafka_options if new_options.empty?
|
|
21
|
+
|
|
22
|
+
# Make sure, that karafka options that someone wants to use are valid before assigning
|
|
23
|
+
# them
|
|
24
|
+
App.config.internal.active_job.job_options_contract.validate!(new_options)
|
|
25
|
+
|
|
26
|
+
new_options.each do |name, value|
|
|
27
|
+
_karafka_options[name] = value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
_karafka_options
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module ActiveJob
|
|
5
|
+
# Contract for validating the options that can be altered with `#karafka_options` per job class
|
|
6
|
+
# @note We keep this in the `Karafka::ActiveJob` namespace instead of `Karafka::Contracts` as
|
|
7
|
+
# we want to keep ActiveJob related Karafka components outside of the core Karafka code and
|
|
8
|
+
# all in the same place
|
|
9
|
+
class JobOptionsContract < Contracts::Base
|
|
10
|
+
configure do |config|
|
|
11
|
+
config.error_messages = YAML.safe_load(
|
|
12
|
+
File.read(
|
|
13
|
+
File.join(Karafka.gem_root, 'config', 'errors.yml')
|
|
14
|
+
)
|
|
15
|
+
).fetch('en').fetch('validations').fetch('job_options')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
optional(:dispatch_method) { |val| %i[produce_async produce_sync].include?(val) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
# ActiveJob related Karafka stuff
|
|
5
|
+
module ActiveJob
|
|
6
|
+
# Karafka routing ActiveJob related components
|
|
7
|
+
module Routing
|
|
8
|
+
# Routing extensions for ActiveJob
|
|
9
|
+
module Extensions
|
|
10
|
+
# This method simplifies routes definition for ActiveJob topics / queues by auto-injecting
|
|
11
|
+
# the consumer class
|
|
12
|
+
# @param name [String, Symbol] name of the topic where ActiveJobs jobs should go
|
|
13
|
+
# @param block [Proc] block that we can use for some extra configuration
|
|
14
|
+
def active_job_topic(name, &block)
|
|
15
|
+
topic(name) do
|
|
16
|
+
consumer App.config.internal.active_job.consumer_class
|
|
17
|
+
|
|
18
|
+
next unless block
|
|
19
|
+
|
|
20
|
+
instance_eval(&block)
|
|
21
|
+
|
|
22
|
+
# This is handled by our custom ActiveJob consumer
|
|
23
|
+
# Without this, default behaviour would cause messages to skip upon shutdown as the
|
|
24
|
+
# offset would be committed for the last message
|
|
25
|
+
manual_offset_management true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/karafka/app.rb
CHANGED
|
@@ -6,31 +6,24 @@ module Karafka
|
|
|
6
6
|
extend Setup::Dsl
|
|
7
7
|
|
|
8
8
|
class << self
|
|
9
|
-
#
|
|
10
|
-
# We need to know details about consumers in order to setup components,
|
|
11
|
-
# that's why we don't setup them after std setup is done
|
|
12
|
-
# @raise [Karafka::Errors::InvalidConfigurationError] raised when configuration
|
|
13
|
-
# doesn't match with the config contract
|
|
14
|
-
def boot!
|
|
15
|
-
initialize!
|
|
16
|
-
Setup::Config.validate!
|
|
17
|
-
Setup::Config.setup_components
|
|
18
|
-
initialized!
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# @return [Karafka::Routing::Builder] consumers builder instance
|
|
9
|
+
# @return [Karafka::Routing::Builder] consumers builder instance alias
|
|
22
10
|
def consumer_groups
|
|
23
|
-
config
|
|
11
|
+
config
|
|
12
|
+
.internal
|
|
13
|
+
.routing
|
|
14
|
+
.builder
|
|
24
15
|
end
|
|
25
16
|
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
config.internal.routing_builder.reload
|
|
17
|
+
# @return [Array<Karafka::Routing::SubscriptionGroup>] active subscription groups
|
|
18
|
+
def subscription_groups
|
|
19
|
+
consumer_groups
|
|
20
|
+
.active
|
|
21
|
+
.flat_map(&:subscription_groups)
|
|
32
22
|
end
|
|
33
23
|
|
|
24
|
+
# Just a nicer name for the consumer groups
|
|
25
|
+
alias routes consumer_groups
|
|
26
|
+
|
|
34
27
|
Status.instance_methods(false).each do |delegated|
|
|
35
28
|
define_method(delegated) do
|
|
36
29
|
App.config.internal.status.send(delegated)
|
|
@@ -42,7 +35,9 @@ module Karafka
|
|
|
42
35
|
root
|
|
43
36
|
env
|
|
44
37
|
logger
|
|
38
|
+
producer
|
|
45
39
|
monitor
|
|
40
|
+
pro?
|
|
46
41
|
].each do |delegated|
|
|
47
42
|
define_method(delegated) do
|
|
48
43
|
Karafka.send(delegated)
|
|
@@ -4,48 +4,108 @@
|
|
|
4
4
|
module Karafka
|
|
5
5
|
# Base consumer from which all Karafka consumers should inherit
|
|
6
6
|
class BaseConsumer
|
|
7
|
-
|
|
7
|
+
# @return [Karafka::Routing::Topic] topic to which a given consumer is subscribed
|
|
8
|
+
attr_accessor :topic
|
|
9
|
+
# @return [Karafka::Messages::Messages] current messages batch
|
|
10
|
+
attr_accessor :messages
|
|
11
|
+
# @return [Karafka::Connection::Client] kafka connection client
|
|
12
|
+
attr_accessor :client
|
|
13
|
+
# @return [Karafka::Processing::Coordinator] coordinator
|
|
14
|
+
attr_accessor :coordinator
|
|
15
|
+
# @return [Waterdrop::Producer] producer instance
|
|
16
|
+
attr_accessor :producer
|
|
8
17
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
trigger_heartbeat!
|
|
17
|
-
].each do |delegated_method_name|
|
|
18
|
-
def_delegator :client, delegated_method_name
|
|
18
|
+
# Can be used to run preparation code
|
|
19
|
+
#
|
|
20
|
+
# @private
|
|
21
|
+
# @note This should not be used by the end users as it is part of the lifecycle of things but
|
|
22
|
+
# not as part of the public api. This can act as a hook when creating non-blocking
|
|
23
|
+
# consumers and doing other advanced stuff
|
|
24
|
+
def on_before_consume; end
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
# Executes the default consumer flow.
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean] true if there was no exception, otherwise false.
|
|
29
|
+
#
|
|
30
|
+
# @note We keep the seek offset tracking, and use it to compensate for async offset flushing
|
|
31
|
+
# that may not yet kick in when error occurs. That way we pause always on the last processed
|
|
32
|
+
# message.
|
|
33
|
+
def on_consume
|
|
34
|
+
Karafka.monitor.instrument('consumer.consumed', caller: self) do
|
|
35
|
+
consume
|
|
36
|
+
end
|
|
22
37
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
attr_accessor :params_batch
|
|
38
|
+
coordinator.consumption(self).success!
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
coordinator.consumption(self).failure!
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
Karafka.monitor.instrument(
|
|
43
|
+
'error.occurred',
|
|
44
|
+
error: e,
|
|
45
|
+
caller: self,
|
|
46
|
+
type: 'consumer.consume.error'
|
|
47
|
+
)
|
|
48
|
+
ensure
|
|
49
|
+
# We need to decrease number of jobs that this coordinator coordinates as it has finished
|
|
50
|
+
coordinator.decrement
|
|
34
51
|
end
|
|
35
52
|
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
# @private
|
|
54
|
+
# @note This should not be used by the end users as it is part of the lifecycle of things but
|
|
55
|
+
# not as part of the public api.
|
|
56
|
+
def on_after_consume
|
|
57
|
+
return if revoked?
|
|
58
|
+
|
|
59
|
+
if coordinator.success?
|
|
60
|
+
coordinator.pause_tracker.reset
|
|
61
|
+
|
|
62
|
+
# Mark as consumed only if manual offset management is not on
|
|
63
|
+
return if topic.manual_offset_management?
|
|
64
|
+
|
|
65
|
+
# We use the non-blocking one here. If someone needs the blocking one, can implement it
|
|
66
|
+
# with manual offset management
|
|
67
|
+
mark_as_consumed(messages.last)
|
|
68
|
+
else
|
|
69
|
+
pause(@seek_offset || messages.first.offset)
|
|
70
|
+
end
|
|
39
71
|
end
|
|
40
72
|
|
|
41
|
-
|
|
73
|
+
# Trigger method for running on shutdown.
|
|
74
|
+
#
|
|
75
|
+
# @private
|
|
76
|
+
def on_revoked
|
|
77
|
+
coordinator.revoke
|
|
42
78
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
79
|
+
Karafka.monitor.instrument('consumer.revoked', caller: self) do
|
|
80
|
+
revoked
|
|
81
|
+
end
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
Karafka.monitor.instrument(
|
|
84
|
+
'error.occurred',
|
|
85
|
+
error: e,
|
|
86
|
+
caller: self,
|
|
87
|
+
type: 'consumer.revoked.error'
|
|
88
|
+
)
|
|
47
89
|
end
|
|
48
90
|
|
|
91
|
+
# Trigger method for running on shutdown.
|
|
92
|
+
#
|
|
93
|
+
# @private
|
|
94
|
+
def on_shutdown
|
|
95
|
+
Karafka.monitor.instrument('consumer.shutdown', caller: self) do
|
|
96
|
+
shutdown
|
|
97
|
+
end
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
Karafka.monitor.instrument(
|
|
100
|
+
'error.occurred',
|
|
101
|
+
error: e,
|
|
102
|
+
caller: self,
|
|
103
|
+
type: 'consumer.shutdown.error'
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
49
109
|
# Method that will perform business logic and on data received from Kafka (it will consume
|
|
50
110
|
# the data)
|
|
51
111
|
# @note This method needs bo be implemented in a subclass. We stub it here as a failover if
|
|
@@ -53,5 +113,95 @@ module Karafka
|
|
|
53
113
|
def consume
|
|
54
114
|
raise NotImplementedError, 'Implement this in a subclass'
|
|
55
115
|
end
|
|
116
|
+
|
|
117
|
+
# Method that will be executed when a given topic partition is revoked. You can use it for
|
|
118
|
+
# some teardown procedures (closing file handler, etc).
|
|
119
|
+
def revoked; end
|
|
120
|
+
|
|
121
|
+
# Method that will be executed when the process is shutting down. You can use it for
|
|
122
|
+
# some teardown procedures (closing file handler, etc).
|
|
123
|
+
def shutdown; end
|
|
124
|
+
|
|
125
|
+
# Marks message as consumed in an async way.
|
|
126
|
+
#
|
|
127
|
+
# @param message [Messages::Message] last successfully processed message.
|
|
128
|
+
# @return [Boolean] true if we were able to mark the offset, false otherwise. False indicates
|
|
129
|
+
# that we were not able and that we have lost the partition.
|
|
130
|
+
#
|
|
131
|
+
# @note We keep track of this offset in case we would mark as consumed and got error when
|
|
132
|
+
# processing another message. In case like this we do not pause on the message we've already
|
|
133
|
+
# processed but rather at the next one. This applies to both sync and async versions of this
|
|
134
|
+
# method.
|
|
135
|
+
def mark_as_consumed(message)
|
|
136
|
+
unless client.mark_as_consumed(message)
|
|
137
|
+
coordinator.revoke
|
|
138
|
+
|
|
139
|
+
return false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
@seek_offset = message.offset + 1
|
|
143
|
+
|
|
144
|
+
true
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Marks message as consumed in a sync way.
|
|
148
|
+
#
|
|
149
|
+
# @param message [Messages::Message] last successfully processed message.
|
|
150
|
+
# @return [Boolean] true if we were able to mark the offset, false otherwise. False indicates
|
|
151
|
+
# that we were not able and that we have lost the partition.
|
|
152
|
+
def mark_as_consumed!(message)
|
|
153
|
+
unless client.mark_as_consumed!(message)
|
|
154
|
+
coordinator.revoke
|
|
155
|
+
|
|
156
|
+
return false
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
@seek_offset = message.offset + 1
|
|
160
|
+
|
|
161
|
+
true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Pauses processing on a given offset for the current topic partition
|
|
165
|
+
#
|
|
166
|
+
# After given partition is resumed, it will continue processing from the given offset
|
|
167
|
+
# @param offset [Integer] offset from which we want to restart the processing
|
|
168
|
+
# @param timeout [Integer, nil] how long in milliseconds do we want to pause or nil to use the
|
|
169
|
+
# default exponential pausing strategy defined for retries
|
|
170
|
+
def pause(offset, timeout = nil)
|
|
171
|
+
timeout ? coordinator.pause_tracker.pause(timeout) : coordinator.pause_tracker.pause
|
|
172
|
+
|
|
173
|
+
client.pause(
|
|
174
|
+
messages.metadata.topic,
|
|
175
|
+
messages.metadata.partition,
|
|
176
|
+
offset
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Resumes processing of the current topic partition
|
|
181
|
+
def resume
|
|
182
|
+
# This is sufficient to expire a partition pause, as with it will be resumed by the listener
|
|
183
|
+
# thread before the next poll.
|
|
184
|
+
coordinator.pause_tracker.expire
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Seeks in the context of current topic and partition
|
|
188
|
+
#
|
|
189
|
+
# @param offset [Integer] offset where we want to seek
|
|
190
|
+
def seek(offset)
|
|
191
|
+
client.seek(
|
|
192
|
+
Karafka::Messages::Seek.new(
|
|
193
|
+
messages.metadata.topic,
|
|
194
|
+
messages.metadata.partition,
|
|
195
|
+
offset
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @return [Boolean] true if partition was revoked from the current consumer
|
|
201
|
+
# @note We know that partition got revoked because when we try to mark message as consumed,
|
|
202
|
+
# unless if is successful, it will return false
|
|
203
|
+
def revoked?
|
|
204
|
+
coordinator.revoked?
|
|
205
|
+
end
|
|
56
206
|
end
|
|
57
207
|
end
|
data/lib/karafka/cli/base.rb
CHANGED
|
@@ -43,16 +43,16 @@ module Karafka
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
# Allows to set description of a given cli command
|
|
46
|
-
# @param
|
|
47
|
-
def desc(
|
|
48
|
-
@desc ||=
|
|
46
|
+
# @param desc [String] Description of a given cli command
|
|
47
|
+
def desc(desc)
|
|
48
|
+
@desc ||= desc
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# This method will bind a given Cli command into Karafka Cli
|
|
52
52
|
# This method is a wrapper to way Thor defines its commands
|
|
53
53
|
# @param cli_class [Karafka::Cli] Karafka cli_class
|
|
54
54
|
def bind_to(cli_class)
|
|
55
|
-
cli_class.desc name,
|
|
55
|
+
cli_class.desc name, @desc
|
|
56
56
|
|
|
57
57
|
(@options || []).each { |option| cli_class.option(*option) }
|
|
58
58
|
|
data/lib/karafka/cli/info.rb
CHANGED
|
@@ -7,24 +7,58 @@ module Karafka
|
|
|
7
7
|
class Info < Base
|
|
8
8
|
desc 'Print configuration details and other options of your application'
|
|
9
9
|
|
|
10
|
+
# Nice karafka banner
|
|
11
|
+
BANNER = <<~BANNER
|
|
12
|
+
|
|
13
|
+
@@@ @@@@@ @@@
|
|
14
|
+
@@@ @@@ @@@
|
|
15
|
+
@@@ @@@ @@@@@@@@@ @@@ @@@ @@@@@@@@@ @@@@@@@@ @@@ @@@@ @@@@@@@@@
|
|
16
|
+
@@@@@@ @@@ @@@ @@@@@ @@@ @@@ @@@ @@@@@@@ @@@ @@@
|
|
17
|
+
@@@@@@@ @@@ @@@ @@@ @@@@ @@@ @@@ @@@@@@@ @@@ @@@
|
|
18
|
+
@@@ @@@@ @@@@@@@@@@ @@@ @@@@@@@@@@ @@@ @@@ @@@@ @@@@@@@@@@
|
|
19
|
+
|
|
20
|
+
BANNER
|
|
21
|
+
|
|
10
22
|
# Print configuration details and other options of your application
|
|
11
23
|
def call
|
|
24
|
+
Karafka.logger.info(BANNER)
|
|
25
|
+
Karafka.logger.info((core_info + license_info).join("\n"))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# @return [Array<String>] core framework related info
|
|
31
|
+
def core_info
|
|
12
32
|
config = Karafka::App.config
|
|
13
33
|
|
|
14
|
-
|
|
15
|
-
|
|
34
|
+
postfix = Karafka.pro? ? ' + Pro' : ''
|
|
35
|
+
|
|
36
|
+
[
|
|
37
|
+
"Karafka version: #{Karafka::VERSION}#{postfix}",
|
|
16
38
|
"Ruby version: #{RUBY_VERSION}",
|
|
17
|
-
"
|
|
39
|
+
"Rdkafka version: #{::Rdkafka::VERSION}",
|
|
40
|
+
"Subscription groups count: #{Karafka::App.subscription_groups.size}",
|
|
41
|
+
"Workers count: #{Karafka::App.config.concurrency}",
|
|
18
42
|
"Application client id: #{config.client_id}",
|
|
19
|
-
"Backend: #{config.backend}",
|
|
20
|
-
"Batch fetching: #{config.batch_fetching}",
|
|
21
|
-
"Batch consuming: #{config.batch_consuming}",
|
|
22
43
|
"Boot file: #{Karafka.boot_file}",
|
|
23
|
-
"Environment: #{Karafka.env}"
|
|
24
|
-
"Kafka seed brokers: #{config.kafka.seed_brokers}"
|
|
44
|
+
"Environment: #{Karafka.env}"
|
|
25
45
|
]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Array<String>] license related info
|
|
49
|
+
def license_info
|
|
50
|
+
config = Karafka::App.config
|
|
26
51
|
|
|
27
|
-
Karafka.
|
|
52
|
+
if Karafka.pro?
|
|
53
|
+
[
|
|
54
|
+
'License: Commercial',
|
|
55
|
+
"License entity: #{config.license.entity}"
|
|
56
|
+
]
|
|
57
|
+
else
|
|
58
|
+
[
|
|
59
|
+
'License: LGPL-3.0'
|
|
60
|
+
]
|
|
61
|
+
end
|
|
28
62
|
end
|
|
29
63
|
end
|
|
30
64
|
end
|
data/lib/karafka/cli/install.rb
CHANGED
|
@@ -7,24 +7,23 @@ module Karafka
|
|
|
7
7
|
class Cli < Thor
|
|
8
8
|
# Install Karafka Cli action
|
|
9
9
|
class Install < Base
|
|
10
|
+
include Helpers::Colorize
|
|
11
|
+
|
|
10
12
|
desc 'Install all required things for Karafka application in current directory'
|
|
11
13
|
|
|
12
14
|
# Directories created by default
|
|
13
15
|
INSTALL_DIRS = %w[
|
|
14
16
|
app/consumers
|
|
15
|
-
app/responders
|
|
16
|
-
app/workers
|
|
17
17
|
config
|
|
18
|
-
lib
|
|
19
18
|
log
|
|
20
|
-
|
|
19
|
+
lib
|
|
21
20
|
].freeze
|
|
22
21
|
|
|
23
22
|
# Where should we map proper files from templates
|
|
24
23
|
INSTALL_FILES_MAP = {
|
|
25
24
|
'karafka.rb.erb' => Karafka.boot_file.basename,
|
|
26
25
|
'application_consumer.rb.erb' => 'app/consumers/application_consumer.rb',
|
|
27
|
-
'
|
|
26
|
+
'example_consumer.rb.erb' => 'app/consumers/example_consumer.rb'
|
|
28
27
|
}.freeze
|
|
29
28
|
|
|
30
29
|
# @param args [Array] all the things that Thor CLI accepts
|
|
@@ -35,6 +34,7 @@ module Karafka
|
|
|
35
34
|
Bundler.default_lockfile
|
|
36
35
|
)
|
|
37
36
|
).dependencies
|
|
37
|
+
|
|
38
38
|
@rails = dependencies.key?('railties') || dependencies.key?('rails')
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -44,16 +44,25 @@ module Karafka
|
|
|
44
44
|
FileUtils.mkdir_p Karafka.root.join(dir)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
puts
|
|
48
|
+
puts 'Installing Karafka framework...'
|
|
49
|
+
puts 'Ruby on Rails detected...' if rails?
|
|
50
|
+
puts
|
|
51
|
+
|
|
47
52
|
INSTALL_FILES_MAP.each do |source, target|
|
|
48
|
-
|
|
53
|
+
pathed_target = Karafka.root.join(target)
|
|
49
54
|
|
|
50
55
|
template = File.read(Karafka.core_root.join("templates/#{source}"))
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
render = ::ERB.new(template, trim_mode: '-').result(binding)
|
|
57
|
+
|
|
58
|
+
File.open(pathed_target, 'w') { |file| file.write(render) }
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
puts "#{green('Created')} #{target}"
|
|
56
61
|
end
|
|
62
|
+
|
|
63
|
+
puts
|
|
64
|
+
puts("Installation #{green('completed')}. Have fun!")
|
|
65
|
+
puts
|
|
57
66
|
end
|
|
58
67
|
|
|
59
68
|
# @return [Boolean] true if we have Rails loaded
|