ruby-kafka 0.1.0.pre.beta4 → 0.1.0.pre.beta5
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
- data/kafka.gemspec +1 -0
- data/lib/kafka/connection.rb +8 -12
- data/lib/kafka/message_buffer.rb +8 -4
- data/lib/kafka/producer.rb +10 -68
- data/lib/kafka/socket_with_timeout.rb +87 -0
- data/lib/kafka/transmission.rb +78 -0
- data/lib/kafka/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ed5c1319300907fea512494cd9f4f07c3b15b70
|
4
|
+
data.tar.gz: e76e37ec50c63fbd8969478a38e6e804059d5a45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 878fc9de44046cee3895212d766f44d5011de1dd3fd05f0f745efd00d284dc2cdae6e5d2aed3cb1ae794210da5f4b76a993cbff03c1554339cb5f5fb088ab071
|
7
|
+
data.tar.gz: 243bb41e327d1f9d8a9d06eb0c1b6bc585a63d2607ab0b25fe44af0ff7cb936429949620a9a4e8b12b5f0838db59aebfab7f73a4e100e7f8763f566094592405
|
data/kafka.gemspec
CHANGED
data/lib/kafka/connection.rb
CHANGED
@@ -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 =
|
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
|
data/lib/kafka/message_buffer.rb
CHANGED
@@ -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
|
data/lib/kafka/producer.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/kafka/version.rb
CHANGED
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.
|
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-
|
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
|