ruby-kafka 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9845964409d385d935b55e4ded95f210721fe7ce
4
- data.tar.gz: d7bebf3d0222a80c0fe95ebec4d83b992abe306d
3
+ metadata.gz: 5c001c97f73b001e170f23d7fd970781aa6bb196
4
+ data.tar.gz: 39f0b03b56ad97eee788fac63ec467599ad8cd8e
5
5
  SHA512:
6
- metadata.gz: 5656f9723720414c954a7087385d52d9618322d31e70f9600bd4b8f34bb76432a31fb0bc88bc6baf5506fe518c47a7a5c9cb0134c91f224acdaa6ef76bbd8fce
7
- data.tar.gz: 042d3a1bf5f8847e9b50a93e2bc8b151e78c4fac463e999e32e9d0acd7f1d5498ebb301d166a8a255adcc2efd15f7bd53de6397348a94822f2462c626c49741f
6
+ metadata.gz: 6f9312f25ab7493d5803b581b22af270399f0f5ff2fbf4012da1f4a30525c430fb1fa21085cb4042aa4b59074a0abe3af9038ee6e97f0b48770baa50eaab6393
7
+ data.tar.gz: 7523013dbc08d7a193faf6b2b2419a6704cb4b7918f9ed993545b534e356084a765a1c8cfba937a55d7236c26985e3cf252cbe50df6c3c98a45e1bc47e32caad
data/CHANGELOG.md CHANGED
@@ -4,6 +4,8 @@ Changes and additions to the library will be listed here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## v0.4.1
8
+
7
9
  - Allow seeking the consumer position (#386).
8
10
  - Reopen idle connections after 5 minutes (#399).
9
11
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-kafka (0.4.0)
4
+ ruby-kafka (0.4.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -89,4 +89,4 @@ RUBY VERSION
89
89
  ruby 2.2.3p173
90
90
 
91
91
  BUNDLED WITH
92
- 1.14.6
92
+ 1.15.3
@@ -83,6 +83,8 @@ module Kafka
83
83
  queue: @queue,
84
84
  producer: sync_producer,
85
85
  delivery_threshold: delivery_threshold,
86
+ instrumenter: instrumenter,
87
+ logger: logger,
86
88
  )
87
89
 
88
90
  # The timer will no-op if the delivery interval is zero.
@@ -180,10 +182,12 @@ module Kafka
180
182
  end
181
183
 
182
184
  class Worker
183
- def initialize(queue:, producer:, delivery_threshold:)
185
+ def initialize(queue:, producer:, delivery_threshold:, instrumenter:, logger:)
184
186
  @queue = queue
185
187
  @producer = producer
186
188
  @delivery_threshold = delivery_threshold
189
+ @instrumenter = instrumenter
190
+ @logger = logger
187
191
  end
188
192
 
189
193
  def run
@@ -197,8 +201,16 @@ module Kafka
197
201
  when :deliver_messages
198
202
  deliver_messages
199
203
  when :shutdown
200
- # Deliver any pending messages first.
201
- deliver_messages
204
+ begin
205
+ # Deliver any pending messages first.
206
+ @producer.deliver_messages
207
+ rescue Error => e
208
+ @logger.error("Failed to deliver messages during shutdown: #{e.message}")
209
+
210
+ @instrumenter.instrument("drop_messages.async_producer", {
211
+ message_count: @producer.buffer_size + @queue.size,
212
+ })
213
+ end
202
214
 
203
215
  # Stop the run loop.
204
216
  break
data/lib/kafka/client.rb CHANGED
@@ -10,6 +10,7 @@ require "kafka/fetched_message"
10
10
  require "kafka/fetch_operation"
11
11
  require "kafka/connection_builder"
12
12
  require "kafka/instrumenter"
13
+ require "kafka/sasl_authenticator"
13
14
 
14
15
  module Kafka
15
16
  class Client
@@ -58,6 +59,15 @@ module Kafka
58
59
 
59
60
  ssl_context = build_ssl_context(ssl_ca_cert_file_path, ssl_ca_cert, ssl_client_cert, ssl_client_cert_key)
60
61
 
62
+ sasl_authenticator = SaslAuthenticator.new(
63
+ sasl_gssapi_principal: sasl_gssapi_principal,
64
+ sasl_gssapi_keytab: sasl_gssapi_keytab,
65
+ sasl_plain_authzid: sasl_plain_authzid,
66
+ sasl_plain_username: sasl_plain_username,
67
+ sasl_plain_password: sasl_plain_password,
68
+ logger: @logger
69
+ )
70
+
61
71
  @connection_builder = ConnectionBuilder.new(
62
72
  client_id: client_id,
63
73
  connect_timeout: connect_timeout,
@@ -65,11 +75,7 @@ module Kafka
65
75
  ssl_context: ssl_context,
66
76
  logger: @logger,
67
77
  instrumenter: @instrumenter,
68
- sasl_gssapi_principal: sasl_gssapi_principal,
69
- sasl_gssapi_keytab: sasl_gssapi_keytab,
70
- sasl_plain_authzid: sasl_plain_authzid,
71
- sasl_plain_username: sasl_plain_username,
72
- sasl_plain_password: sasl_plain_password
78
+ sasl_authenticator: sasl_authenticator
73
79
  )
74
80
 
75
81
  @cluster = initialize_cluster
@@ -248,7 +254,8 @@ module Kafka
248
254
  logger: @logger,
249
255
  group_id: group_id,
250
256
  session_timeout: session_timeout,
251
- retention_time: retention_time
257
+ retention_time: retention_time,
258
+ instrumenter: instrumenter,
252
259
  )
253
260
 
254
261
  offset_manager = OffsetManager.new(
data/lib/kafka/cluster.rb CHANGED
@@ -193,6 +193,8 @@ module Kafka
193
193
  # @raise [ConnectionError] if none of the nodes in `seed_brokers` are available.
194
194
  # @return [Protocol::MetadataResponse] the cluster metadata.
195
195
  def fetch_cluster_info
196
+ errors = []
197
+
196
198
  @seed_brokers.shuffle.each do |node|
197
199
  @logger.info "Fetching cluster metadata from #{node}"
198
200
 
@@ -207,12 +209,15 @@ module Kafka
207
209
  return cluster_info
208
210
  rescue Error => e
209
211
  @logger.error "Failed to fetch metadata from #{node}: #{e}"
212
+ errors << [node, e]
210
213
  ensure
211
214
  broker.disconnect unless broker.nil?
212
215
  end
213
216
  end
214
217
 
215
- raise ConnectionError, "Could not connect to any of the seed brokers: #{@seed_brokers.join(', ')}"
218
+ error_description = errors.map {|node, exception| "- #{node}: #{exception}" }.join("\n")
219
+
220
+ raise ConnectionError, "Could not connect to any of the seed brokers:\n#{error_description}"
216
221
  end
217
222
 
218
223
  def connect_to_broker(broker_id)
@@ -48,7 +48,7 @@ module Kafka
48
48
  # broker. Default is 10 seconds.
49
49
  #
50
50
  # @return [Connection] a new connection.
51
- def initialize(host:, port:, client_id:, logger:, instrumenter:, connect_timeout: nil, socket_timeout: nil, ssl_context: nil)
51
+ def initialize(host:, port:, client_id:, logger:, instrumenter:, sasl_authenticator:, connect_timeout: nil, socket_timeout: nil, ssl_context: nil)
52
52
  @host, @port, @client_id = host, port, client_id
53
53
  @logger = logger
54
54
  @instrumenter = instrumenter
@@ -56,6 +56,7 @@ module Kafka
56
56
  @connect_timeout = connect_timeout || CONNECT_TIMEOUT
57
57
  @socket_timeout = socket_timeout || SOCKET_TIMEOUT
58
58
  @ssl_context = ssl_context
59
+ @sasl_authenticator = sasl_authenticator
59
60
  end
60
61
 
61
62
  def to_s
@@ -128,6 +129,7 @@ module Kafka
128
129
  @correlation_id = 0
129
130
 
130
131
  @last_request = nil
132
+ @sasl_authenticator.authenticate!(self)
131
133
  rescue Errno::ETIMEDOUT => e
132
134
  @logger.error "Timed out while trying to connect to #{self}: #{e}"
133
135
  raise ConnectionError, e
@@ -1,20 +1,13 @@
1
- require 'kafka/sasl_gssapi_authenticator'
2
- require 'kafka/sasl_plain_authenticator'
3
-
4
1
  module Kafka
5
2
  class ConnectionBuilder
6
- def initialize(client_id:, logger:, instrumenter:, connect_timeout:, socket_timeout:, ssl_context:, sasl_gssapi_principal:, sasl_gssapi_keytab:, sasl_plain_authzid:, sasl_plain_username:, sasl_plain_password:)
3
+ def initialize(client_id:, logger:, instrumenter:, connect_timeout:, socket_timeout:, ssl_context:, sasl_authenticator:)
7
4
  @client_id = client_id
8
5
  @logger = logger
9
6
  @instrumenter = instrumenter
10
7
  @connect_timeout = connect_timeout
11
8
  @socket_timeout = socket_timeout
12
9
  @ssl_context = ssl_context
13
- @sasl_gssapi_principal = sasl_gssapi_principal
14
- @sasl_gssapi_keytab = sasl_gssapi_keytab
15
- @sasl_plain_authzid = sasl_plain_authzid
16
- @sasl_plain_username = sasl_plain_username
17
- @sasl_plain_password = sasl_plain_password
10
+ @sasl_authenticator = sasl_authenticator
18
11
  end
19
12
 
20
13
  def build_connection(host, port)
@@ -26,49 +19,12 @@ module Kafka
26
19
  socket_timeout: @socket_timeout,
27
20
  logger: @logger,
28
21
  instrumenter: @instrumenter,
29
- ssl_context: @ssl_context
22
+ ssl_context: @ssl_context,
23
+ sasl_authenticator: @sasl_authenticator
30
24
  )
31
25
 
32
- if authenticate_using_sasl_gssapi?
33
- sasl_gssapi_authenticate(connection)
34
- elsif authenticate_using_sasl_plain?
35
- sasl_plain_authenticate(connection)
36
- end
37
-
38
26
  connection
39
27
  end
40
28
 
41
- private
42
-
43
- def sasl_gssapi_authenticate(connection)
44
- auth = SaslGssapiAuthenticator.new(
45
- connection: connection,
46
- logger: @logger,
47
- sasl_gssapi_principal: @sasl_gssapi_principal,
48
- sasl_gssapi_keytab: @sasl_gssapi_keytab
49
- )
50
-
51
- auth.authenticate!
52
- end
53
-
54
- def sasl_plain_authenticate(connection)
55
- auth = SaslPlainAuthenticator.new(
56
- connection: connection,
57
- logger: @logger,
58
- authzid: @sasl_plain_authzid,
59
- username: @sasl_plain_username,
60
- password: @sasl_plain_password
61
- )
62
-
63
- auth.authenticate!
64
- end
65
-
66
- def authenticate_using_sasl_gssapi?
67
- !@ssl_context && @sasl_gssapi_principal && !@sasl_gssapi_principal.empty?
68
- end
69
-
70
- def authenticate_using_sasl_plain?
71
- @sasl_plain_authzid && @sasl_plain_username && @sasl_plain_password
72
- end
73
29
  end
74
30
  end
@@ -5,11 +5,12 @@ module Kafka
5
5
  class ConsumerGroup
6
6
  attr_reader :assigned_partitions, :generation_id
7
7
 
8
- def initialize(cluster:, logger:, group_id:, session_timeout:, retention_time:)
8
+ def initialize(cluster:, logger:, group_id:, session_timeout:, retention_time:, instrumenter:)
9
9
  @cluster = cluster
10
10
  @logger = logger
11
11
  @group_id = group_id
12
12
  @session_timeout = session_timeout
13
+ @instrumenter = instrumenter
13
14
  @member_id = ""
14
15
  @generation_id = nil
15
16
  @members = {}
@@ -53,7 +54,10 @@ module Kafka
53
54
 
54
55
  def leave
55
56
  @logger.info "Leaving group `#{@group_id}`"
56
- coordinator.leave_group(group_id: @group_id, member_id: @member_id)
57
+
58
+ @instrumenter.instrument("leave_group.consumer", group_id: @group_id) do
59
+ coordinator.leave_group(group_id: @group_id, member_id: @member_id)
60
+ end
57
61
  rescue ConnectionError
58
62
  end
59
63
 
@@ -108,18 +112,20 @@ module Kafka
108
112
  def join_group
109
113
  @logger.info "Joining group `#{@group_id}`"
110
114
 
111
- response = coordinator.join_group(
112
- group_id: @group_id,
113
- session_timeout: @session_timeout,
114
- member_id: @member_id,
115
- )
115
+ @instrumenter.instrument("join_group.consumer", group_id: @group_id) do
116
+ response = coordinator.join_group(
117
+ group_id: @group_id,
118
+ session_timeout: @session_timeout,
119
+ member_id: @member_id,
120
+ )
116
121
 
117
- Protocol.handle_error(response.error_code)
122
+ Protocol.handle_error(response.error_code)
118
123
 
119
- @generation_id = response.generation_id
120
- @member_id = response.member_id
121
- @leader_id = response.leader_id
122
- @members = response.members
124
+ @generation_id = response.generation_id
125
+ @member_id = response.member_id
126
+ @leader_id = response.leader_id
127
+ @members = response.members
128
+ end
123
129
 
124
130
  @logger.info "Joined group `#{@group_id}` with member id `#{@member_id}`"
125
131
  rescue UnknownMemberId
@@ -147,20 +153,22 @@ module Kafka
147
153
  )
148
154
  end
149
155
 
150
- response = coordinator.sync_group(
151
- group_id: @group_id,
152
- generation_id: @generation_id,
153
- member_id: @member_id,
154
- group_assignment: group_assignment,
155
- )
156
+ @instrumenter.instrument("sync_group.consumer", group_id: @group_id) do
157
+ response = coordinator.sync_group(
158
+ group_id: @group_id,
159
+ generation_id: @generation_id,
160
+ member_id: @member_id,
161
+ group_assignment: group_assignment,
162
+ )
156
163
 
157
- Protocol.handle_error(response.error_code)
164
+ Protocol.handle_error(response.error_code)
158
165
 
159
- response.member_assignment.topics.each do |topic, assigned_partitions|
160
- @logger.info "Partitions assigned for `#{topic}`: #{assigned_partitions.join(', ')}"
161
- end
166
+ response.member_assignment.topics.each do |topic, assigned_partitions|
167
+ @logger.info "Partitions assigned for `#{topic}`: #{assigned_partitions.join(', ')}"
168
+ end
162
169
 
163
- @assigned_partitions.replace(response.member_assignment.topics)
170
+ @assigned_partitions.replace(response.member_assignment.topics)
171
+ end
164
172
  end
165
173
 
166
174
  def coordinator
data/lib/kafka/datadog.rb CHANGED
@@ -130,6 +130,45 @@ module Kafka
130
130
  end
131
131
  end
132
132
 
133
+ def join_group(event)
134
+ tags = {
135
+ client: event.payload.fetch(:client_id),
136
+ group_id: event.payload.fetch(:group_id),
137
+ }
138
+
139
+ timing("consumer.join_group", event.duration, tags: tags)
140
+
141
+ if event.payload.key?(:exception)
142
+ increment("consumer.join_group.errors", tags: tags)
143
+ end
144
+ end
145
+
146
+ def sync_group(event)
147
+ tags = {
148
+ client: event.payload.fetch(:client_id),
149
+ group_id: event.payload.fetch(:group_id),
150
+ }
151
+
152
+ timing("consumer.sync_group", event.duration, tags: tags)
153
+
154
+ if event.payload.key?(:exception)
155
+ increment("consumer.sync_group.errors", tags: tags)
156
+ end
157
+ end
158
+
159
+ def leave_group(event)
160
+ tags = {
161
+ client: event.payload.fetch(:client_id),
162
+ group_id: event.payload.fetch(:group_id),
163
+ }
164
+
165
+ timing("consumer.leave_group", event.duration, tags: tags)
166
+
167
+ if event.payload.key?(:exception)
168
+ increment("consumer.leave_group.errors", tags: tags)
169
+ end
170
+ end
171
+
133
172
  attach_to "consumer.kafka"
134
173
  end
135
174
 
@@ -145,6 +184,7 @@ module Kafka
145
184
 
146
185
  tags = {
147
186
  client: client,
187
+ topic: topic,
148
188
  }
149
189
 
150
190
  # This gets us the write rate.
@@ -226,6 +266,7 @@ module Kafka
226
266
 
227
267
  tags = {
228
268
  client: client,
269
+ topic: topic,
229
270
  }
230
271
 
231
272
  # This gets us the avg/max queue size per producer.
@@ -244,6 +285,16 @@ module Kafka
244
285
  increment("async_producer.produce.errors", tags: tags)
245
286
  end
246
287
 
288
+ def drop_messages(event)
289
+ tags = {
290
+ client: event.payload.fetch(:client_id),
291
+ }
292
+
293
+ message_count = event.payload.fetch(:message_count)
294
+
295
+ count("async_producer.dropped_messages", message_count, tags: tags)
296
+ end
297
+
247
298
  attach_to "async_producer.kafka"
248
299
  end
249
300
  end
@@ -30,9 +30,9 @@ module Kafka
30
30
  @processed_offsets[topic] ||= {}
31
31
 
32
32
  # The committed offset should always be the offset of the next message that the
33
- # application will read, thus adding one to the last message processed
33
+ # application will read, thus adding one to the last message processed.
34
34
  @processed_offsets[topic][partition] = offset + 1
35
- @logger.debug "Marking #{topic}/#{partition}:#{offset} as committed"
35
+ @logger.debug "Marking #{topic}/#{partition}:#{offset} as processed"
36
36
  end
37
37
 
38
38
  def seek_to_default(topic, partition)
@@ -0,0 +1,56 @@
1
+ require 'kafka/sasl_gssapi_authenticator'
2
+ require 'kafka/sasl_plain_authenticator'
3
+
4
+ module Kafka
5
+ class SaslAuthenticator
6
+ def initialize(logger:, sasl_gssapi_principal:, sasl_gssapi_keytab:, sasl_plain_authzid:, sasl_plain_username:, sasl_plain_password:)
7
+ @logger = logger
8
+ @sasl_gssapi_principal = sasl_gssapi_principal
9
+ @sasl_gssapi_keytab = sasl_gssapi_keytab
10
+ @sasl_plain_authzid = sasl_plain_authzid
11
+ @sasl_plain_username = sasl_plain_username
12
+ @sasl_plain_password = sasl_plain_password
13
+ end
14
+
15
+ def authenticate!(connection)
16
+ if authenticate_using_sasl_gssapi?
17
+ sasl_gssapi_authenticate(connection)
18
+ elsif authenticate_using_sasl_plain?
19
+ sasl_plain_authenticate(connection)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def sasl_gssapi_authenticate(connection)
26
+ auth = SaslGssapiAuthenticator.new(
27
+ connection: connection,
28
+ logger: @logger,
29
+ sasl_gssapi_principal: @sasl_gssapi_principal,
30
+ sasl_gssapi_keytab: @sasl_gssapi_keytab
31
+ )
32
+
33
+ auth.authenticate!
34
+ end
35
+
36
+ def sasl_plain_authenticate(connection)
37
+ auth = SaslPlainAuthenticator.new(
38
+ connection: connection,
39
+ logger: @logger,
40
+ authzid: @sasl_plain_authzid,
41
+ username: @sasl_plain_username,
42
+ password: @sasl_plain_password
43
+ )
44
+
45
+ auth.authenticate!
46
+ end
47
+
48
+ def authenticate_using_sasl_gssapi?
49
+ !@ssl_context && @sasl_gssapi_principal && !@sasl_gssapi_principal.empty?
50
+ end
51
+
52
+ def authenticate_using_sasl_plain?
53
+ @sasl_plain_authzid && @sasl_plain_username && @sasl_plain_password
54
+ end
55
+ end
56
+ end
data/lib/kafka/statsd.rb CHANGED
@@ -111,6 +111,39 @@ module Kafka
111
111
  end
112
112
  end
113
113
 
114
+ def join_group(event)
115
+ client = event.payload.fetch(:client_id)
116
+ group_id = event.payload.fetch(:group_id)
117
+
118
+ timing("consumer.#{client}.#{group_id}.join_group", event.duration)
119
+
120
+ if event.payload.key?(:exception)
121
+ increment("consumer.#{client}.#{group_id}.join_group.errors")
122
+ end
123
+ end
124
+
125
+ def sync_group(event)
126
+ client = event.payload.fetch(:client_id)
127
+ group_id = event.payload.fetch(:group_id)
128
+
129
+ timing("consumer.#{client}.#{group_id}.sync_group", event.duration)
130
+
131
+ if event.payload.key?(:exception)
132
+ increment("consumer.#{client}.#{group_id}.sync_group.errors")
133
+ end
134
+ end
135
+
136
+ def leave_group(event)
137
+ client = event.payload.fetch(:client_id)
138
+ group_id = event.payload.fetch(:group_id)
139
+
140
+ timing("consumer.#{client}.#{group_id}.leave_group", event.duration)
141
+
142
+ if event.payload.key?(:exception)
143
+ increment("consumer.#{client}.#{group_id}.leave_group.errors")
144
+ end
145
+ end
146
+
114
147
  attach_to "consumer.kafka"
115
148
  end
116
149
 
@@ -205,6 +238,13 @@ module Kafka
205
238
  increment("async_producer.#{client}.#{topic}.produce.errors")
206
239
  end
207
240
 
241
+ def drop_messages(event)
242
+ client = event.payload.fetch(:client_id)
243
+ message_count = event.payload.fetch(:message_count)
244
+
245
+ count("async_producer.#{client}.dropped_messages", message_count)
246
+ end
247
+
208
248
  attach_to "async_producer.kafka"
209
249
  end
210
250
  end
data/lib/kafka/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kafka
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-kafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-08-25 00:00:00.000000000 Z
11
+ date: 2017-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -322,6 +322,7 @@ files:
322
322
  - lib/kafka/protocol/sync_group_response.rb
323
323
  - lib/kafka/protocol/topic_metadata_request.rb
324
324
  - lib/kafka/round_robin_assignment_strategy.rb
325
+ - lib/kafka/sasl_authenticator.rb
325
326
  - lib/kafka/sasl_gssapi_authenticator.rb
326
327
  - lib/kafka/sasl_plain_authenticator.rb
327
328
  - lib/kafka/snappy_codec.rb