karafka 1.4.15 → 2.0.0.alpha1
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/FUNDING.yml +3 -0
- data/.github/workflows/ci.yml +74 -24
- data/.ruby-version +1 -1
- data/CHANGELOG.md +38 -39
- data/Gemfile +6 -0
- data/Gemfile.lock +50 -52
- data/LICENSE +14 -0
- data/LICENSE-COMM +89 -0
- data/LICENSE-LGPL +165 -0
- data/README.md +59 -14
- data/bin/benchmarks +85 -0
- data/bin/create_token +28 -0
- data/bin/integrations +160 -0
- data/bin/stress +13 -0
- data/certs/karafka-pro.pem +11 -0
- data/certs/mensfeld.pem +23 -24
- data/config/errors.yml +4 -38
- data/docker-compose.yml +11 -3
- data/karafka.gemspec +10 -20
- data/lib/active_job/consumer.rb +22 -0
- data/lib/active_job/karafka.rb +18 -0
- data/lib/active_job/queue_adapters/karafka_adapter.rb +29 -0
- data/lib/active_job/routing_extensions.rb +15 -0
- data/lib/karafka/app.rb +13 -20
- data/lib/karafka/base_consumer.rb +103 -34
- data/lib/karafka/cli/base.rb +4 -4
- data/lib/karafka/cli/info.rb +43 -8
- data/lib/karafka/cli/install.rb +3 -8
- data/lib/karafka/cli/server.rb +17 -30
- data/lib/karafka/cli.rb +4 -11
- data/lib/karafka/connection/client.rb +279 -93
- data/lib/karafka/connection/listener.rb +137 -38
- data/lib/karafka/connection/messages_buffer.rb +57 -0
- data/lib/karafka/connection/pauses_manager.rb +46 -0
- data/lib/karafka/connection/rebalance_manager.rb +62 -0
- data/lib/karafka/contracts/config.rb +25 -7
- data/lib/karafka/contracts/consumer_group.rb +0 -173
- data/lib/karafka/contracts/consumer_group_topic.rb +17 -7
- data/lib/karafka/contracts/server_cli_options.rb +1 -9
- data/lib/karafka/contracts.rb +1 -1
- data/lib/karafka/env.rb +46 -0
- data/lib/karafka/errors.rb +14 -18
- 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 +42 -0
- data/lib/karafka/instrumentation/monitor.rb +14 -21
- data/lib/karafka/instrumentation/stdout_listener.rb +64 -91
- data/lib/karafka/instrumentation.rb +21 -0
- data/lib/karafka/licenser.rb +65 -0
- data/lib/karafka/{params → messages}/batch_metadata.rb +7 -13
- data/lib/karafka/messages/builders/batch_metadata.rb +30 -0
- data/lib/karafka/messages/builders/message.rb +38 -0
- data/lib/karafka/messages/builders/messages.rb +40 -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/processing/executor.rb +96 -0
- data/lib/karafka/processing/executors_buffer.rb +49 -0
- data/lib/karafka/processing/jobs/base.rb +18 -0
- data/lib/karafka/processing/jobs/consume.rb +28 -0
- data/lib/karafka/processing/jobs/revoked.rb +22 -0
- data/lib/karafka/processing/jobs/shutdown.rb +23 -0
- data/lib/karafka/processing/jobs_queue.rb +121 -0
- data/lib/karafka/processing/worker.rb +57 -0
- data/lib/karafka/processing/workers_batch.rb +22 -0
- data/lib/karafka/railtie.rb +65 -0
- data/lib/karafka/routing/builder.rb +15 -14
- data/lib/karafka/routing/consumer_group.rb +10 -18
- 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 +51 -0
- data/lib/karafka/routing/topic.rb +47 -25
- data/lib/karafka/runner.rb +59 -0
- data/lib/karafka/serialization/json/deserializer.rb +6 -15
- data/lib/karafka/server.rb +62 -25
- data/lib/karafka/setup/config.rb +86 -159
- data/lib/karafka/status.rb +13 -3
- data/lib/karafka/templates/example_consumer.rb.erb +16 -0
- data/lib/karafka/templates/karafka.rb.erb +14 -50
- data/lib/karafka/time_trackers/base.rb +19 -0
- data/lib/karafka/time_trackers/pause.rb +84 -0
- data/lib/karafka/time_trackers/poll.rb +65 -0
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +30 -44
- data.tar.gz.sig +0 -0
- metadata +96 -132
- 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/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
data/lib/karafka/setup/config.rb
CHANGED
@@ -17,14 +17,33 @@ module Karafka
|
|
17
17
|
# Contract for checking the config provided by the user
|
18
18
|
CONTRACT = Karafka::Contracts::Config.new.freeze
|
19
19
|
|
20
|
-
|
20
|
+
# Defaults for kafka settings, that will be overwritten only if not present already
|
21
|
+
KAFKA_DEFAULTS = {
|
22
|
+
'client.id' => 'karafka'
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
private_constant :CONTRACT, :KAFKA_DEFAULTS
|
21
26
|
|
22
27
|
# Available settings
|
28
|
+
|
29
|
+
# Namespace for Pro version related license management. If you use LGPL, no need to worry
|
30
|
+
# about any of this
|
31
|
+
setting :license do
|
32
|
+
# option token [String, false] - license token issued when you acquire a Pro license
|
33
|
+
# Leave false if using the LGPL version and all is going to work just fine :)
|
34
|
+
#
|
35
|
+
# @note By using the commercial components, you accept the LICENSE-COMM commercial license
|
36
|
+
# terms and conditions
|
37
|
+
setting :token, default: false
|
38
|
+
# option entity [String] for whom we did issue the license
|
39
|
+
setting :entity, default: ''
|
40
|
+
# option expires_on [Date] date when the license expires
|
41
|
+
setting :expires_on, default: Date.parse('2100-01-01')
|
42
|
+
end
|
43
|
+
|
23
44
|
# option client_id [String] kafka client_id - used to provide
|
24
45
|
# default Kafka groups namespaces and identify that app in kafka
|
25
|
-
setting :client_id
|
26
|
-
# What backend do we want to use to process messages
|
27
|
-
setting :backend, default: :inline
|
46
|
+
setting :client_id, default: 'karafka'
|
28
47
|
# option logger [Instance] logger that we want to use
|
29
48
|
setting :logger, default: ::Karafka::Instrumentation::Logger.new
|
30
49
|
# option monitor [Instance] monitor that we will to use (defaults to Karafka::Monitor)
|
@@ -33,147 +52,36 @@ module Karafka
|
|
33
52
|
# or they need to maintain their own internal consumer group naming conventions, they
|
34
53
|
# can easily do it, replacing the default client_id + consumer name pattern concept
|
35
54
|
setting :consumer_mapper, default: Routing::ConsumerMapper.new
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
# - #incoming - for remapping from the incoming message to our internal format
|
40
|
-
# - #outgoing - for remapping from internal topic name into outgoing message
|
41
|
-
setting :topic_mapper, default: Routing::TopicMapper.new
|
42
|
-
# Default serializer for converting whatever we want to send to kafka to json
|
43
|
-
setting :serializer, default: Karafka::Serialization::Json::Serializer.new
|
55
|
+
# option [Boolean] should we reload consumers with each incoming batch thus effectively
|
56
|
+
# supporting code reload (if someone reloads code) or should we keep the persistence
|
57
|
+
setting :consumer_persistence, default: true
|
44
58
|
# Default deserializer for converting incoming data into ruby objects
|
45
59
|
setting :deserializer, default: Karafka::Serialization::Json::Deserializer.new
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
# longer wait for the consumers to stop gracefully but instead we force terminate
|
60
|
+
# option [Boolean] should we leave offset management to the user
|
61
|
+
setting :manual_offset_management, default: false
|
62
|
+
# options max_messages [Integer] how many messages do we want to fetch from Kafka in one go
|
63
|
+
setting :max_messages, default: 100_000
|
64
|
+
# option [Integer] number of milliseconds we can wait while fetching data
|
65
|
+
setting :max_wait_time, default: 10_000
|
66
|
+
# option shutdown_timeout [Integer] the number of milliseconds after which Karafka no
|
67
|
+
# longer waits for the consumers to stop gracefully but instead we force terminate
|
55
68
|
# everything.
|
56
|
-
setting :shutdown_timeout, default:
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
setting :pause_max_timeout, default: nil
|
73
|
-
# option pause_exponential_backoff [Boolean] whether to enable exponential backoff
|
74
|
-
setting :pause_exponential_backoff, default: false
|
75
|
-
# option offset_commit_interval [Integer] the interval between offset commits,
|
76
|
-
# in seconds.
|
77
|
-
setting :offset_commit_interval, default: 10
|
78
|
-
# option offset_commit_threshold [Integer] the number of messages that can be
|
79
|
-
# processed before their offsets are committed. If zero, offset commits are
|
80
|
-
# not triggered by message consumption.
|
81
|
-
setting :offset_commit_threshold, default: 0
|
82
|
-
# option heartbeat_interval [Integer] the interval between heartbeats; must be less
|
83
|
-
# than the session window.
|
84
|
-
setting :heartbeat_interval, default: 10
|
85
|
-
# option offset_retention_time [Integer] The length of the retention window, known as
|
86
|
-
# offset retention time
|
87
|
-
setting :offset_retention_time, default: nil
|
88
|
-
# option fetcher_max_queue_size [Integer] max number of items in the fetch queue that
|
89
|
-
# are stored for further processing. Note, that each item in the queue represents a
|
90
|
-
# response from a single broker
|
91
|
-
setting :fetcher_max_queue_size, default: 10
|
92
|
-
# option assignment_strategy [Object] a strategy determining the assignment of
|
93
|
-
# partitions to the consumers.
|
94
|
-
setting :assignment_strategy, default: Karafka::AssignmentStrategies::RoundRobin.new
|
95
|
-
# option max_bytes_per_partition [Integer] the maximum amount of data fetched
|
96
|
-
# from a single partition at a time.
|
97
|
-
setting :max_bytes_per_partition, default: 1_048_576
|
98
|
-
# whether to consume messages starting at the beginning or to just consume new messages
|
99
|
-
setting :start_from_beginning, default: true
|
100
|
-
# option resolve_seed_brokers [Boolean] whether to resolve each hostname of the seed
|
101
|
-
# brokers
|
102
|
-
setting :resolve_seed_brokers, default: false
|
103
|
-
# option min_bytes [Integer] the minimum number of bytes to read before
|
104
|
-
# returning messages from the server; if `max_wait_time` is reached, this
|
105
|
-
# is ignored.
|
106
|
-
setting :min_bytes, default: 1
|
107
|
-
# option max_bytes [Integer] the maximum number of bytes to read before returning messages
|
108
|
-
# from each broker.
|
109
|
-
setting :max_bytes, default: 10_485_760
|
110
|
-
# option max_wait_time [Integer, Float] max_wait_time is the maximum number of seconds to
|
111
|
-
# wait before returning data from a single message fetch. By setting this high you also
|
112
|
-
# increase the fetching throughput - and by setting it low you set a bound on latency.
|
113
|
-
# This configuration overrides `min_bytes`, so you'll _always_ get data back within the
|
114
|
-
# time specified. The default value is one second. If you want to have at most five
|
115
|
-
# seconds of latency, set `max_wait_time` to 5. You should make sure
|
116
|
-
# max_wait_time * num brokers + heartbeat_interval is less than session_timeout.
|
117
|
-
setting :max_wait_time, default: 1
|
118
|
-
# option automatically_mark_as_consumed [Boolean] should we automatically mark received
|
119
|
-
# messages as consumed (processed) after non-error consumption
|
120
|
-
setting :automatically_mark_as_consumed, default: true
|
121
|
-
# option reconnect_timeout [Integer] How long should we wait before trying to reconnect to
|
122
|
-
# Kafka cluster that went down (in seconds)
|
123
|
-
setting :reconnect_timeout, default: 5
|
124
|
-
# option connect_timeout [Integer] Sets the number of seconds to wait while connecting to
|
125
|
-
# a broker for the first time. When ruby-kafka initializes, it needs to connect to at
|
126
|
-
# least one host.
|
127
|
-
setting :connect_timeout, default: 10
|
128
|
-
# option socket_timeout [Integer] Sets the number of seconds to wait when reading from or
|
129
|
-
# writing to a socket connection to a broker. After this timeout expires the connection
|
130
|
-
# will be killed. Note that some Kafka operations are by definition long-running, such as
|
131
|
-
# waiting for new messages to arrive in a partition, so don't set this value too low
|
132
|
-
setting :socket_timeout, default: 30
|
133
|
-
# option partitioner [Object, nil] the partitioner that should be used by the client
|
134
|
-
setting :partitioner, default: nil
|
135
|
-
|
136
|
-
# SSL authentication related settings
|
137
|
-
# option ca_cert [String, nil] SSL CA certificate
|
138
|
-
setting :ssl_ca_cert, default: nil
|
139
|
-
# option ssl_ca_cert_file_path [String, nil] SSL CA certificate file path
|
140
|
-
setting :ssl_ca_cert_file_path, default: nil
|
141
|
-
# option ssl_ca_certs_from_system [Boolean] Use the CA certs from your system's default
|
142
|
-
# certificate store
|
143
|
-
setting :ssl_ca_certs_from_system, default: false
|
144
|
-
# option ssl_verify_hostname [Boolean] Verify the hostname for client certs
|
145
|
-
setting :ssl_verify_hostname, default: true
|
146
|
-
# option ssl_client_cert [String, nil] SSL client certificate
|
147
|
-
setting :ssl_client_cert, default: nil
|
148
|
-
# option ssl_client_cert_key [String, nil] SSL client certificate password
|
149
|
-
setting :ssl_client_cert_key, default: nil
|
150
|
-
# option sasl_gssapi_principal [String, nil] sasl principal
|
151
|
-
setting :sasl_gssapi_principal, default: nil
|
152
|
-
# option sasl_gssapi_keytab [String, nil] sasl keytab
|
153
|
-
setting :sasl_gssapi_keytab, default: nil
|
154
|
-
# option sasl_plain_authzid [String] The authorization identity to use
|
155
|
-
setting :sasl_plain_authzid, default: ''
|
156
|
-
# option sasl_plain_username [String, nil] The username used to authenticate
|
157
|
-
setting :sasl_plain_username, default: nil
|
158
|
-
# option sasl_plain_password [String, nil] The password used to authenticate
|
159
|
-
setting :sasl_plain_password, default: nil
|
160
|
-
# option sasl_scram_username [String, nil] The username used to authenticate
|
161
|
-
setting :sasl_scram_username, default: nil
|
162
|
-
# option sasl_scram_password [String, nil] The password used to authenticate
|
163
|
-
setting :sasl_scram_password, default: nil
|
164
|
-
# option sasl_scram_mechanism [String, nil] Scram mechanism, either 'sha256' or 'sha512'
|
165
|
-
setting :sasl_scram_mechanism, default: nil
|
166
|
-
# option sasl_over_ssl [Boolean] whether to enforce SSL with SASL
|
167
|
-
setting :sasl_over_ssl, default: true
|
168
|
-
# option ssl_client_cert_chain [String, nil] client cert chain or nil if not used
|
169
|
-
setting :ssl_client_cert_chain, default: nil
|
170
|
-
# option ssl_client_cert_key_password [String, nil] the password required to read
|
171
|
-
# the ssl_client_cert_key
|
172
|
-
setting :ssl_client_cert_key_password, default: nil
|
173
|
-
# @param sasl_oauth_token_provider [Object, nil] OAuthBearer Token Provider instance that
|
174
|
-
# implements method token.
|
175
|
-
setting :sasl_oauth_token_provider, default: nil
|
176
|
-
end
|
69
|
+
setting :shutdown_timeout, default: 60_000
|
70
|
+
# option [Integer] number of threads in which we want to do parallel processing
|
71
|
+
setting :concurrency, default: 5
|
72
|
+
# option [Integer] how long should we wait upon processing error
|
73
|
+
setting :pause_timeout, default: 1_000
|
74
|
+
# option [Integer] what is the max timeout in case of an exponential backoff
|
75
|
+
setting :pause_max_timeout, default: 30_000
|
76
|
+
# option [Boolean] should we use exponential backoff
|
77
|
+
setting :pause_with_exponential_backoff, default: true
|
78
|
+
# option [::WaterDrop::Producer, nil]
|
79
|
+
# Unless configured, will be created once Karafka is configured based on user Karafka setup
|
80
|
+
setting :producer, default: nil
|
81
|
+
|
82
|
+
# rdkafka default options
|
83
|
+
# @see https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
|
84
|
+
setting :kafka, default: {}
|
177
85
|
|
178
86
|
# Namespace for internal settings that should not be modified
|
179
87
|
# It's a temporary step to "declassify" several things internally before we move to a
|
@@ -187,29 +95,37 @@ module Karafka
|
|
187
95
|
# @note In the future, we need to have a single process representation for all the karafka
|
188
96
|
# instances
|
189
97
|
setting :process, default: Process.new
|
190
|
-
# option
|
191
|
-
|
192
|
-
|
193
|
-
# the setup
|
194
|
-
setting :configurators, default: [Configurators::WaterDrop.new]
|
98
|
+
# option subscription_groups_builder [Routing::SubscriptionGroupsBuilder] subscription
|
99
|
+
# group builder
|
100
|
+
setting :subscription_groups_builder, default: Routing::SubscriptionGroupsBuilder.new
|
195
101
|
end
|
196
102
|
|
197
103
|
class << self
|
198
104
|
# Configuring method
|
199
|
-
# @
|
200
|
-
|
201
|
-
|
202
|
-
|
105
|
+
# @param block [Proc] block we want to execute with the config instance
|
106
|
+
def setup(&block)
|
107
|
+
configure(&block)
|
108
|
+
merge_kafka_defaults!(config)
|
109
|
+
validate!
|
110
|
+
|
111
|
+
# Check the license presence (if needed) and
|
112
|
+
Licenser.new.verify(config.license)
|
113
|
+
|
114
|
+
configure_components
|
203
115
|
end
|
204
116
|
|
205
|
-
|
206
|
-
|
207
|
-
#
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
117
|
+
private
|
118
|
+
|
119
|
+
# Propagates the kafka setting defaults unless they are already present
|
120
|
+
# This makes it easier to set some values that users usually don't change but still allows
|
121
|
+
# them to overwrite the whole hash if they want to
|
122
|
+
# @param config [Dry::Configurable::Config] dry config of this producer
|
123
|
+
def merge_kafka_defaults!(config)
|
124
|
+
KAFKA_DEFAULTS.each do |key, value|
|
125
|
+
next if config.kafka.key?(key)
|
126
|
+
|
127
|
+
config.kafka[key] = value
|
128
|
+
end
|
213
129
|
end
|
214
130
|
|
215
131
|
# Validate config based on the config contract
|
@@ -223,6 +139,17 @@ module Karafka
|
|
223
139
|
|
224
140
|
raise Errors::InvalidConfigurationError, validation_result.errors.to_h
|
225
141
|
end
|
142
|
+
|
143
|
+
# Sets up all the components that are based on the user configuration
|
144
|
+
# @note At the moment it is only WaterDrop
|
145
|
+
def configure_components
|
146
|
+
config.producer ||= ::WaterDrop::Producer.new do |producer_config|
|
147
|
+
# In some cases WaterDrop updates the config and we don't want our consumer config to
|
148
|
+
# be polluted by those updates, that's why we copy
|
149
|
+
producer_config.kafka = config.kafka.dup
|
150
|
+
producer_config.logger = config.logger
|
151
|
+
end
|
152
|
+
end
|
226
153
|
end
|
227
154
|
end
|
228
155
|
end
|
data/lib/karafka/status.rb
CHANGED
@@ -3,16 +3,21 @@
|
|
3
3
|
module Karafka
|
4
4
|
# App status monitor
|
5
5
|
class Status
|
6
|
-
# Available states and their transitions
|
6
|
+
# Available states and their transitions.
|
7
7
|
STATES = {
|
8
8
|
initializing: :initialize!,
|
9
|
-
initialized: :initialized!,
|
10
9
|
running: :run!,
|
11
|
-
stopping: :stop
|
10
|
+
stopping: :stop!,
|
11
|
+
stopped: :stopped!
|
12
12
|
}.freeze
|
13
13
|
|
14
14
|
private_constant :STATES
|
15
15
|
|
16
|
+
# By default we are in the initializing state
|
17
|
+
def initialize
|
18
|
+
initialize!
|
19
|
+
end
|
20
|
+
|
16
21
|
STATES.each do |state, transition|
|
17
22
|
define_method :"#{state}?" do
|
18
23
|
@status == state
|
@@ -20,6 +25,11 @@ module Karafka
|
|
20
25
|
|
21
26
|
define_method transition do
|
22
27
|
@status = state
|
28
|
+
|
29
|
+
# Skip on creation (initializing)
|
30
|
+
# We skip as during this state we do not have yet a monitor
|
31
|
+
return if initializing?
|
32
|
+
|
23
33
|
# Trap context disallows to run certain things that we instrument
|
24
34
|
# so the state changes are executed from a separate thread
|
25
35
|
Thread.new { Karafka.monitor.instrument("app.#{state}") }.join
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Example consumer that prints messages payloads
|
4
|
+
class ExampleConsumer < ApplicationConsumer
|
5
|
+
def consume
|
6
|
+
messages.each { |message| puts message.payload }
|
7
|
+
end
|
8
|
+
|
9
|
+
# Run anything upon partition being revoked
|
10
|
+
# def on_revoked
|
11
|
+
# end
|
12
|
+
|
13
|
+
# Define here any teardown things you want when Karafka server stops
|
14
|
+
# def on_shutdown
|
15
|
+
# end
|
16
|
+
end
|
@@ -1,19 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
<% if rails? -%>
|
4
|
-
ENV['RAILS_ENV'] ||= 'development'
|
5
|
-
ENV['KARAFKA_ENV'] = ENV['RAILS_ENV']
|
6
4
|
require ::File.expand_path('../config/environment', __FILE__)
|
7
|
-
Rails.application.eager_load!
|
8
|
-
|
9
|
-
# This lines will make Karafka print to stdout like puma or unicorn
|
10
|
-
if Rails.env.development?
|
11
|
-
Rails.logger.extend(
|
12
|
-
ActiveSupport::Logger.broadcast(
|
13
|
-
ActiveSupport::Logger.new($stdout)
|
14
|
-
)
|
15
|
-
)
|
16
|
-
end
|
17
5
|
<% else -%>
|
18
6
|
# This file is auto-generated during the install process.
|
19
7
|
# If by any chance you've wanted a setup for Rails app, either run the `karafka:install`
|
@@ -31,9 +19,7 @@ APP_LOADER.enable_reloading
|
|
31
19
|
%w[
|
32
20
|
lib
|
33
21
|
app/consumers
|
34
|
-
|
35
|
-
app/workers
|
36
|
-
].each(&APP_LOADER.method(:push_dir))
|
22
|
+
].each { |dir| APP_LOADER.push_dir(dir) }
|
37
23
|
|
38
24
|
APP_LOADER.setup
|
39
25
|
APP_LOADER.eager_load
|
@@ -41,10 +27,12 @@ APP_LOADER.eager_load
|
|
41
27
|
|
42
28
|
class KarafkaApp < Karafka::App
|
43
29
|
setup do |config|
|
44
|
-
config.kafka
|
30
|
+
config.kafka = { 'bootstrap.servers' => '127.0.0.1:9092' }
|
45
31
|
config.client_id = 'example_app'
|
46
32
|
<% if rails? -%>
|
47
|
-
|
33
|
+
# Recreate consumers with each batch. This will allow Rails code reload to work in the
|
34
|
+
# development mode. Otherwise Karafka process would not be aware of code changes
|
35
|
+
config.consumer_persistence = !Rails.env.development?
|
48
36
|
<% end -%>
|
49
37
|
end
|
50
38
|
|
@@ -52,41 +40,17 @@ class KarafkaApp < Karafka::App
|
|
52
40
|
# interested in logging events for certain environments. Since instrumentation
|
53
41
|
# notifications add extra boilerplate, if you want to achieve max performance,
|
54
42
|
# listen to only what you really need for given environment.
|
55
|
-
Karafka.monitor.subscribe(WaterDrop::Instrumentation::StdoutListener.new)
|
56
43
|
Karafka.monitor.subscribe(Karafka::Instrumentation::StdoutListener.new)
|
57
44
|
# Karafka.monitor.subscribe(Karafka::Instrumentation::ProctitleListener.new)
|
58
45
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
# )
|
69
|
-
|
70
|
-
consumer_groups.draw do
|
71
|
-
# topic :example do
|
72
|
-
# consumer ExampleConsumer
|
73
|
-
# end
|
74
|
-
|
75
|
-
# consumer_group :bigger_group do
|
76
|
-
# topic :test do
|
77
|
-
# consumer TestConsumer
|
78
|
-
# end
|
79
|
-
#
|
80
|
-
# topic :test2 do
|
81
|
-
# consumer Test2Consumer
|
82
|
-
# end
|
83
|
-
# end
|
46
|
+
routes.draw do
|
47
|
+
<% if rails? -%>
|
48
|
+
# Uncomment this if you use Karafka with ActiveJob
|
49
|
+
# You ned to define the topic per each queue name you use
|
50
|
+
# active_job_topic :default
|
51
|
+
<% end -%>
|
52
|
+
topic :example do
|
53
|
+
consumer ExampleConsumer
|
54
|
+
end
|
84
55
|
end
|
85
56
|
end
|
86
|
-
|
87
|
-
Karafka.monitor.subscribe('app.initialized') do
|
88
|
-
# Put here all the things you want to do after the Karafka framework
|
89
|
-
# initialization
|
90
|
-
end
|
91
|
-
|
92
|
-
KarafkaApp.boot!
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
# Time trackers module.
|
5
|
+
#
|
6
|
+
# Time trackers are used to track time in context of having a time poll (amount of time
|
7
|
+
# available for processing) or a pausing engine (pause for a time period).
|
8
|
+
module TimeTrackers
|
9
|
+
# Base class for all the time-trackers.
|
10
|
+
class Base
|
11
|
+
private
|
12
|
+
|
13
|
+
# @return [Float] current time in milliseconds
|
14
|
+
def now
|
15
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC) * 1000
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module TimeTrackers
|
5
|
+
# Handles Kafka topic partition pausing and resuming with exponential back-offs.
|
6
|
+
class Pause < Base
|
7
|
+
attr_reader :count
|
8
|
+
|
9
|
+
# @param timeout [Integer] how long should we wait when anything went wrong (in ms)
|
10
|
+
# @param max_timeout [Integer, nil] if exponential is on, what is the max value we can reach
|
11
|
+
# exponentially on which we will stay
|
12
|
+
# @param exponential_backoff [Boolean] should we wait exponentially or with the same
|
13
|
+
# timeout value
|
14
|
+
# @return [Karafka::TimeTrackers::Pause]
|
15
|
+
# @example
|
16
|
+
# pause = Karafka::TimeTrackers::Pause.new(timeout: 1000)
|
17
|
+
# pause.expired? #=> true
|
18
|
+
# pause.paused? #=> false
|
19
|
+
# pause.pause
|
20
|
+
# sleep(1.1)
|
21
|
+
# pause.paused? #=> true
|
22
|
+
# pause.expired? #=> true
|
23
|
+
# pause.count #=> 1
|
24
|
+
# pause.pause
|
25
|
+
# pause.count #=> 1
|
26
|
+
# pause.paused? #=> true
|
27
|
+
# pause.expired? #=> false
|
28
|
+
# pause.resume
|
29
|
+
# pause.count #=> 2
|
30
|
+
# pause.paused? #=> false
|
31
|
+
# pause.reset
|
32
|
+
# pause.count #=> 0
|
33
|
+
def initialize(timeout:, max_timeout:, exponential_backoff:)
|
34
|
+
@started_at = nil
|
35
|
+
@count = 0
|
36
|
+
@timeout = timeout
|
37
|
+
@max_timeout = max_timeout
|
38
|
+
@exponential_backoff = exponential_backoff
|
39
|
+
super()
|
40
|
+
end
|
41
|
+
|
42
|
+
# Pauses the processing from now till the end of the interval (backoff or non-backoff)
|
43
|
+
# and records the count.
|
44
|
+
def pause
|
45
|
+
@started_at = now
|
46
|
+
@ends_at = @started_at + backoff_interval
|
47
|
+
@count += 1
|
48
|
+
end
|
49
|
+
|
50
|
+
# Marks the pause as resumed.
|
51
|
+
def resume
|
52
|
+
@started_at = nil
|
53
|
+
@ends_at = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Boolean] are we paused from processing
|
57
|
+
def paused?
|
58
|
+
!@started_at.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Boolean] did the pause expire
|
62
|
+
def expired?
|
63
|
+
@ends_at ? now >= @ends_at : true
|
64
|
+
end
|
65
|
+
|
66
|
+
# Resets the pause counter.
|
67
|
+
def reset
|
68
|
+
@count = 0
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Computers the exponential backoff
|
74
|
+
# @return [Integer] backoff in milliseconds
|
75
|
+
def backoff_interval
|
76
|
+
backoff_factor = @exponential_backoff ? 2**@count : 1
|
77
|
+
|
78
|
+
timeout = backoff_factor * @timeout
|
79
|
+
|
80
|
+
@max_timeout && timeout > @max_timeout ? @max_timeout : timeout
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module TimeTrackers
|
5
|
+
# Object used to keep track of time we've used running certain operations.
|
6
|
+
#
|
7
|
+
# @example Keep track of sleeping and stop after 3 seconds of 0.1 sleep intervals
|
8
|
+
# time_poll = Poll.new(3000)
|
9
|
+
# time_poll.start
|
10
|
+
#
|
11
|
+
# until time_poll.exceeded?
|
12
|
+
# time_poll.start
|
13
|
+
# puts "I have #{time_poll.remaining.to_i}ms remaining to sleep..."
|
14
|
+
# sleep(0.1)
|
15
|
+
# time_poll.checkpoint
|
16
|
+
# end
|
17
|
+
class Poll < Base
|
18
|
+
attr_reader :remaining, :attempts
|
19
|
+
|
20
|
+
# @param total_time [Integer] amount of milliseconds before we exceed the given time limit
|
21
|
+
# @return [TimeTracker] time poll instance
|
22
|
+
def initialize(total_time)
|
23
|
+
@remaining = total_time
|
24
|
+
@attempts = 0
|
25
|
+
super()
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Boolean] did we exceed the time limit
|
29
|
+
def exceeded?
|
30
|
+
@remaining <= 0
|
31
|
+
end
|
32
|
+
|
33
|
+
# Starts time tracking.
|
34
|
+
def start
|
35
|
+
@attempts += 1
|
36
|
+
@started_at = now
|
37
|
+
end
|
38
|
+
|
39
|
+
# Stops time tracking of a given piece of code and updates the remaining time.
|
40
|
+
def checkpoint
|
41
|
+
@remaining -= (now - @started_at)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Boolean] If anything went wrong, can we retry after a backoff period or not
|
45
|
+
# (do we have enough time)
|
46
|
+
def retryable?
|
47
|
+
remaining > backoff_interval
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sleeps for amount of time matching attempt, so we sleep more with each attempt in case of
|
51
|
+
# a retry.
|
52
|
+
def backoff
|
53
|
+
# Sleep requires seconds not ms
|
54
|
+
sleep(backoff_interval / 1_000.0)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# @return [Integer] milliseconds of the backoff time
|
60
|
+
def backoff_interval
|
61
|
+
100 * attempts
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/karafka/version.rb
CHANGED