ruby-kafka 0.1.0.pre.beta4 → 0.1.0.pre.beta5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71fcf73c1adb43253684cb0ef1406a0631c3ce3a
4
- data.tar.gz: dde9f9123b31f20475c7d280532f0b135f6a84bf
3
+ metadata.gz: 2ed5c1319300907fea512494cd9f4f07c3b15b70
4
+ data.tar.gz: e76e37ec50c63fbd8969478a38e6e804059d5a45
5
5
  SHA512:
6
- metadata.gz: 8c44cc61c8b50ab3854830596bfd965837e03e1af97c3917c3ecf4386bab38534184688ae5dc1f4c284c8f2878143637e9dffdfe35e95c504a8f1381ca4da6e5
7
- data.tar.gz: 06de9ac78b013cf448e03d0ba4ec879059bfbc04c6626c373e30c4c0c95c3b23282b30c3607df8955ab4c617d5d31d67c8ae80033381212baccf107aefda0489
6
+ metadata.gz: 878fc9de44046cee3895212d766f44d5011de1dd3fd05f0f745efd00d284dc2cdae6e5d2aed3cb1ae794210da5f4b76a993cbff03c1554339cb5f5fb088ab071
7
+ data.tar.gz: 243bb41e327d1f9d8a9d06eb0c1b6bc585a63d2607ab0b25fe44af0ff7cb936429949620a9a4e8b12b5f0838db59aebfab7f73a4e100e7f8763f566094592405
@@ -31,4 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "pry"
32
32
  spec.add_development_dependency "dotenv"
33
33
  spec.add_development_dependency "docker-api"
34
+ spec.add_development_dependency "rspec-benchmark"
34
35
  end
@@ -1,5 +1,5 @@
1
- require "socket"
2
1
  require "stringio"
2
+ require "kafka/socket_with_timeout"
3
3
  require "kafka/protocol/request_message"
4
4
  require "kafka/protocol/encoder"
5
5
  require "kafka/protocol/decoder"
@@ -38,7 +38,7 @@ module Kafka
38
38
 
39
39
  @logger.info "Opening connection to #{@host}:#{@port} with client id #{@client_id}..."
40
40
 
41
- @socket = Socket.tcp(host, port, connect_timeout: @connect_timeout)
41
+ @socket = SocketWithTimeout.new(@host, @port, timeout: @connect_timeout)
42
42
 
43
43
  @encoder = Kafka::Protocol::Encoder.new(@socket)
44
44
  @decoder = Kafka::Protocol::Decoder.new(@socket)
@@ -112,14 +112,12 @@ module Kafka
112
112
 
113
113
  data = Kafka::Protocol::Encoder.encode_with(message)
114
114
 
115
- unless IO.select(nil, [@socket], nil, @socket_timeout)
116
- @logger.error "Timed out while writing request #{@correlation_id}"
117
- raise ConnectionError
118
- end
119
-
120
115
  @encoder.write_bytes(data)
121
116
 
122
117
  nil
118
+ rescue Errno::ETIMEDOUT
119
+ @logger.error "Timed out while writing request #{@correlation_id}"
120
+ raise ConnectionError
123
121
  end
124
122
 
125
123
  # Reads a response from the connection.
@@ -131,11 +129,6 @@ module Kafka
131
129
  def read_response(response_class)
132
130
  @logger.debug "Waiting for response #{@correlation_id} from #{to_s}"
133
131
 
134
- unless IO.select([@socket], nil, nil, @socket_timeout)
135
- @logger.error "Timed out while waiting for response #{@correlation_id}"
136
- raise ConnectionError
137
- end
138
-
139
132
  bytes = @decoder.bytes
140
133
 
141
134
  buffer = StringIO.new(bytes)
@@ -147,6 +140,9 @@ module Kafka
147
140
  @logger.debug "Received response #{correlation_id} from #{to_s}"
148
141
 
149
142
  return correlation_id, response
143
+ rescue Errno::ETIMEDOUT
144
+ @logger.error "Timed out while waiting for response #{@correlation_id}"
145
+ raise ConnectionError
150
146
  end
151
147
  end
152
148
  end
@@ -4,15 +4,20 @@ module Kafka
4
4
  class MessageBuffer
5
5
  include Enumerable
6
6
 
7
+ attr_reader :size
8
+
7
9
  def initialize
8
10
  @buffer = {}
11
+ @size = 0
9
12
  end
10
13
 
11
14
  def write(message, topic:, partition:)
15
+ @size += 1
12
16
  buffer_for(topic, partition) << message
13
17
  end
14
18
 
15
19
  def concat(messages, topic:, partition:)
20
+ @size += messages.count
16
21
  buffer_for(topic, partition).concat(messages)
17
22
  end
18
23
 
@@ -20,10 +25,6 @@ module Kafka
20
25
  @buffer
21
26
  end
22
27
 
23
- def size
24
- @buffer.values.inject(0) {|sum, messages| messages.values.flatten.size + sum }
25
- end
26
-
27
28
  def empty?
28
29
  @buffer.empty?
29
30
  end
@@ -43,6 +44,8 @@ module Kafka
43
44
  #
44
45
  # @return [nil]
45
46
  def clear_messages(topic:, partition:)
47
+ @size -= @buffer[topic][partition].count
48
+
46
49
  @buffer[topic].delete(partition)
47
50
  @buffer.delete(topic) if @buffer[topic].empty?
48
51
  end
@@ -52,6 +55,7 @@ module Kafka
52
55
  # @return [nil]
53
56
  def clear
54
57
  @buffer = {}
58
+ @size = 0
55
59
  end
56
60
 
57
61
  private
@@ -1,6 +1,7 @@
1
1
  require "kafka/partitioner"
2
2
  require "kafka/message_buffer"
3
3
  require "kafka/protocol/message"
4
+ require "kafka/transmission"
4
5
 
5
6
  module Kafka
6
7
 
@@ -161,11 +162,19 @@ module Kafka
161
162
  def send_messages
162
163
  attempt = 0
163
164
 
165
+ transmission = Transmission.new(
166
+ broker_pool: @broker_pool,
167
+ buffer: @buffer,
168
+ required_acks: @required_acks,
169
+ ack_timeout: @ack_timeout,
170
+ logger: @logger,
171
+ )
172
+
164
173
  loop do
165
174
  @logger.info "Sending #{@buffer.size} messages"
166
175
 
167
176
  attempt += 1
168
- transmit_messages
177
+ transmission.send_messages
169
178
 
170
179
  if @buffer.empty?
171
180
  @logger.info "Successfully transmitted all messages"
@@ -208,72 +217,5 @@ module Kafka
208
217
  def shutdown
209
218
  @broker_pool.shutdown
210
219
  end
211
-
212
- private
213
-
214
- def transmit_messages
215
- messages_for_broker = {}
216
-
217
- @buffer.each do |topic, partition, messages|
218
- broker_id = @broker_pool.get_leader_id(topic, partition)
219
-
220
- @logger.debug "Current leader for #{topic}/#{partition} is node #{broker_id}"
221
-
222
- messages_for_broker[broker_id] ||= MessageBuffer.new
223
- messages_for_broker[broker_id].concat(messages, topic: topic, partition: partition)
224
- end
225
-
226
- messages_for_broker.each do |broker_id, message_set|
227
- begin
228
- broker = @broker_pool.get_broker(broker_id)
229
-
230
- response = broker.produce(
231
- messages_for_topics: message_set.to_h,
232
- required_acks: @required_acks,
233
- timeout: @ack_timeout * 1000, # Kafka expects the timeout in milliseconds.
234
- )
235
-
236
- handle_response(response) if response
237
- rescue ConnectionError => e
238
- @logger.error "Could not connect to broker #{broker_id}: #{e}"
239
-
240
- # Mark the broker pool as stale in order to force a cluster metadata refresh.
241
- @broker_pool.mark_as_stale!
242
- end
243
- end
244
- end
245
-
246
- def handle_response(response)
247
- response.each_partition do |topic_info, partition_info|
248
- topic = topic_info.topic
249
- partition = partition_info.partition
250
-
251
- begin
252
- Protocol.handle_error(partition_info.error_code)
253
- rescue Kafka::CorruptMessage
254
- @logger.error "Corrupt message when writing to #{topic}/#{partition}"
255
- rescue Kafka::UnknownTopicOrPartition
256
- @logger.error "Unknown topic or partition #{topic}/#{partition}"
257
- rescue Kafka::LeaderNotAvailable
258
- @logger.error "Leader currently not available for #{topic}/#{partition}"
259
- @broker_pool.mark_as_stale!
260
- rescue Kafka::NotLeaderForPartition
261
- @logger.error "Broker not currently leader for #{topic}/#{partition}"
262
- @broker_pool.mark_as_stale!
263
- rescue Kafka::RequestTimedOut
264
- @logger.error "Timed out while writing to #{topic}/#{partition}"
265
- rescue Kafka::NotEnoughReplicas
266
- @logger.error "Not enough in-sync replicas for #{topic}/#{partition}"
267
- rescue Kafka::NotEnoughReplicasAfterAppend
268
- @logger.error "Messages written, but to fewer in-sync replicas than required for #{topic}/#{partition}"
269
- else
270
- offset = partition_info.offset
271
- @logger.info "Successfully sent messages for #{topic}/#{partition}; new offset is #{offset}"
272
-
273
- # The messages were successfully written; clear them from the buffer.
274
- @buffer.clear_messages(topic: topic, partition: partition)
275
- end
276
- end
277
- end
278
220
  end
279
221
  end
@@ -0,0 +1,87 @@
1
+ require "socket"
2
+
3
+ module Kafka
4
+
5
+ # Opens sockets in a non-blocking fashion, ensuring that we're not stalling
6
+ # for long periods of time.
7
+ #
8
+ # It's possible to set timeouts for connecting to the server, for reading data,
9
+ # and for writing data. Whenever a timeout is exceeded, Errno::ETIMEDOUT is
10
+ # raised.
11
+ #
12
+ class SocketWithTimeout
13
+
14
+ # Opens a socket.
15
+ #
16
+ # @param host [String]
17
+ # @param port [Integer]
18
+ # @param timeout [Integer] the connection timeout, in seconds.
19
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
20
+ def initialize(host, port, timeout: nil)
21
+ addr = Socket.getaddrinfo(host, nil)
22
+ sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
23
+
24
+ @socket = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
25
+ @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
26
+
27
+ begin
28
+ # Initiate the socket connection in the background. If it doesn't fail
29
+ # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
30
+ # indicating the connection is in progress.
31
+ @socket.connect_nonblock(sockaddr)
32
+ rescue IO::WaitWritable
33
+ # IO.select will block until the socket is writable or the timeout
34
+ # is exceeded, whichever comes first.
35
+ unless IO.select(nil, [@socket], nil, timeout)
36
+ # IO.select returns nil when the socket is not ready before timeout
37
+ # seconds have elapsed
38
+ @socket.close
39
+ raise Errno::ETIMEDOUT
40
+ end
41
+
42
+ begin
43
+ # Verify there is now a good connection.
44
+ @socket.connect_nonblock(sockaddr)
45
+ rescue Errno::EISCONN
46
+ # The socket is connected, we're good!
47
+ end
48
+ end
49
+ end
50
+
51
+ # Reads bytes from the socket, possible with a timeout.
52
+ #
53
+ # @param num_bytes [Integer] the number of bytes to read.
54
+ # @param timeout [Integer] the number of seconds to wait before timing out.
55
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
56
+ # @return [String] the data that was read from the socket.
57
+ def read(num_bytes, timeout: nil)
58
+ unless IO.select([@socket], nil, nil, timeout)
59
+ raise Errno::ETIMEDOUT
60
+ end
61
+
62
+ @socket.read(num_bytes)
63
+ end
64
+
65
+ # Writes bytes to the socket, possible with a timeout.
66
+ #
67
+ # @param bytes [String] the data that should be written to the socket.
68
+ # @param timeout [Integer] the number of seconds to wait before timing out.
69
+ # @raise [Errno::ETIMEDOUT] if the timeout is exceeded.
70
+ # @return [Integer] the number of bytes written.
71
+ def write(bytes, timeout: nil)
72
+ unless IO.select(nil, [@socket], nil, timeout)
73
+ raise Errno::ETIMEDOUT
74
+ end
75
+
76
+ @socket.write(bytes)
77
+ end
78
+
79
+ def close
80
+ @socket.close
81
+ end
82
+
83
+ def set_encoding(encoding)
84
+ @socket.set_encoding(encoding)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,78 @@
1
+ module Kafka
2
+ class Transmission
3
+ def initialize(broker_pool:, buffer:, required_acks:, ack_timeout:, logger:)
4
+ @broker_pool = broker_pool
5
+ @buffer = buffer
6
+ @required_acks = required_acks
7
+ @ack_timeout = ack_timeout
8
+ @logger = logger
9
+ end
10
+
11
+ def send_messages
12
+ messages_for_broker = {}
13
+
14
+ @buffer.each do |topic, partition, messages|
15
+ broker_id = @broker_pool.get_leader_id(topic, partition)
16
+
17
+ @logger.debug "Current leader for #{topic}/#{partition} is node #{broker_id}"
18
+
19
+ messages_for_broker[broker_id] ||= MessageBuffer.new
20
+ messages_for_broker[broker_id].concat(messages, topic: topic, partition: partition)
21
+ end
22
+
23
+ messages_for_broker.each do |broker_id, message_set|
24
+ begin
25
+ broker = @broker_pool.get_broker(broker_id)
26
+
27
+ response = broker.produce(
28
+ messages_for_topics: message_set.to_h,
29
+ required_acks: @required_acks,
30
+ timeout: @ack_timeout * 1000, # Kafka expects the timeout in milliseconds.
31
+ )
32
+
33
+ handle_response(response) if response
34
+ rescue ConnectionError => e
35
+ @logger.error "Could not connect to broker #{broker_id}: #{e}"
36
+
37
+ # Mark the broker pool as stale in order to force a cluster metadata refresh.
38
+ @broker_pool.mark_as_stale!
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def handle_response(response)
46
+ response.each_partition do |topic_info, partition_info|
47
+ topic = topic_info.topic
48
+ partition = partition_info.partition
49
+
50
+ begin
51
+ Protocol.handle_error(partition_info.error_code)
52
+ rescue Kafka::CorruptMessage
53
+ @logger.error "Corrupt message when writing to #{topic}/#{partition}"
54
+ rescue Kafka::UnknownTopicOrPartition
55
+ @logger.error "Unknown topic or partition #{topic}/#{partition}"
56
+ rescue Kafka::LeaderNotAvailable
57
+ @logger.error "Leader currently not available for #{topic}/#{partition}"
58
+ @broker_pool.mark_as_stale!
59
+ rescue Kafka::NotLeaderForPartition
60
+ @logger.error "Broker not currently leader for #{topic}/#{partition}"
61
+ @broker_pool.mark_as_stale!
62
+ rescue Kafka::RequestTimedOut
63
+ @logger.error "Timed out while writing to #{topic}/#{partition}"
64
+ rescue Kafka::NotEnoughReplicas
65
+ @logger.error "Not enough in-sync replicas for #{topic}/#{partition}"
66
+ rescue Kafka::NotEnoughReplicasAfterAppend
67
+ @logger.error "Messages written, but to fewer in-sync replicas than required for #{topic}/#{partition}"
68
+ else
69
+ offset = partition_info.offset
70
+ @logger.info "Successfully sent messages for #{topic}/#{partition}; new offset is #{offset}"
71
+
72
+ # The messages were successfully written; clear them from the buffer.
73
+ @buffer.clear_messages(topic: topic, partition: partition)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,3 +1,3 @@
1
1
  module Kafka
2
- VERSION = "0.1.0-beta4"
2
+ VERSION = "0.1.0-beta5"
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.1.0.pre.beta4
4
+ version: 0.1.0.pre.beta5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-01 00:00:00.000000000 Z
11
+ date: 2016-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-benchmark
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description: |-
98
112
  A client library for the Kafka distributed commit log.
99
113
 
@@ -133,6 +147,8 @@ files:
133
147
  - lib/kafka/protocol/produce_response.rb
134
148
  - lib/kafka/protocol/request_message.rb
135
149
  - lib/kafka/protocol/topic_metadata_request.rb
150
+ - lib/kafka/socket_with_timeout.rb
151
+ - lib/kafka/transmission.rb
136
152
  - lib/kafka/version.rb
137
153
  - lib/ruby-kafka.rb
138
154
  homepage: https://github.com/zendesk/ruby-kafka