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
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Karafka
|
|
4
|
-
# Base responder from which all Karafka responders should inherit
|
|
5
|
-
# Similar to Rails responders concept. It allows us to design flow from one app to another
|
|
6
|
-
# by isolating what responses should be sent (and where) based on a given action
|
|
7
|
-
# It differs from Rails responders in the way it works: in std http request we can have one
|
|
8
|
-
# response, here we can have unlimited number of them
|
|
9
|
-
#
|
|
10
|
-
# It has a simple API for defining where should we respond (and if it is required)
|
|
11
|
-
#
|
|
12
|
-
# @example Basic usage (each registered topic is required to be used by default)
|
|
13
|
-
# class Responder < BaseResponder
|
|
14
|
-
# topic :new_action
|
|
15
|
-
#
|
|
16
|
-
# def respond(data)
|
|
17
|
-
# respond_to :new_action, data
|
|
18
|
-
# end
|
|
19
|
-
# end
|
|
20
|
-
#
|
|
21
|
-
# @example Responding to a topic with extra options
|
|
22
|
-
# class Responder < BaseResponder
|
|
23
|
-
# topic :new_action
|
|
24
|
-
#
|
|
25
|
-
# def respond(data)
|
|
26
|
-
# respond_to :new_action, data, partition_key: 'thing'
|
|
27
|
-
# end
|
|
28
|
-
# end
|
|
29
|
-
#
|
|
30
|
-
# @example Marking topic as not required (we won't have to use it)
|
|
31
|
-
# class Responder < BaseResponder
|
|
32
|
-
# topic :required_topic
|
|
33
|
-
# topic :new_action, required: false
|
|
34
|
-
#
|
|
35
|
-
# def respond(data)
|
|
36
|
-
# respond_to :required_topic, data
|
|
37
|
-
# end
|
|
38
|
-
# end
|
|
39
|
-
#
|
|
40
|
-
# @example Multiple times used topic
|
|
41
|
-
# class Responder < BaseResponder
|
|
42
|
-
# topic :required_topic
|
|
43
|
-
#
|
|
44
|
-
# def respond(data)
|
|
45
|
-
# data.each do |subset|
|
|
46
|
-
# respond_to :required_topic, subset
|
|
47
|
-
# end
|
|
48
|
-
# end
|
|
49
|
-
# end
|
|
50
|
-
#
|
|
51
|
-
# @example Specify serializer for a topic
|
|
52
|
-
# class Responder < BaseResponder
|
|
53
|
-
# topic :xml_topic, serializer: MyXMLSerializer
|
|
54
|
-
#
|
|
55
|
-
# def respond(data)
|
|
56
|
-
# data.each do |subset|
|
|
57
|
-
# respond_to :xml_topic, subset
|
|
58
|
-
# end
|
|
59
|
-
# end
|
|
60
|
-
# end
|
|
61
|
-
#
|
|
62
|
-
# @example Accept multiple arguments to a respond method
|
|
63
|
-
# class Responder < BaseResponder
|
|
64
|
-
# topic :users_actions
|
|
65
|
-
# topic :articles_viewed
|
|
66
|
-
#
|
|
67
|
-
# def respond(user, article)
|
|
68
|
-
# respond_to :users_actions, user
|
|
69
|
-
# respond_to :articles_viewed, article
|
|
70
|
-
# end
|
|
71
|
-
# end
|
|
72
|
-
class BaseResponder
|
|
73
|
-
# Responder usage contract
|
|
74
|
-
CONTRACT = Karafka::Contracts::ResponderUsage.new.freeze
|
|
75
|
-
|
|
76
|
-
private_constant :CONTRACT
|
|
77
|
-
|
|
78
|
-
class << self
|
|
79
|
-
# Definitions of all topics that we want to be able to use in this responder should go here
|
|
80
|
-
attr_accessor :topics
|
|
81
|
-
# Contract that we can use to control and/or require some additional details upon options
|
|
82
|
-
# that are being passed to the producer. This can be in particular useful if we want to make
|
|
83
|
-
# sure that for example partition_key is always present.
|
|
84
|
-
attr_accessor :options_contract
|
|
85
|
-
|
|
86
|
-
# Registers a topic as on to which we will be able to respond
|
|
87
|
-
# @param topic_name [Symbol, String] name of topic to which we want to respond
|
|
88
|
-
# @param options [Hash] hash with optional configuration details
|
|
89
|
-
def topic(topic_name, options = {})
|
|
90
|
-
options[:serializer] ||= Karafka::App.config.serializer
|
|
91
|
-
options[:registered] = true
|
|
92
|
-
self.topics ||= {}
|
|
93
|
-
topic_obj = Responders::Topic.new(topic_name, options)
|
|
94
|
-
self.topics[topic_obj.name] = topic_obj
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# A simple alias for easier standalone responder usage.
|
|
98
|
-
# Instead of building it with new.call it allows (in case of using JSON serializer)
|
|
99
|
-
# to just run it directly from the class level
|
|
100
|
-
# @param data Anything that we want to respond with
|
|
101
|
-
# @example Send user data with a responder
|
|
102
|
-
# UsersCreatedResponder.call(@created_user)
|
|
103
|
-
def call(*data)
|
|
104
|
-
# Just in case there were no topics defined for a responder, we initialize with
|
|
105
|
-
# empty hash not to handle a nil case
|
|
106
|
-
self.topics ||= {}
|
|
107
|
-
new.call(*data)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
attr_reader :messages_buffer
|
|
112
|
-
|
|
113
|
-
# Creates a responder object
|
|
114
|
-
# @return [Karafka::BaseResponder] base responder descendant responder
|
|
115
|
-
def initialize
|
|
116
|
-
@messages_buffer = {}
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Performs respond and validates that all the response requirement were met
|
|
120
|
-
# @param data Anything that we want to respond with
|
|
121
|
-
# @note We know that validators should be executed also before sending data to topics, however
|
|
122
|
-
# the implementation gets way more complicated then, that's why we check after everything
|
|
123
|
-
# was sent using responder
|
|
124
|
-
# @example Send user data with a responder
|
|
125
|
-
# UsersCreatedResponder.new.call(@created_user)
|
|
126
|
-
# @example Send user data with a responder using non default Parser
|
|
127
|
-
# UsersCreatedResponder.new(MyParser).call(@created_user)
|
|
128
|
-
def call(*data)
|
|
129
|
-
respond(*data)
|
|
130
|
-
validate_usage!
|
|
131
|
-
validate_options!
|
|
132
|
-
deliver!
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
private
|
|
136
|
-
|
|
137
|
-
# Checks if we met all the topics requirements. It will fail if we didn't send a message to
|
|
138
|
-
# a registered required topic, etc.
|
|
139
|
-
def validate_usage!
|
|
140
|
-
registered_topics = self.class.topics.map do |name, topic|
|
|
141
|
-
topic.to_h.merge!(
|
|
142
|
-
usage_count: messages_buffer[name]&.count || 0
|
|
143
|
-
)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
used_topics = messages_buffer.map do |name, usage|
|
|
147
|
-
topic = self.class.topics[name] || Responders::Topic.new(name, registered: false)
|
|
148
|
-
topic.to_h.merge!(usage_count: usage.count)
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
result = CONTRACT.call(
|
|
152
|
-
registered_topics: registered_topics,
|
|
153
|
-
used_topics: used_topics
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
return if result.success?
|
|
157
|
-
|
|
158
|
-
raise Karafka::Errors::InvalidResponderUsageError, result.errors.to_h
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# Checks if we met all the options requirements before sending them to the producer.
|
|
162
|
-
def validate_options!
|
|
163
|
-
return true unless self.class.options_contract
|
|
164
|
-
|
|
165
|
-
messages_buffer.each_value do |messages_set|
|
|
166
|
-
messages_set.each do |message_data|
|
|
167
|
-
result = self.class.options_contract.call(message_data.last)
|
|
168
|
-
next if result.success?
|
|
169
|
-
|
|
170
|
-
raise Karafka::Errors::InvalidResponderMessageOptionsError, result.errors.to_h
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Takes all the messages from the buffer and delivers them one by one
|
|
176
|
-
# @note This method is executed after the validation, so we're sure that
|
|
177
|
-
# what we send is legit and it will go to a proper topics
|
|
178
|
-
def deliver!
|
|
179
|
-
messages_buffer.each_value do |data_elements|
|
|
180
|
-
data_elements.each do |data, options|
|
|
181
|
-
# We map this topic name, so it will match namespaced/etc topic in Kafka
|
|
182
|
-
# @note By default will not change topic (if default mapper used)
|
|
183
|
-
mapped_topic = Karafka::App.config.topic_mapper.outgoing(options[:topic])
|
|
184
|
-
external_options = options.merge(topic: mapped_topic)
|
|
185
|
-
producer(options).call(data, external_options)
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# Method that needs to be implemented in a subclass. It should handle responding
|
|
191
|
-
# on registered topics
|
|
192
|
-
# @param _data [Object] anything that we want to use to send to Kafka
|
|
193
|
-
# @raise [NotImplementedError] This method needs to be implemented in a subclass
|
|
194
|
-
def respond(*_data)
|
|
195
|
-
raise NotImplementedError, 'Implement this in a subclass'
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# This method allow us to respond to a single topic with a given data. It can be used
|
|
199
|
-
# as many times as we need. Especially when we have 1:n flow
|
|
200
|
-
# @param topic [Symbol, String] topic to which we want to respond
|
|
201
|
-
# @param data [String, Object] string or object that we want to send
|
|
202
|
-
# @param options [Hash] options for waterdrop (e.g. partition_key).
|
|
203
|
-
# @note Respond to does not accept multiple data arguments.
|
|
204
|
-
def respond_to(topic, data, options = {})
|
|
205
|
-
# We normalize the format to string, as WaterDrop and Ruby-Kafka support only
|
|
206
|
-
# string topics
|
|
207
|
-
topic = topic.to_s
|
|
208
|
-
|
|
209
|
-
messages_buffer[topic] ||= []
|
|
210
|
-
messages_buffer[topic] << [
|
|
211
|
-
self.class.topics[topic].serializer.call(data),
|
|
212
|
-
options.merge(topic: topic)
|
|
213
|
-
]
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
# @param options [Hash] options for waterdrop
|
|
217
|
-
# @return [Class] WaterDrop producer (sync or async based on the settings)
|
|
218
|
-
def producer(options)
|
|
219
|
-
if self.class.topics[options[:topic]].async?
|
|
220
|
-
WaterDrop::AsyncProducer
|
|
221
|
-
else
|
|
222
|
-
WaterDrop::SyncProducer
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|
data/lib/karafka/cli/flow.rb
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Karafka
|
|
4
|
-
# Karafka framework Cli
|
|
5
|
-
class Cli < Thor
|
|
6
|
-
# Description of topics flow (incoming/outgoing)
|
|
7
|
-
class Flow < Base
|
|
8
|
-
desc 'Print application data flow (incoming => outgoing)'
|
|
9
|
-
|
|
10
|
-
# Print out all defined routes in alphabetical order
|
|
11
|
-
def call
|
|
12
|
-
topics.each do |topic|
|
|
13
|
-
any_topics = !topic.responder&.topics.nil?
|
|
14
|
-
log_messages = []
|
|
15
|
-
|
|
16
|
-
if any_topics
|
|
17
|
-
log_messages << "#{topic.name} =>"
|
|
18
|
-
|
|
19
|
-
topic.responder.topics.each_value do |responder_topic|
|
|
20
|
-
features = []
|
|
21
|
-
features << (responder_topic.required? ? 'always' : 'conditionally')
|
|
22
|
-
|
|
23
|
-
log_messages << format(responder_topic.name, "(#{features.join(', ')})")
|
|
24
|
-
end
|
|
25
|
-
else
|
|
26
|
-
log_messages << "#{topic.name} => (nothing)"
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
Karafka.logger.info(log_messages.join("\n"))
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
# @return [Array<Karafka::Routing::Topic>] all topics sorted in alphabetical order
|
|
36
|
-
def topics
|
|
37
|
-
Karafka::App.consumer_groups.map(&:topics).flatten.sort_by(&:name)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Formats a given value with label in a nice way
|
|
41
|
-
# @param label [String] label describing value
|
|
42
|
-
# @param value [String] value that should be printed
|
|
43
|
-
def format(label, value)
|
|
44
|
-
" - #{label}: #{value}"
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Karafka
|
|
4
|
-
class Cli < Thor
|
|
5
|
-
# Command that gets invoked when no method is provided when running the CLI
|
|
6
|
-
# It allows us to exit with exit code 1 instead of default 0 to indicate that something
|
|
7
|
-
# was missing
|
|
8
|
-
# @see https://github.com/karafka/karafka/issues/619
|
|
9
|
-
class Missingno < Base
|
|
10
|
-
desc 'Hidden command that gets invoked when no command is provided', hide: true
|
|
11
|
-
|
|
12
|
-
# Prints an error about the lack of command (nothing selected)
|
|
13
|
-
def call
|
|
14
|
-
Karafka.logger.error('No command provided')
|
|
15
|
-
exit 1
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Karafka
|
|
4
|
-
# Special type of a listener, that is not an instrumentation one, but one that triggers
|
|
5
|
-
# code reload in the development mode after each fetched batch (or message)
|
|
6
|
-
#
|
|
7
|
-
# Please refer to the development code reload sections for details on the benefits and downsides
|
|
8
|
-
# of the in-process code reloading
|
|
9
|
-
class CodeReloader
|
|
10
|
-
# This mutex is needed as we might have an application that has multiple consumer groups
|
|
11
|
-
# running in separate threads and we should not trigger reload before fully reloading the app
|
|
12
|
-
# in previous thread
|
|
13
|
-
MUTEX = Mutex.new
|
|
14
|
-
|
|
15
|
-
private_constant :MUTEX
|
|
16
|
-
|
|
17
|
-
# @param reloaders [Array<Object>] any code loaders that we use in this app. Whether it is
|
|
18
|
-
# the Rails loader, Zeitwerk or anything else that allows reloading triggering
|
|
19
|
-
# @param block [Proc] yields given block just before reloading. This can be used to hook custom
|
|
20
|
-
# reloading stuff, that ain't reloaders (for example for resetting dry-events registry)
|
|
21
|
-
def initialize(*reloaders, &block)
|
|
22
|
-
@reloaders = reloaders
|
|
23
|
-
@block = block
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Binds to the instrumentation events and triggers reload
|
|
27
|
-
# @param _event [Dry::Event] empty dry event
|
|
28
|
-
# @note Since we de-register all the user defined objects and redraw routes, it means that
|
|
29
|
-
# we won't be able to do a multi-batch buffering in the development mode as each of the
|
|
30
|
-
# batches will be buffered on a newly created "per fetch" instance.
|
|
31
|
-
def on_connection_listener_fetch_loop(_event)
|
|
32
|
-
reload
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
# Triggers reload of both standard and Rails reloaders as well as expires all internals of
|
|
38
|
-
# Karafka, so it can be rediscovered and rebuilt
|
|
39
|
-
def reload
|
|
40
|
-
MUTEX.synchronize do
|
|
41
|
-
if @reloaders[0].respond_to?(:execute)
|
|
42
|
-
reload_with_rails
|
|
43
|
-
else
|
|
44
|
-
reload_without_rails
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Rails reloading procedure
|
|
50
|
-
def reload_with_rails
|
|
51
|
-
updatable = @reloaders.select(&:updated?)
|
|
52
|
-
|
|
53
|
-
return if updatable.empty?
|
|
54
|
-
|
|
55
|
-
updatable.each(&:execute)
|
|
56
|
-
@block&.call
|
|
57
|
-
Karafka::App.reload
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Zeitwerk and other reloaders
|
|
61
|
-
def reload_without_rails
|
|
62
|
-
@reloaders.each(&:reload)
|
|
63
|
-
@block&.call
|
|
64
|
-
Karafka::App.reload
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Karafka
|
|
4
|
-
# Namespace for all the things related to Kafka connection
|
|
5
|
-
module Connection
|
|
6
|
-
# Mapper used to convert our internal settings into ruby-kafka settings based on their
|
|
7
|
-
# API requirements.
|
|
8
|
-
# Since ruby-kafka has more and more options and there are few "levels" on which
|
|
9
|
-
# we have to apply them (despite the fact, that in Karafka you configure all of it
|
|
10
|
-
# in one place), we have to remap it into what ruby-kafka driver requires
|
|
11
|
-
# @note The good thing about Kafka.new method is that it ignores all options that
|
|
12
|
-
# do nothing. So we don't have to worry about injecting our internal settings
|
|
13
|
-
# into the client and breaking stuff
|
|
14
|
-
module ApiAdapter
|
|
15
|
-
class << self
|
|
16
|
-
# Builds all the configuration settings for Kafka.new method
|
|
17
|
-
# @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
|
|
18
|
-
# @return [Array<Hash>] Array with all the client arguments including hash with all
|
|
19
|
-
# the settings required by Kafka.new method
|
|
20
|
-
# @note We return array, so we can inject any arguments we want, in case of changes in the
|
|
21
|
-
# raw driver
|
|
22
|
-
def client(consumer_group)
|
|
23
|
-
# This one is a default that takes all the settings except special
|
|
24
|
-
# cases defined in the map
|
|
25
|
-
settings = {
|
|
26
|
-
logger: ::Karafka.logger,
|
|
27
|
-
client_id: ::Karafka::App.config.client_id
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
kafka_configs.each_key do |setting_name|
|
|
31
|
-
# All options for config adapter should be ignored as we're just interested
|
|
32
|
-
# in what is left, as we want to pass all the options that are "typical"
|
|
33
|
-
# and not listed in the api_adapter special cases mapping. All the values
|
|
34
|
-
# from the api_adapter mapping go somewhere else, not to the client directly
|
|
35
|
-
next if AttributesMap.api_adapter.values.flatten.include?(setting_name)
|
|
36
|
-
|
|
37
|
-
# Settings for each consumer group are either defined per consumer group or are
|
|
38
|
-
# inherited from the global/general settings level, thus we don't have to fetch them
|
|
39
|
-
# from the kafka settings as they are already on a consumer group level
|
|
40
|
-
settings[setting_name] = consumer_group.public_send(setting_name)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
settings_hash = sanitize(settings)
|
|
44
|
-
|
|
45
|
-
# Normalization for the way Kafka::Client accepts arguments from 0.5.3
|
|
46
|
-
[settings_hash.delete(:seed_brokers), settings_hash]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Builds all the configuration settings for kafka#consumer method
|
|
50
|
-
# @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
|
|
51
|
-
# @return [Hash] all the consumer keyword arguments including hash with all
|
|
52
|
-
# the settings required by Kafka#consumer
|
|
53
|
-
def consumer(consumer_group)
|
|
54
|
-
settings = { group_id: consumer_group.id }
|
|
55
|
-
settings = fetch_for(:consumer, consumer_group, settings)
|
|
56
|
-
sanitize(settings)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Builds all the configuration settings for kafka consumer consume_each_batch and
|
|
60
|
-
# consume_each_message methods
|
|
61
|
-
# @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
|
|
62
|
-
# @return [Hash] hash with all the arguments required by consuming method
|
|
63
|
-
# including all the settings required by
|
|
64
|
-
# Kafka::Consumer#consume_each_message and Kafka::Consumer#consume_each_batch method
|
|
65
|
-
def consumption(consumer_group)
|
|
66
|
-
sanitize(
|
|
67
|
-
fetch_for(
|
|
68
|
-
:consumption,
|
|
69
|
-
consumer_group,
|
|
70
|
-
automatically_mark_as_processed: consumer_group.automatically_mark_as_consumed
|
|
71
|
-
)
|
|
72
|
-
)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Builds all the configuration settings for kafka consumer#subscribe method
|
|
76
|
-
# @param topic [Karafka::Routing::Topic] topic that holds details for a given subscription
|
|
77
|
-
# @return [Hash] hash with all the settings required by kafka consumer#subscribe method
|
|
78
|
-
def subscribe(topic)
|
|
79
|
-
settings = fetch_for(:subscribe, topic)
|
|
80
|
-
[Karafka::App.config.topic_mapper.outgoing(topic.name), sanitize(settings)]
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Builds all the configuration settings required by kafka consumer#pause method
|
|
84
|
-
# @param topic [String] topic that we want to pause
|
|
85
|
-
# @param partition [Integer] number partition that we want to pause
|
|
86
|
-
# @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
|
|
87
|
-
# @return [Hash] hash with all the details required to pause kafka consumer
|
|
88
|
-
def pause(topic, partition, consumer_group)
|
|
89
|
-
{
|
|
90
|
-
args: [Karafka::App.config.topic_mapper.outgoing(topic), partition],
|
|
91
|
-
kwargs: {
|
|
92
|
-
timeout: consumer_group.pause_timeout,
|
|
93
|
-
max_timeout: consumer_group.pause_max_timeout,
|
|
94
|
-
exponential_backoff: consumer_group.pause_exponential_backoff
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Remaps topic details taking the topic mapper feature into consideration.
|
|
100
|
-
# @param params [Karafka::Params::Params] params instance
|
|
101
|
-
# @return [Array] array with all the details needed by ruby-kafka to mark message
|
|
102
|
-
# as processed
|
|
103
|
-
# @note When default empty topic mapper is used, no need for any conversion as the
|
|
104
|
-
# internal and external format are exactly the same
|
|
105
|
-
def mark_message_as_processed(params)
|
|
106
|
-
# Majority of users don't use custom topic mappers. No need to change anything when it
|
|
107
|
-
# is a default mapper that does not change anything. Only some cloud providers require
|
|
108
|
-
# topics to be remapped
|
|
109
|
-
return [params.metadata] if Karafka::App.config.topic_mapper.is_a?(
|
|
110
|
-
Karafka::Routing::TopicMapper
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
# @note We don't use tap as it is around 13% slower than non-dup version
|
|
114
|
-
dupped = params.metadata.dup
|
|
115
|
-
dupped['topic'] = Karafka::App.config.topic_mapper.outgoing(params.metadata.topic)
|
|
116
|
-
[dupped]
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
private
|
|
120
|
-
|
|
121
|
-
# Fetches proper settings for a given map namespace
|
|
122
|
-
# @param namespace_key [Symbol] namespace from attributes map config adapter hash
|
|
123
|
-
# @param route_layer [Object] route topic or consumer group
|
|
124
|
-
# @param preexisting_settings [Hash] hash with some preexisting settings that might have
|
|
125
|
-
# been loaded in a different way
|
|
126
|
-
def fetch_for(namespace_key, route_layer, preexisting_settings = {})
|
|
127
|
-
kafka_configs.each_key do |setting_name|
|
|
128
|
-
# Ignore settings that are not related to our namespace
|
|
129
|
-
next unless AttributesMap.api_adapter[namespace_key].include?(setting_name)
|
|
130
|
-
|
|
131
|
-
# Ignore settings that are already initialized
|
|
132
|
-
# In case they are in preexisting settings fetched differently
|
|
133
|
-
next if preexisting_settings.key?(setting_name)
|
|
134
|
-
|
|
135
|
-
# Fetch all the settings from a given layer object. Objects can handle the fallback
|
|
136
|
-
# to the kafka settings, so
|
|
137
|
-
preexisting_settings[setting_name] = route_layer.send(setting_name)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
preexisting_settings
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# Removes nil containing keys from the final settings so it can use Kafkas driver
|
|
144
|
-
# defaults for those
|
|
145
|
-
# @param settings [Hash] settings that may contain nil values
|
|
146
|
-
# @return [Hash] settings without nil using keys (non of karafka options should be nil)
|
|
147
|
-
def sanitize(settings)
|
|
148
|
-
settings.reject { |_key, value| value.nil? }
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# @return [Hash] Kafka config details as a hash
|
|
152
|
-
def kafka_configs
|
|
153
|
-
::Karafka::App.config.kafka.to_h
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Karafka
|
|
4
|
-
module Connection
|
|
5
|
-
# Class that delegates processing of batch received messages for which we listen to
|
|
6
|
-
# a proper processor
|
|
7
|
-
module BatchDelegator
|
|
8
|
-
class << self
|
|
9
|
-
# Delegates messages (does something with them)
|
|
10
|
-
# It will either schedule or run a proper processor action for messages
|
|
11
|
-
# @param group_id [String] group_id of a group from which a given message came
|
|
12
|
-
# @param kafka_batch [<Kafka::FetchedBatch>] raw messages fetched batch
|
|
13
|
-
# @note This should be looped to obtain a constant delegating of new messages
|
|
14
|
-
def call(group_id, kafka_batch)
|
|
15
|
-
topic = Persistence::Topics.fetch(group_id, kafka_batch.topic)
|
|
16
|
-
consumer = Persistence::Consumers.fetch(topic, kafka_batch.partition)
|
|
17
|
-
|
|
18
|
-
Karafka.monitor.instrument(
|
|
19
|
-
'connection.batch_delegator.call',
|
|
20
|
-
caller: self,
|
|
21
|
-
consumer: consumer,
|
|
22
|
-
kafka_batch: kafka_batch
|
|
23
|
-
) do
|
|
24
|
-
# Due to how ruby-kafka is built, we have the metadata that is stored on the batch
|
|
25
|
-
# level only available for batch consuming
|
|
26
|
-
consumer.batch_metadata = Params::Builders::BatchMetadata.from_kafka_batch(
|
|
27
|
-
kafka_batch,
|
|
28
|
-
topic
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
kafka_messages = kafka_batch.messages
|
|
32
|
-
|
|
33
|
-
# Depending on a case (persisted or not) we might use new consumer instance per
|
|
34
|
-
# each batch, or use the same one for all of them (for implementing buffering, etc.)
|
|
35
|
-
if topic.batch_consuming
|
|
36
|
-
consumer.params_batch = Params::Builders::ParamsBatch.from_kafka_messages(
|
|
37
|
-
kafka_messages,
|
|
38
|
-
topic
|
|
39
|
-
)
|
|
40
|
-
consumer.call
|
|
41
|
-
else
|
|
42
|
-
kafka_messages.each do |kafka_message|
|
|
43
|
-
consumer.params_batch = Params::Builders::ParamsBatch.from_kafka_messages(
|
|
44
|
-
[kafka_message],
|
|
45
|
-
topic
|
|
46
|
-
)
|
|
47
|
-
consumer.call
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Karafka
|
|
4
|
-
module Connection
|
|
5
|
-
# Builder used to construct Kafka client
|
|
6
|
-
module Builder
|
|
7
|
-
class << self
|
|
8
|
-
# Builds a Kafka::Client instance that we use to work with Kafka cluster
|
|
9
|
-
# @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group for which we want
|
|
10
|
-
# to have a new Kafka client
|
|
11
|
-
# @return [::Kafka::Client] returns a Kafka client
|
|
12
|
-
def call(consumer_group)
|
|
13
|
-
settings = ApiAdapter.client(consumer_group)
|
|
14
|
-
|
|
15
|
-
Kafka.new(
|
|
16
|
-
settings[0],
|
|
17
|
-
**settings[1]
|
|
18
|
-
)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Karafka
|
|
4
|
-
module Connection
|
|
5
|
-
# Class that delegates processing of a single received message for which we listen to
|
|
6
|
-
# a proper processor
|
|
7
|
-
module MessageDelegator
|
|
8
|
-
class << self
|
|
9
|
-
# Delegates message (does something with it)
|
|
10
|
-
# It will either schedule or run a proper processor action for the incoming message
|
|
11
|
-
# @param group_id [String] group_id of a group from which a given message came
|
|
12
|
-
# @param kafka_message [<Kafka::FetchedMessage>] raw message from kafka
|
|
13
|
-
# @note This should be looped to obtain a constant delegating of new messages
|
|
14
|
-
def call(group_id, kafka_message)
|
|
15
|
-
topic = Persistence::Topics.fetch(group_id, kafka_message.topic)
|
|
16
|
-
consumer = Persistence::Consumers.fetch(topic, kafka_message.partition)
|
|
17
|
-
|
|
18
|
-
Karafka.monitor.instrument(
|
|
19
|
-
'connection.message_delegator.call',
|
|
20
|
-
caller: self,
|
|
21
|
-
consumer: consumer,
|
|
22
|
-
kafka_message: kafka_message
|
|
23
|
-
) do
|
|
24
|
-
# @note We always get a single message within single delegator, which means that
|
|
25
|
-
# we don't care if user marked it as a batch consumed or not.
|
|
26
|
-
consumer.params_batch = Params::Builders::ParamsBatch.from_kafka_messages(
|
|
27
|
-
[kafka_message],
|
|
28
|
-
topic
|
|
29
|
-
)
|
|
30
|
-
consumer.call
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|