karafka 2.3.4 → 2.4.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +12 -38
- data/CHANGELOG.md +56 -2
- data/Gemfile +6 -3
- data/Gemfile.lock +25 -23
- data/bin/integrations +1 -1
- data/config/locales/errors.yml +21 -2
- data/config/locales/pro_errors.yml +16 -1
- data/karafka.gemspec +4 -2
- data/lib/active_job/queue_adapters/karafka_adapter.rb +2 -0
- data/lib/karafka/admin/configs/config.rb +81 -0
- data/lib/karafka/admin/configs/resource.rb +88 -0
- data/lib/karafka/admin/configs.rb +103 -0
- data/lib/karafka/admin.rb +201 -100
- data/lib/karafka/base_consumer.rb +2 -2
- data/lib/karafka/cli/info.rb +9 -7
- data/lib/karafka/cli/server.rb +7 -7
- data/lib/karafka/cli/topics/align.rb +109 -0
- data/lib/karafka/cli/topics/base.rb +66 -0
- data/lib/karafka/cli/topics/create.rb +35 -0
- data/lib/karafka/cli/topics/delete.rb +30 -0
- data/lib/karafka/cli/topics/migrate.rb +31 -0
- data/lib/karafka/cli/topics/plan.rb +169 -0
- data/lib/karafka/cli/topics/repartition.rb +41 -0
- data/lib/karafka/cli/topics/reset.rb +18 -0
- data/lib/karafka/cli/topics.rb +13 -123
- data/lib/karafka/connection/client.rb +55 -37
- data/lib/karafka/connection/listener.rb +22 -17
- data/lib/karafka/connection/proxy.rb +93 -4
- data/lib/karafka/connection/status.rb +14 -2
- data/lib/karafka/contracts/config.rb +14 -1
- data/lib/karafka/contracts/topic.rb +1 -1
- data/lib/karafka/deserializers/headers.rb +15 -0
- data/lib/karafka/deserializers/key.rb +15 -0
- data/lib/karafka/deserializers/payload.rb +16 -0
- data/lib/karafka/embedded.rb +2 -0
- data/lib/karafka/helpers/async.rb +5 -2
- data/lib/karafka/helpers/colorize.rb +6 -0
- data/lib/karafka/instrumentation/callbacks/oauthbearer_token_refresh.rb +29 -0
- data/lib/karafka/instrumentation/logger_listener.rb +23 -3
- data/lib/karafka/instrumentation/notifications.rb +10 -0
- data/lib/karafka/instrumentation/vendors/appsignal/client.rb +16 -2
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +20 -0
- data/lib/karafka/messages/batch_metadata.rb +1 -1
- data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
- data/lib/karafka/messages/builders/message.rb +10 -6
- data/lib/karafka/messages/message.rb +2 -1
- data/lib/karafka/messages/metadata.rb +20 -4
- data/lib/karafka/messages/parser.rb +1 -1
- data/lib/karafka/pro/base_consumer.rb +12 -23
- data/lib/karafka/pro/encryption/cipher.rb +7 -3
- data/lib/karafka/pro/encryption/contracts/config.rb +1 -0
- data/lib/karafka/pro/encryption/errors.rb +4 -1
- data/lib/karafka/pro/encryption/messages/middleware.rb +13 -11
- data/lib/karafka/pro/encryption/messages/parser.rb +22 -20
- data/lib/karafka/pro/encryption/setup/config.rb +5 -0
- data/lib/karafka/pro/iterator/expander.rb +2 -1
- data/lib/karafka/pro/iterator/tpl_builder.rb +38 -0
- data/lib/karafka/pro/iterator.rb +28 -2
- data/lib/karafka/pro/loader.rb +3 -0
- data/lib/karafka/pro/processing/coordinator.rb +15 -2
- data/lib/karafka/pro/processing/expansions_selector.rb +2 -0
- data/lib/karafka/pro/processing/jobs_queue.rb +122 -5
- data/lib/karafka/pro/processing/periodic_job/consumer.rb +67 -0
- data/lib/karafka/pro/processing/piping/consumer.rb +126 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +2 -0
- data/lib/karafka/pro/processing/strategies/default.rb +5 -1
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +21 -5
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +2 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +2 -0
- data/lib/karafka/pro/processing/subscription_groups_coordinator.rb +52 -0
- data/lib/karafka/pro/routing/features/direct_assignments/config.rb +27 -0
- data/lib/karafka/pro/routing/features/direct_assignments/contracts/consumer_group.rb +53 -0
- data/lib/karafka/pro/routing/features/direct_assignments/contracts/topic.rb +108 -0
- data/lib/karafka/pro/routing/features/direct_assignments/subscription_group.rb +77 -0
- data/lib/karafka/pro/routing/features/direct_assignments/topic.rb +69 -0
- data/lib/karafka/pro/routing/features/direct_assignments.rb +25 -0
- data/lib/karafka/pro/routing/features/patterns/builder.rb +1 -1
- data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +76 -0
- data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +16 -5
- data/lib/karafka/pro/routing/features/swarm/topic.rb +25 -2
- data/lib/karafka/pro/routing/features/swarm.rb +11 -0
- data/lib/karafka/pro/swarm/liveness_listener.rb +20 -0
- data/lib/karafka/processing/coordinator.rb +17 -8
- data/lib/karafka/processing/coordinators_buffer.rb +5 -2
- data/lib/karafka/processing/executor.rb +6 -2
- data/lib/karafka/processing/executors_buffer.rb +5 -2
- data/lib/karafka/processing/jobs_queue.rb +9 -4
- data/lib/karafka/processing/strategies/aj_dlq_mom.rb +1 -1
- data/lib/karafka/processing/strategies/default.rb +7 -1
- data/lib/karafka/processing/strategies/dlq.rb +17 -2
- data/lib/karafka/processing/workers_batch.rb +4 -1
- data/lib/karafka/routing/builder.rb +6 -2
- data/lib/karafka/routing/consumer_group.rb +2 -1
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +5 -0
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +8 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +10 -2
- data/lib/karafka/routing/features/deserializers/config.rb +18 -0
- data/lib/karafka/routing/features/deserializers/contracts/topic.rb +31 -0
- data/lib/karafka/routing/features/deserializers/topic.rb +51 -0
- data/lib/karafka/routing/features/deserializers.rb +11 -0
- data/lib/karafka/routing/proxy.rb +9 -14
- data/lib/karafka/routing/router.rb +11 -2
- data/lib/karafka/routing/subscription_group.rb +9 -1
- data/lib/karafka/routing/topic.rb +0 -1
- data/lib/karafka/runner.rb +1 -1
- data/lib/karafka/setup/config.rb +50 -9
- data/lib/karafka/status.rb +7 -8
- data/lib/karafka/swarm/supervisor.rb +16 -2
- data/lib/karafka/templates/karafka.rb.erb +28 -1
- data/lib/karafka/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +38 -12
- metadata.gz.sig +0 -0
- data/lib/karafka/routing/consumer_mapper.rb +0 -23
- data/lib/karafka/serialization/json/deserializer.rb +0 -19
- data/lib/karafka/time_trackers/partition_usage.rb +0 -56
@@ -5,6 +5,10 @@ module Karafka
|
|
5
5
|
# Buffer for executors of a given subscription group. It wraps around the concept of building
|
6
6
|
# and caching them, so we can re-use them instead of creating new each time.
|
7
7
|
class ExecutorsBuffer
|
8
|
+
include Helpers::ConfigImporter.new(
|
9
|
+
executor_class: %i[internal processing executor_class]
|
10
|
+
)
|
11
|
+
|
8
12
|
# @param client [Connection::Client]
|
9
13
|
# @param subscription_group [Routing::SubscriptionGroup]
|
10
14
|
# @return [ExecutorsBuffer]
|
@@ -13,7 +17,6 @@ module Karafka
|
|
13
17
|
@client = client
|
14
18
|
# We need two layers here to keep track of topics, partitions and processing groups
|
15
19
|
@buffer = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = {} } }
|
16
|
-
@executor_class = Karafka::App.config.internal.processing.executor_class
|
17
20
|
end
|
18
21
|
|
19
22
|
# Finds or creates an executor based on the provided details
|
@@ -24,7 +27,7 @@ module Karafka
|
|
24
27
|
# @param coordinator [Karafka::Processing::Coordinator]
|
25
28
|
# @return [Executor, Pro::Processing::Executor] consumer executor
|
26
29
|
def find_or_create(topic, partition, parallel_key, coordinator)
|
27
|
-
@buffer[topic][partition][parallel_key] ||=
|
30
|
+
@buffer[topic][partition][parallel_key] ||= executor_class.new(
|
28
31
|
@subscription_group.id,
|
29
32
|
@client,
|
30
33
|
coordinator
|
@@ -13,6 +13,11 @@ module Karafka
|
|
13
13
|
# @note This job queue also keeps track / understands number of busy workers. This is because
|
14
14
|
# we use a single workers poll that can have granular scheduling.
|
15
15
|
class JobsQueue
|
16
|
+
include Helpers::ConfigImporter.new(
|
17
|
+
concurrency: %i[concurrency],
|
18
|
+
tick_interval: %i[internal tick_interval]
|
19
|
+
)
|
20
|
+
|
16
21
|
# @return [Karafka::Processing::JobsQueue]
|
17
22
|
def initialize
|
18
23
|
@queue = Queue.new
|
@@ -24,8 +29,6 @@ module Karafka
|
|
24
29
|
# We cannot use a single semaphore as it could potentially block in listeners that should
|
25
30
|
# process with their data and also could unlock when a given group needs to remain locked
|
26
31
|
@semaphores = {}
|
27
|
-
@concurrency = Karafka::App.config.concurrency
|
28
|
-
@tick_interval = ::Karafka::App.config.internal.tick_interval
|
29
32
|
@in_processing = Hash.new { |h, k| h[k] = [] }
|
30
33
|
@statistics = { busy: 0, enqueued: 0 }
|
31
34
|
|
@@ -67,7 +70,7 @@ module Karafka
|
|
67
70
|
# Assume that moving to queue means being picked up immediately not to create stats
|
68
71
|
# race conditions because of pop overhead. If there are workers available, we assume
|
69
72
|
# work is going to be handled as we never reject enqueued jobs
|
70
|
-
if @statistics[:busy] <
|
73
|
+
if @statistics[:busy] < concurrency
|
71
74
|
@statistics[:busy] += 1
|
72
75
|
else
|
73
76
|
# If system is fully loaded, it means this job is indeed enqueued
|
@@ -155,12 +158,14 @@ module Karafka
|
|
155
158
|
# the work to be finished.
|
156
159
|
# @note This method is blocking.
|
157
160
|
def wait(group_id)
|
161
|
+
interval_in_seconds = tick_interval / 1_000.0
|
162
|
+
|
158
163
|
# Go doing other things while we cannot process and wait for anyone to finish their work
|
159
164
|
# and re-check the wait status
|
160
165
|
while wait?(group_id)
|
161
166
|
yield if block_given?
|
162
167
|
|
163
|
-
@semaphores.fetch(group_id).pop(timeout:
|
168
|
+
@semaphores.fetch(group_id).pop(timeout: interval_in_seconds)
|
164
169
|
end
|
165
170
|
end
|
166
171
|
|
@@ -34,7 +34,7 @@ module Karafka
|
|
34
34
|
# We can commit the offset here because we know that we skip it "forever" and
|
35
35
|
# since AJ consumer commits the offset after each job, we also know that the
|
36
36
|
# previous job was successful
|
37
|
-
|
37
|
+
mark_dispatched_to_dlq(skippable_message)
|
38
38
|
pause(coordinator.seek_offset, nil, false)
|
39
39
|
end
|
40
40
|
end
|
@@ -120,7 +120,7 @@ module Karafka
|
|
120
120
|
raise e
|
121
121
|
ensure
|
122
122
|
# We need to decrease number of jobs that this coordinator coordinates as it has finished
|
123
|
-
coordinator.decrement
|
123
|
+
coordinator.decrement(:consume)
|
124
124
|
end
|
125
125
|
|
126
126
|
# Standard flow marks work as consumed and moves on if everything went ok.
|
@@ -147,6 +147,8 @@ module Karafka
|
|
147
147
|
# Code that should run on idle runs without messages available
|
148
148
|
def handle_idle
|
149
149
|
nil
|
150
|
+
ensure
|
151
|
+
coordinator.decrement(:idle)
|
150
152
|
end
|
151
153
|
|
152
154
|
# We need to always un-pause the processing in case we have lost a given partition.
|
@@ -161,6 +163,8 @@ module Karafka
|
|
161
163
|
Karafka.monitor.instrument('consumer.revoked', caller: self) do
|
162
164
|
revoked
|
163
165
|
end
|
166
|
+
ensure
|
167
|
+
coordinator.decrement(:revoked)
|
164
168
|
end
|
165
169
|
|
166
170
|
# Runs the shutdown code
|
@@ -169,6 +173,8 @@ module Karafka
|
|
169
173
|
Karafka.monitor.instrument('consumer.shutdown', caller: self) do
|
170
174
|
shutdown
|
171
175
|
end
|
176
|
+
ensure
|
177
|
+
coordinator.decrement(:shutdown)
|
172
178
|
end
|
173
179
|
end
|
174
180
|
end
|
@@ -76,7 +76,7 @@ module Karafka
|
|
76
76
|
dispatch_to_dlq(skippable_message)
|
77
77
|
|
78
78
|
# We mark the broken message as consumed and move on
|
79
|
-
|
79
|
+
mark_dispatched_to_dlq(skippable_message)
|
80
80
|
|
81
81
|
return if revoked?
|
82
82
|
|
@@ -106,7 +106,8 @@ module Karafka
|
|
106
106
|
# @param skippable_message [Karafka::Messages::Message] message we are skipping that also
|
107
107
|
# should go to the dlq topic
|
108
108
|
def dispatch_to_dlq(skippable_message)
|
109
|
-
producer.
|
109
|
+
producer.public_send(
|
110
|
+
topic.dead_letter_queue.dispatch_method,
|
110
111
|
topic: topic.dead_letter_queue.topic,
|
111
112
|
payload: skippable_message.raw_payload
|
112
113
|
)
|
@@ -118,6 +119,20 @@ module Karafka
|
|
118
119
|
message: skippable_message
|
119
120
|
)
|
120
121
|
end
|
122
|
+
|
123
|
+
# Marks message that went to DLQ (if applicable) based on the requested method
|
124
|
+
# @param skippable_message [Karafka::Messages::Message]
|
125
|
+
def mark_dispatched_to_dlq(skippable_message)
|
126
|
+
case topic.dead_letter_queue.marking_method
|
127
|
+
when :mark_as_consumed
|
128
|
+
mark_as_consumed(skippable_message)
|
129
|
+
when :mark_as_consumed!
|
130
|
+
mark_as_consumed!(skippable_message)
|
131
|
+
else
|
132
|
+
# This should never happen. Bug if encountered. Please report
|
133
|
+
raise Karafka::Errors::UnsupportedCaseError
|
134
|
+
end
|
135
|
+
end
|
121
136
|
end
|
122
137
|
end
|
123
138
|
end
|
@@ -5,11 +5,14 @@ module Karafka
|
|
5
5
|
# Abstraction layer around workers batch.
|
6
6
|
class WorkersBatch
|
7
7
|
include Enumerable
|
8
|
+
include Helpers::ConfigImporter.new(
|
9
|
+
concurrency: %i[concurrency]
|
10
|
+
)
|
8
11
|
|
9
12
|
# @param jobs_queue [JobsQueue]
|
10
13
|
# @return [WorkersBatch]
|
11
14
|
def initialize(jobs_queue)
|
12
|
-
@batch = Array.new(
|
15
|
+
@batch = Array.new(concurrency) { Processing::Worker.new(jobs_queue) }
|
13
16
|
end
|
14
17
|
|
15
18
|
# Iterates over available workers and yields each worker
|
@@ -14,6 +14,10 @@ module Karafka
|
|
14
14
|
# end
|
15
15
|
# end
|
16
16
|
class Builder < Array
|
17
|
+
include Helpers::ConfigImporter.new(
|
18
|
+
default_group_id: %i[group_id]
|
19
|
+
)
|
20
|
+
|
17
21
|
# Empty default per-topic config
|
18
22
|
EMPTY_DEFAULTS = ->(_) {}.freeze
|
19
23
|
|
@@ -116,7 +120,7 @@ module Karafka
|
|
116
120
|
**args,
|
117
121
|
&block
|
118
122
|
)
|
119
|
-
consumer_group(
|
123
|
+
consumer_group(default_group_id) do
|
120
124
|
target.public_send(
|
121
125
|
:subscription_group=,
|
122
126
|
subscription_group_name.to_s,
|
@@ -132,7 +136,7 @@ module Karafka
|
|
132
136
|
# @param topic_name [String, Symbol] name of a topic from which we want to consumer
|
133
137
|
# @param block [Proc] proc we want to evaluate in the topic context
|
134
138
|
def topic(topic_name, &block)
|
135
|
-
consumer_group(
|
139
|
+
consumer_group(default_group_id) do
|
136
140
|
topic(topic_name, &block)
|
137
141
|
end
|
138
142
|
end
|
@@ -22,7 +22,8 @@ module Karafka
|
|
22
22
|
# kafka and don't understand the concept of consumer groups.
|
23
23
|
def initialize(name)
|
24
24
|
@name = name.to_s
|
25
|
-
|
25
|
+
# This used to be different when consumer mappers existed but now it is the same
|
26
|
+
@id = @name
|
26
27
|
@topics = Topics.new([])
|
27
28
|
# Initialize the subscription group so there's always a value for it, since even if not
|
28
29
|
# defined directly, a subscription group will be created
|
@@ -17,6 +17,11 @@ module Karafka
|
|
17
17
|
:transactional,
|
18
18
|
# Strategy to apply (if strategies supported)
|
19
19
|
:strategy,
|
20
|
+
# Should we use `#produce_sync` or `#produce_async`
|
21
|
+
:dispatch_method,
|
22
|
+
# Should we use `#mark_as_consumed` or `#mark_as_consumed!` (in flows that mark)
|
23
|
+
:marking_method,
|
24
|
+
# Initialize with kwargs
|
20
25
|
keyword_init: true
|
21
26
|
) do
|
22
27
|
alias_method :active?, :active
|
@@ -21,6 +21,14 @@ module Karafka
|
|
21
21
|
required(:independent) { |val| [true, false].include?(val) }
|
22
22
|
required(:max_retries) { |val| val.is_a?(Integer) && val >= 0 }
|
23
23
|
required(:transactional) { |val| [true, false].include?(val) }
|
24
|
+
|
25
|
+
required(:dispatch_method) do |val|
|
26
|
+
%i[produce_async produce_sync].include?(val)
|
27
|
+
end
|
28
|
+
|
29
|
+
required(:marking_method) do |val|
|
30
|
+
%i[mark_as_consumed mark_as_consumed!].include?(val)
|
31
|
+
end
|
24
32
|
end
|
25
33
|
|
26
34
|
# Validate topic name only if dlq is active
|
@@ -18,19 +18,27 @@ module Karafka
|
|
18
18
|
# in a retry flow to reset the errors counter
|
19
19
|
# @param transactional [Boolean] if applicable, should transaction be used to move
|
20
20
|
# given message to the dead-letter topic and mark it as consumed.
|
21
|
+
# @param dispatch_method [Symbol] `:produce_async` or `:produce_sync`. Describes
|
22
|
+
# whether dispatch on dlq should be sync or async (async by default)
|
23
|
+
# @param marking_method [Symbol] `:mark_as_consumed` or `:mark_as_consumed!`. Describes
|
24
|
+
# whether marking on DLQ should be async or sync (async by default)
|
21
25
|
# @return [Config] defined config
|
22
26
|
def dead_letter_queue(
|
23
27
|
max_retries: DEFAULT_MAX_RETRIES,
|
24
28
|
topic: nil,
|
25
29
|
independent: false,
|
26
|
-
transactional: true
|
30
|
+
transactional: true,
|
31
|
+
dispatch_method: :produce_async,
|
32
|
+
marking_method: :mark_as_consumed
|
27
33
|
)
|
28
34
|
@dead_letter_queue ||= Config.new(
|
29
35
|
active: !topic.nil?,
|
30
36
|
max_retries: max_retries,
|
31
37
|
topic: topic,
|
32
38
|
independent: independent,
|
33
|
-
transactional: transactional
|
39
|
+
transactional: transactional,
|
40
|
+
dispatch_method: dispatch_method,
|
41
|
+
marking_method: marking_method
|
34
42
|
)
|
35
43
|
end
|
36
44
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Routing
|
5
|
+
module Features
|
6
|
+
class Deserializers < Base
|
7
|
+
# Config of this feature
|
8
|
+
Config = Struct.new(
|
9
|
+
:active,
|
10
|
+
:payload,
|
11
|
+
:key,
|
12
|
+
:headers,
|
13
|
+
keyword_init: true
|
14
|
+
) { alias_method :active?, :active }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Routing
|
5
|
+
module Features
|
6
|
+
class Deserializers < Base
|
7
|
+
# This feature validation contracts
|
8
|
+
module Contracts
|
9
|
+
# Basic validation of the Kafka expected config details
|
10
|
+
class Topic < Karafka::Contracts::Base
|
11
|
+
configure do |config|
|
12
|
+
config.error_messages = YAML.safe_load(
|
13
|
+
File.read(
|
14
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
|
15
|
+
)
|
16
|
+
).fetch('en').fetch('validations').fetch('topic')
|
17
|
+
end
|
18
|
+
|
19
|
+
nested :deserializers do
|
20
|
+
# Always enabled
|
21
|
+
required(:active) { |val| val == true }
|
22
|
+
required(:payload) { |val| val.respond_to?(:call) }
|
23
|
+
required(:headers) { |val| val.respond_to?(:call) }
|
24
|
+
required(:key) { |val| val.respond_to?(:call) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Routing
|
5
|
+
module Features
|
6
|
+
# Deserializers for all the message details (payload, headers, key)
|
7
|
+
class Deserializers < Base
|
8
|
+
# Routing topic deserializers API. It allows to configure deserializers for various
|
9
|
+
# components of each message.
|
10
|
+
module Topic
|
11
|
+
# Allows for setting all the deserializers with standard defaults
|
12
|
+
# @param payload [Object] Deserializer for the message payload
|
13
|
+
# @param key [Object] deserializer for the message key
|
14
|
+
# @param headers [Object] deserializer for the message headers
|
15
|
+
def deserializers(
|
16
|
+
payload: ::Karafka::Deserializers::Payload.new,
|
17
|
+
key: ::Karafka::Deserializers::Key.new,
|
18
|
+
headers: ::Karafka::Deserializers::Headers.new
|
19
|
+
)
|
20
|
+
@deserializers ||= Config.new(
|
21
|
+
active: true,
|
22
|
+
payload: payload,
|
23
|
+
key: key,
|
24
|
+
headers: headers
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Supports pre 2.4 format where only payload deserializer could be defined. We do not
|
29
|
+
# retire this format because it is not bad when users do not do anything advanced with
|
30
|
+
# key or headers
|
31
|
+
# @param payload [Object] payload deserializer
|
32
|
+
def deserializer(payload)
|
33
|
+
deserializers(payload: payload)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Boolean] Deserializers are always active
|
37
|
+
def deserializers?
|
38
|
+
deserializers.active?
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Hash] topic setup hash
|
42
|
+
def to_h
|
43
|
+
super.merge(
|
44
|
+
deserializers: deserializers.to_h
|
45
|
+
).freeze
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -18,22 +18,17 @@ module Karafka
|
|
18
18
|
instance_eval(&defaults) if defaults
|
19
19
|
end
|
20
20
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# Translates the no "=" DSL of routing into elements assignments on target
|
26
|
-
# @param method_name [Symbol] name of the missing method
|
27
|
-
def method_missing(method_name, #{arg_forwarding})
|
28
|
-
return super unless respond_to_missing?(method_name)
|
21
|
+
# Translates the no "=" DSL of routing into elements assignments on target
|
22
|
+
# @param method_name [Symbol] name of the missing method
|
23
|
+
def method_missing(method_name, ...)
|
24
|
+
return super unless respond_to_missing?(method_name)
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
26
|
+
if @target.respond_to?(:"#{method_name}=")
|
27
|
+
@target.public_send(:"#{method_name}=", ...)
|
28
|
+
else
|
29
|
+
@target.public_send(method_name, ...)
|
35
30
|
end
|
36
|
-
|
31
|
+
end
|
37
32
|
|
38
33
|
# Tells whether or not a given element exists on the target
|
39
34
|
# @param method_name [Symbol] name of the missing method
|
@@ -23,7 +23,7 @@ module Karafka
|
|
23
23
|
end
|
24
24
|
|
25
25
|
# Finds the topic by name (in any consumer group) and if not present, will built a new
|
26
|
-
# representation of the topic with the defaults and default
|
26
|
+
# representation of the topic with the defaults and default deserializers.
|
27
27
|
#
|
28
28
|
# This is used in places where we may operate on topics that are not part of the routing
|
29
29
|
# but we want to do something on them (display data, iterate over, etc)
|
@@ -33,7 +33,16 @@ module Karafka
|
|
33
33
|
# @note Please note, that in case of a new topic, it will have a newly built consumer group
|
34
34
|
# as well, that is not part of the routing.
|
35
35
|
def find_or_initialize_by_name(name)
|
36
|
-
find_by(name: name)
|
36
|
+
existing_topic = find_by(name: name)
|
37
|
+
|
38
|
+
return existing_topic if existing_topic
|
39
|
+
|
40
|
+
virtual_topic = Topic.new(name, ConsumerGroup.new(name))
|
41
|
+
|
42
|
+
Karafka::Routing::Proxy.new(
|
43
|
+
virtual_topic,
|
44
|
+
Karafka::App.config.internal.routing.builder.defaults
|
45
|
+
).target
|
37
46
|
end
|
38
47
|
|
39
48
|
module_function :find_by
|
@@ -76,7 +76,8 @@ module Karafka
|
|
76
76
|
activity_manager.active?(:subscription_groups, name)
|
77
77
|
end
|
78
78
|
|
79
|
-
# @return [Array<String>] names of topics to which we should subscribe
|
79
|
+
# @return [false, Array<String>] names of topics to which we should subscribe or false when
|
80
|
+
# operating only on direct assignments
|
80
81
|
#
|
81
82
|
# @note Most of the time it should not include inactive topics but in case of pattern
|
82
83
|
# matching the matcher topics become inactive down the road, hence we filter out so
|
@@ -85,6 +86,13 @@ module Karafka
|
|
85
86
|
topics.select(&:active?).map(&:subscription_name)
|
86
87
|
end
|
87
88
|
|
89
|
+
# @param _consumer [Karafka::Connection::Proxy]
|
90
|
+
# @return [false, Rdkafka::Consumer::TopicPartitionList] List of tpls for direct assignments
|
91
|
+
# or false for the normal mode
|
92
|
+
def assignments(_consumer)
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
88
96
|
# @return [String] id of the subscription group
|
89
97
|
# @note This is an alias for displaying in places where we print the stringified version.
|
90
98
|
def to_s
|
data/lib/karafka/runner.rb
CHANGED
@@ -25,7 +25,7 @@ module Karafka
|
|
25
25
|
# Register all the listeners so they can be started and managed
|
26
26
|
@manager.register(listeners)
|
27
27
|
|
28
|
-
workers.
|
28
|
+
workers.each_with_index { |worker, i| worker.async_call("karafka.worker##{i}") }
|
29
29
|
|
30
30
|
# We aggregate threads here for a supervised shutdown process
|
31
31
|
Karafka::Server.workers = workers
|
data/lib/karafka/setup/config.rb
CHANGED
@@ -64,15 +64,9 @@ module Karafka
|
|
64
64
|
setting :logger, default: ::Karafka::Instrumentation::Logger.new
|
65
65
|
# option monitor [Instance] monitor that we will to use (defaults to Karafka::Monitor)
|
66
66
|
setting :monitor, default: ::Karafka::Instrumentation::Monitor.new
|
67
|
-
# Mapper used to remap consumer groups ids, so in case users migrate from other tools
|
68
|
-
# or they need to maintain their own internal consumer group naming conventions, they
|
69
|
-
# can easily do it, replacing the default client_id + consumer name pattern concept
|
70
|
-
setting :consumer_mapper, default: Routing::ConsumerMapper.new
|
71
67
|
# option [Boolean] should we reload consumers with each incoming batch thus effectively
|
72
68
|
# supporting code reload (if someone reloads code) or should we keep the persistence
|
73
69
|
setting :consumer_persistence, default: true
|
74
|
-
# Default deserializer for converting incoming data into ruby objects
|
75
|
-
setting :deserializer, default: Karafka::Serialization::Json::Deserializer.new
|
76
70
|
# option [String] should we start with the earliest possible offset or latest
|
77
71
|
# This will set the `auto.offset.reset` value unless present in the kafka scope
|
78
72
|
setting :initial_offset, default: 'earliest'
|
@@ -100,6 +94,15 @@ module Karafka
|
|
100
94
|
# Disabling this may be needed in scenarios where we do not have control over topics names
|
101
95
|
# and/or we work with existing systems where we cannot change topics names.
|
102
96
|
setting :strict_topics_namespacing, default: true
|
97
|
+
# option [String] default consumer group name for implicit routing
|
98
|
+
setting :group_id, default: 'app'
|
99
|
+
|
100
|
+
setting :oauth do
|
101
|
+
# option [false, #call] Listener for using oauth bearer. This listener will be able to
|
102
|
+
# get the client name to decide whether to use a single multi-client token refreshing
|
103
|
+
# or have separate tokens per instance.
|
104
|
+
setting :token_provider_listener, default: false
|
105
|
+
end
|
103
106
|
|
104
107
|
# rdkafka default options
|
105
108
|
# @see https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
|
@@ -137,11 +140,12 @@ module Karafka
|
|
137
140
|
# involving a consumer instance
|
138
141
|
'enable.auto.commit': false,
|
139
142
|
# Make sure that topic metadata lookups do not create topics accidentally
|
140
|
-
'allow.auto.create.topics': false
|
143
|
+
'allow.auto.create.topics': false,
|
144
|
+
# Do not store offsets automatically in admin in any way
|
145
|
+
'enable.auto.offset.store': false
|
141
146
|
}
|
142
147
|
|
143
|
-
# option [String] default name for the admin consumer group.
|
144
|
-
# subject to be remapped by the consumer mapper as any other consumer group in the routes
|
148
|
+
# option [String] default name for the admin consumer group.
|
145
149
|
setting :group_id, default: 'karafka_admin'
|
146
150
|
|
147
151
|
# option max_wait_time [Integer] We wait only for this amount of time before raising error
|
@@ -230,6 +234,14 @@ module Karafka
|
|
230
234
|
|
231
235
|
# Settings that are altered by our client proxy layer
|
232
236
|
setting :proxy do
|
237
|
+
# commit offsets request
|
238
|
+
setting :commit do
|
239
|
+
# How many times should we try to run this call before raising an error
|
240
|
+
setting :max_attempts, default: 3
|
241
|
+
# How long should we wait before next attempt in case of a failure
|
242
|
+
setting :wait_time, default: 1_000
|
243
|
+
end
|
244
|
+
|
233
245
|
# Committed offsets for given CG query
|
234
246
|
setting :committed do
|
235
247
|
# timeout for this request. For busy or remote clusters, this should be high enough
|
@@ -259,6 +271,26 @@ module Karafka
|
|
259
271
|
# How long should we wait before next attempt in case of a failure
|
260
272
|
setting :wait_time, default: 1_000
|
261
273
|
end
|
274
|
+
|
275
|
+
# Settings for lag request
|
276
|
+
setting :lag do
|
277
|
+
# timeout for this request. For busy or remote clusters, this should be high enough
|
278
|
+
setting :timeout, default: 10_000
|
279
|
+
# How many times should we try to run this call before raising an error
|
280
|
+
setting :max_attempts, default: 3
|
281
|
+
# How long should we wait before next attempt in case of a failure
|
282
|
+
setting :wait_time, default: 1_000
|
283
|
+
end
|
284
|
+
|
285
|
+
# Settings for metadata request
|
286
|
+
setting :metadata do
|
287
|
+
# timeout for this request. For busy or remote clusters, this should be high enough
|
288
|
+
setting :timeout, default: 10_000
|
289
|
+
# How many times should we try to run this call before raising an error
|
290
|
+
setting :max_attempts, default: 3
|
291
|
+
# How long should we wait before next attempt in case of a failure
|
292
|
+
setting :wait_time, default: 1_000
|
293
|
+
end
|
262
294
|
end
|
263
295
|
end
|
264
296
|
|
@@ -368,10 +400,19 @@ module Karafka
|
|
368
400
|
# Sets up all the components that are based on the user configuration
|
369
401
|
# @note At the moment it is only WaterDrop
|
370
402
|
def configure_components
|
403
|
+
oauth_listener = config.oauth.token_provider_listener
|
404
|
+
# We need to subscribe the oauth listener here because we want it to be ready before
|
405
|
+
# any consumer/admin runs
|
406
|
+
Karafka::App.monitor.subscribe(oauth_listener) if oauth_listener
|
407
|
+
|
371
408
|
config.producer ||= ::WaterDrop::Producer.new do |producer_config|
|
372
409
|
# In some cases WaterDrop updates the config and we don't want our consumer config to
|
373
410
|
# be polluted by those updates, that's why we copy
|
374
411
|
producer_config.kafka = AttributesMap.producer(config.kafka.dup)
|
412
|
+
# We also propagate same listener to the default producer to make sure, that the
|
413
|
+
# listener for oauth is also automatically used by the producer. That way we don't
|
414
|
+
# have to configure it manually for the default producer
|
415
|
+
producer_config.oauth.token_provider_listener = oauth_listener
|
375
416
|
producer_config.logger = config.logger
|
376
417
|
end
|
377
418
|
end
|
data/lib/karafka/status.rb
CHANGED
@@ -3,6 +3,11 @@
|
|
3
3
|
module Karafka
|
4
4
|
# App status monitor
|
5
5
|
class Status
|
6
|
+
include Helpers::ConfigImporter.new(
|
7
|
+
monitor: %i[monitor],
|
8
|
+
conductor: %i[internal connection conductor]
|
9
|
+
)
|
10
|
+
|
6
11
|
# Available states and their transitions.
|
7
12
|
STATES = {
|
8
13
|
initializing: :initialize!,
|
@@ -60,14 +65,8 @@ module Karafka
|
|
60
65
|
# We skip as during this state we do not have yet a monitor
|
61
66
|
return if initializing?
|
62
67
|
|
63
|
-
|
64
|
-
|
65
|
-
# We need to signal conductor on each state change as those may be relevant to
|
66
|
-
# listeners operations
|
67
|
-
@conductor ||= Karafka::App.config.internal.connection.conductor
|
68
|
-
@conductor.signal
|
69
|
-
|
70
|
-
Karafka.monitor.instrument("app.#{state}")
|
68
|
+
conductor.signal
|
69
|
+
monitor.instrument("app.#{state}", caller: self)
|
71
70
|
end
|
72
71
|
end
|
73
72
|
RUBY
|