ruby-kafka 0.7.0 → 0.7.1.beta1
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/.circleci/config.yml +3 -3
- data/.gitignore +1 -0
- data/CHANGELOG.md +4 -0
- data/lib/kafka.rb +32 -0
- data/lib/kafka/broker.rb +18 -0
- data/lib/kafka/client.rb +38 -4
- data/lib/kafka/cluster.rb +60 -37
- data/lib/kafka/consumer.rb +2 -2
- data/lib/kafka/fetch_operation.rb +18 -59
- data/lib/kafka/fetched_batch.rb +9 -9
- data/lib/kafka/fetched_batch_generator.rb +114 -0
- data/lib/kafka/fetched_offset_resolver.rb +48 -0
- data/lib/kafka/fetcher.rb +2 -2
- data/lib/kafka/produce_operation.rb +52 -14
- data/lib/kafka/producer.rb +82 -2
- data/lib/kafka/protocol.rb +68 -48
- data/lib/kafka/protocol/add_partitions_to_txn_request.rb +34 -0
- data/lib/kafka/protocol/add_partitions_to_txn_response.rb +47 -0
- data/lib/kafka/protocol/decoder.rb +3 -6
- data/lib/kafka/protocol/encoder.rb +6 -11
- data/lib/kafka/protocol/end_txn_request.rb +29 -0
- data/lib/kafka/protocol/end_txn_response.rb +19 -0
- data/lib/kafka/protocol/fetch_request.rb +3 -1
- data/lib/kafka/protocol/fetch_response.rb +37 -18
- data/lib/kafka/protocol/init_producer_id_request.rb +26 -0
- data/lib/kafka/protocol/init_producer_id_response.rb +27 -0
- data/lib/kafka/protocol/list_offset_request.rb +8 -2
- data/lib/kafka/protocol/list_offset_response.rb +11 -6
- data/lib/kafka/protocol/record.rb +9 -0
- data/lib/kafka/protocol/record_batch.rb +17 -1
- data/lib/kafka/ssl_context.rb +19 -5
- data/lib/kafka/transaction_manager.rb +261 -0
- data/lib/kafka/transaction_state_machine.rb +72 -0
- data/lib/kafka/version.rb +1 -1
- data/ruby-kafka.gemspec +1 -1
- metadata +20 -4
data/lib/kafka/protocol.rb
CHANGED
@@ -14,46 +14,52 @@ module Kafka
|
|
14
14
|
# The replica id of non-brokers is always -1.
|
15
15
|
REPLICA_ID = -1
|
16
16
|
|
17
|
-
PRODUCE_API
|
18
|
-
FETCH_API
|
19
|
-
LIST_OFFSET_API
|
20
|
-
TOPIC_METADATA_API
|
21
|
-
OFFSET_COMMIT_API
|
22
|
-
OFFSET_FETCH_API
|
23
|
-
FIND_COORDINATOR_API
|
24
|
-
JOIN_GROUP_API
|
25
|
-
HEARTBEAT_API
|
26
|
-
LEAVE_GROUP_API
|
27
|
-
SYNC_GROUP_API
|
28
|
-
DESCRIBE_GROUPS_API
|
29
|
-
LIST_GROUPS_API
|
30
|
-
SASL_HANDSHAKE_API
|
31
|
-
API_VERSIONS_API
|
32
|
-
CREATE_TOPICS_API
|
33
|
-
DELETE_TOPICS_API
|
34
|
-
|
35
|
-
|
36
|
-
|
17
|
+
PRODUCE_API = 0
|
18
|
+
FETCH_API = 1
|
19
|
+
LIST_OFFSET_API = 2
|
20
|
+
TOPIC_METADATA_API = 3
|
21
|
+
OFFSET_COMMIT_API = 8
|
22
|
+
OFFSET_FETCH_API = 9
|
23
|
+
FIND_COORDINATOR_API = 10
|
24
|
+
JOIN_GROUP_API = 11
|
25
|
+
HEARTBEAT_API = 12
|
26
|
+
LEAVE_GROUP_API = 13
|
27
|
+
SYNC_GROUP_API = 14
|
28
|
+
DESCRIBE_GROUPS_API = 15
|
29
|
+
LIST_GROUPS_API = 16
|
30
|
+
SASL_HANDSHAKE_API = 17
|
31
|
+
API_VERSIONS_API = 18
|
32
|
+
CREATE_TOPICS_API = 19
|
33
|
+
DELETE_TOPICS_API = 20
|
34
|
+
INIT_PRODUCER_ID_API = 22
|
35
|
+
ADD_PARTITIONS_TO_TXN_API = 24
|
36
|
+
END_TXN_API = 26
|
37
|
+
DESCRIBE_CONFIGS_API = 32
|
38
|
+
ALTER_CONFIGS_API = 33
|
39
|
+
CREATE_PARTITIONS_API = 37
|
37
40
|
|
38
41
|
# A mapping from numeric API keys to symbolic API names.
|
39
42
|
APIS = {
|
40
|
-
PRODUCE_API
|
41
|
-
FETCH_API
|
42
|
-
LIST_OFFSET_API
|
43
|
-
TOPIC_METADATA_API
|
44
|
-
OFFSET_COMMIT_API
|
45
|
-
OFFSET_FETCH_API
|
46
|
-
FIND_COORDINATOR_API
|
47
|
-
JOIN_GROUP_API
|
48
|
-
HEARTBEAT_API
|
49
|
-
LEAVE_GROUP_API
|
50
|
-
SYNC_GROUP_API
|
51
|
-
SASL_HANDSHAKE_API
|
52
|
-
API_VERSIONS_API
|
53
|
-
CREATE_TOPICS_API
|
54
|
-
DELETE_TOPICS_API
|
55
|
-
|
56
|
-
|
43
|
+
PRODUCE_API => :produce,
|
44
|
+
FETCH_API => :fetch,
|
45
|
+
LIST_OFFSET_API => :list_offset,
|
46
|
+
TOPIC_METADATA_API => :topic_metadata,
|
47
|
+
OFFSET_COMMIT_API => :offset_commit,
|
48
|
+
OFFSET_FETCH_API => :offset_fetch,
|
49
|
+
FIND_COORDINATOR_API => :find_coordinator,
|
50
|
+
JOIN_GROUP_API => :join_group,
|
51
|
+
HEARTBEAT_API => :heartbeat,
|
52
|
+
LEAVE_GROUP_API => :leave_group,
|
53
|
+
SYNC_GROUP_API => :sync_group,
|
54
|
+
SASL_HANDSHAKE_API => :sasl_handshake,
|
55
|
+
API_VERSIONS_API => :api_versions,
|
56
|
+
CREATE_TOPICS_API => :create_topics,
|
57
|
+
DELETE_TOPICS_API => :delete_topics,
|
58
|
+
INIT_PRODUCER_ID_API => :init_producer_id_api,
|
59
|
+
ADD_PARTITIONS_TO_TXN_API => :add_partitions_to_txn_api,
|
60
|
+
END_TXN_API => :end_txn_api,
|
61
|
+
DESCRIBE_CONFIGS_API => :describe_configs_api,
|
62
|
+
CREATE_PARTITIONS_API => :create_partitions
|
57
63
|
}
|
58
64
|
|
59
65
|
# A mapping from numeric error codes to exception classes.
|
@@ -95,24 +101,32 @@ module Kafka
|
|
95
101
|
39 => InvalidReplicaAssignment,
|
96
102
|
40 => InvalidConfig,
|
97
103
|
41 => NotController,
|
98
|
-
42 => InvalidRequest
|
104
|
+
42 => InvalidRequest,
|
105
|
+
45 => OutOfOrderSequenceNumberError,
|
106
|
+
46 => DuplicateSequenceNumberError,
|
107
|
+
47 => InvalidProducerEpochError,
|
108
|
+
48 => InvalidTxnStateError,
|
109
|
+
49 => InvalidProducerIDMappingError,
|
110
|
+
50 => InvalidTransactionTimeoutError,
|
111
|
+
51 => ConcurrentTransactionError,
|
112
|
+
52 => TransactionCoordinatorFencedError
|
99
113
|
}
|
100
114
|
|
101
115
|
# A mapping from int to corresponding resource type in symbol.
|
102
116
|
# https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/resource/ResourceType.java
|
103
|
-
RESOURCE_TYPE_UNKNOWN
|
104
|
-
RESOURCE_TYPE_ANY
|
105
|
-
RESOURCE_TYPE_TOPIC
|
106
|
-
RESOURCE_TYPE_GROUP
|
107
|
-
RESOURCE_TYPE_CLUSTER
|
117
|
+
RESOURCE_TYPE_UNKNOWN = 0
|
118
|
+
RESOURCE_TYPE_ANY = 1
|
119
|
+
RESOURCE_TYPE_TOPIC = 2
|
120
|
+
RESOURCE_TYPE_GROUP = 3
|
121
|
+
RESOURCE_TYPE_CLUSTER = 4
|
108
122
|
RESOURCE_TYPE_TRANSACTIONAL_ID = 5
|
109
123
|
RESOURCE_TYPE_DELEGATION_TOKEN = 6
|
110
124
|
RESOURCE_TYPES = {
|
111
|
-
RESOURCE_TYPE_UNKNOWN
|
112
|
-
RESOURCE_TYPE_ANY
|
113
|
-
RESOURCE_TYPE_TOPIC
|
114
|
-
RESOURCE_TYPE_GROUP
|
115
|
-
RESOURCE_TYPE_CLUSTER
|
125
|
+
RESOURCE_TYPE_UNKNOWN => :unknown,
|
126
|
+
RESOURCE_TYPE_ANY => :any,
|
127
|
+
RESOURCE_TYPE_TOPIC => :topic,
|
128
|
+
RESOURCE_TYPE_GROUP => :group,
|
129
|
+
RESOURCE_TYPE_CLUSTER => :cluster,
|
116
130
|
RESOURCE_TYPE_TRANSACTIONAL_ID => :transactional_id,
|
117
131
|
RESOURCE_TYPE_DELEGATION_TOKEN => :delegation_token,
|
118
132
|
}
|
@@ -188,3 +202,9 @@ require "kafka/protocol/list_groups_request"
|
|
188
202
|
require "kafka/protocol/list_groups_response"
|
189
203
|
require "kafka/protocol/describe_groups_request"
|
190
204
|
require "kafka/protocol/describe_groups_response"
|
205
|
+
require "kafka/protocol/init_producer_id_request"
|
206
|
+
require "kafka/protocol/init_producer_id_response"
|
207
|
+
require "kafka/protocol/add_partitions_to_txn_request"
|
208
|
+
require "kafka/protocol/add_partitions_to_txn_response"
|
209
|
+
require "kafka/protocol/end_txn_request"
|
210
|
+
require "kafka/protocol/end_txn_response"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kafka
|
4
|
+
module Protocol
|
5
|
+
class AddPartitionsToTxnRequest
|
6
|
+
def initialize(transactional_id: nil, producer_id:, producer_epoch:, topics:)
|
7
|
+
@transactional_id = transactional_id
|
8
|
+
@producer_id = producer_id
|
9
|
+
@producer_epoch = producer_epoch
|
10
|
+
@topics = topics
|
11
|
+
end
|
12
|
+
|
13
|
+
def api_key
|
14
|
+
ADD_PARTITIONS_TO_TXN_API
|
15
|
+
end
|
16
|
+
|
17
|
+
def response_class
|
18
|
+
AddPartitionsToTxnResponse
|
19
|
+
end
|
20
|
+
|
21
|
+
def encode(encoder)
|
22
|
+
encoder.write_string(@transactional_id.to_s)
|
23
|
+
encoder.write_int64(@producer_id)
|
24
|
+
encoder.write_int16(@producer_epoch)
|
25
|
+
encoder.write_array(@topics.to_a) do |topic, partitions|
|
26
|
+
encoder.write_string(topic)
|
27
|
+
encoder.write_array(partitions) do |partition|
|
28
|
+
encoder.write_int32(partition)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kafka
|
4
|
+
module Protocol
|
5
|
+
class AddPartitionsToTxnResponse
|
6
|
+
class PartitionError
|
7
|
+
attr_reader :partition, :error_code
|
8
|
+
|
9
|
+
def initialize(partition:, error_code:)
|
10
|
+
@partition = partition
|
11
|
+
@error_code = error_code
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class TopicPartitionsError
|
16
|
+
attr_reader :topic, :partitions
|
17
|
+
|
18
|
+
def initialize(topic:, partitions:)
|
19
|
+
@topic = topic
|
20
|
+
@partitions = partitions
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :errors
|
25
|
+
|
26
|
+
def initialize(errors:)
|
27
|
+
@errors = errors
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.decode(decoder)
|
31
|
+
_throttle_time_ms = decoder.int32
|
32
|
+
errors = decoder.array do
|
33
|
+
TopicPartitionsError.new(
|
34
|
+
topic: decoder.string,
|
35
|
+
partitions: decoder.array do
|
36
|
+
PartitionError.new(
|
37
|
+
partition: decoder.int32,
|
38
|
+
error_code: decoder.int16
|
39
|
+
)
|
40
|
+
end
|
41
|
+
)
|
42
|
+
end
|
43
|
+
new(errors: errors)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -6,8 +6,6 @@ module Kafka
|
|
6
6
|
# from it. The Kafka protocol is not self-describing, so a client must call
|
7
7
|
# these methods in just the right order for things to work.
|
8
8
|
class Decoder
|
9
|
-
VARINT_MASK = 0b10000000
|
10
|
-
|
11
9
|
def self.from_string(str)
|
12
10
|
new(StringIO.new(str))
|
13
11
|
end
|
@@ -123,12 +121,11 @@ module Kafka
|
|
123
121
|
def varint
|
124
122
|
group = 0
|
125
123
|
data = 0
|
126
|
-
|
127
|
-
chunk
|
128
|
-
data |= (chunk & (~VARINT_MASK)) << group
|
124
|
+
while (chunk = int8) & 0x80 != 0
|
125
|
+
data |= (chunk & 0x7f) << group
|
129
126
|
group += 7
|
130
|
-
break if (chunk & VARINT_MASK) == 0
|
131
127
|
end
|
128
|
+
data |= chunk << group
|
132
129
|
data & 0b1 != 0 ? ~(data >> 1) : (data >> 1)
|
133
130
|
end
|
134
131
|
|
@@ -7,8 +7,6 @@ module Kafka
|
|
7
7
|
# An encoder wraps an IO object, making it easy to write specific data types
|
8
8
|
# to it.
|
9
9
|
class Encoder
|
10
|
-
VARINT_MASK = 0b10000000
|
11
|
-
|
12
10
|
# Initializes a new encoder.
|
13
11
|
#
|
14
12
|
# @param io [IO] an object that acts as an IO.
|
@@ -134,16 +132,13 @@ module Kafka
|
|
134
132
|
int = int << 1
|
135
133
|
int = ~int | 1 if int < 0
|
136
134
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
write_int8(chunk)
|
142
|
-
return
|
143
|
-
else
|
144
|
-
write_int8(chunk | VARINT_MASK)
|
145
|
-
end
|
135
|
+
chunks = []
|
136
|
+
while int & 0xff80 != 0
|
137
|
+
chunks << (int & 0x7f | 0x80)
|
138
|
+
int >>= 7
|
146
139
|
end
|
140
|
+
chunks << int
|
141
|
+
write(chunks.pack("C*"))
|
147
142
|
end
|
148
143
|
|
149
144
|
# Writes a byte string to the IO object.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kafka
|
4
|
+
module Protocol
|
5
|
+
class EndTxnRequest
|
6
|
+
def initialize(transactional_id:, producer_id:, producer_epoch:, transaction_result:)
|
7
|
+
@transactional_id = transactional_id
|
8
|
+
@producer_id = producer_id
|
9
|
+
@producer_epoch = producer_epoch
|
10
|
+
@transaction_result = transaction_result
|
11
|
+
end
|
12
|
+
|
13
|
+
def api_key
|
14
|
+
END_TXN_API
|
15
|
+
end
|
16
|
+
|
17
|
+
def response_class
|
18
|
+
EndTxnResposne
|
19
|
+
end
|
20
|
+
|
21
|
+
def encode(encoder)
|
22
|
+
encoder.write_string(@transactional_id)
|
23
|
+
encoder.write_int64(@producer_id)
|
24
|
+
encoder.write_int16(@producer_epoch)
|
25
|
+
encoder.write_boolean(@transaction_result)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kafka
|
4
|
+
module Protocol
|
5
|
+
class EndTxnResposne
|
6
|
+
attr_reader :error_code
|
7
|
+
|
8
|
+
def initialize(error_code:)
|
9
|
+
@error_code = error_code
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.decode(decoder)
|
13
|
+
_throttle_time_ms = decoder.int32
|
14
|
+
error_code = decoder.int16
|
15
|
+
new(error_code: error_code)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -7,10 +7,12 @@ module Kafka
|
|
7
7
|
#
|
8
8
|
# ## API Specification
|
9
9
|
#
|
10
|
-
# FetchRequest => ReplicaId MaxWaitTime MinBytes [TopicName [Partition FetchOffset MaxBytes]]
|
10
|
+
# FetchRequest => ReplicaId MaxWaitTime MinBytes MaxBytes IsolationLevel [TopicName [Partition FetchOffset MaxBytes]]
|
11
11
|
# ReplicaId => int32
|
12
12
|
# MaxWaitTime => int32
|
13
13
|
# MinBytes => int32
|
14
|
+
# MaxBytes => int32
|
15
|
+
# IsolationLevel => int8
|
14
16
|
# TopicName => string
|
15
17
|
# Partition => int32
|
16
18
|
# FetchOffset => int64
|
@@ -10,12 +10,18 @@ module Kafka
|
|
10
10
|
#
|
11
11
|
# ## API Specification
|
12
12
|
#
|
13
|
-
# FetchResponse => [TopicName [Partition ErrorCode HighwaterMarkOffset
|
13
|
+
# FetchResponse => ThrottleTimeMS [TopicName [Partition ErrorCode HighwaterMarkOffset LastStableOffset [AbortedTransaction] Records]]
|
14
|
+
# ThrottleTimeMS => int32
|
14
15
|
# TopicName => string
|
15
16
|
# Partition => int32
|
16
17
|
# ErrorCode => int16
|
17
18
|
# HighwaterMarkOffset => int64
|
19
|
+
# LastStableOffset => int64
|
18
20
|
# MessageSetSize => int32
|
21
|
+
# AbortedTransaction => [
|
22
|
+
# ProducerId => int64
|
23
|
+
# FirstOffset => int64
|
24
|
+
# ]
|
19
25
|
#
|
20
26
|
class FetchResponse
|
21
27
|
MAGIC_BYTE_OFFSET = 16
|
@@ -44,6 +50,15 @@ module Kafka
|
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
53
|
+
class AbortedTransaction
|
54
|
+
attr_reader :producer_id, :first_offset
|
55
|
+
|
56
|
+
def initialize(producer_id:, first_offset:)
|
57
|
+
@producer_id = producer_id
|
58
|
+
@first_offset = first_offset
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
47
62
|
attr_reader :topics
|
48
63
|
|
49
64
|
def initialize(topics: [], throttle_time_ms: 0)
|
@@ -66,32 +81,36 @@ module Kafka
|
|
66
81
|
aborted_transactions = decoder.array do
|
67
82
|
producer_id = decoder.int64
|
68
83
|
first_offset = decoder.int64
|
69
|
-
|
84
|
+
AbortedTransaction.new(
|
70
85
|
producer_id: producer_id,
|
71
86
|
first_offset: first_offset
|
72
|
-
|
87
|
+
)
|
73
88
|
end
|
74
89
|
|
75
|
-
|
90
|
+
messages_raw = decoder.bytes
|
76
91
|
messages = []
|
77
92
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
93
|
+
if !messages_raw.nil? && !messages_raw.empty?
|
94
|
+
messages_decoder = Decoder.from_string(messages_raw)
|
95
|
+
|
96
|
+
magic_byte = messages_decoder.peek(MAGIC_BYTE_OFFSET, MAGIC_BYTE_LENGTH)[0].to_i
|
97
|
+
if magic_byte == RecordBatch::MAGIC_BYTE
|
98
|
+
until messages_decoder.eof?
|
99
|
+
begin
|
100
|
+
record_batch = RecordBatch.decode(messages_decoder)
|
101
|
+
messages << record_batch
|
102
|
+
rescue InsufficientDataMessage
|
103
|
+
if messages.length > 0
|
104
|
+
break
|
105
|
+
else
|
106
|
+
raise
|
107
|
+
end
|
89
108
|
end
|
90
109
|
end
|
110
|
+
else
|
111
|
+
message_set = MessageSet.decode(messages_decoder)
|
112
|
+
messages << message_set
|
91
113
|
end
|
92
|
-
else
|
93
|
-
message_set = MessageSet.decode(messages_decoder)
|
94
|
-
messages = message_set.messages
|
95
114
|
end
|
96
115
|
|
97
116
|
FetchedPartition.new(
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kafka
|
4
|
+
module Protocol
|
5
|
+
class InitProducerIDRequest
|
6
|
+
def initialize(transactional_id: nil, transactional_timeout:)
|
7
|
+
@transactional_id = transactional_id
|
8
|
+
@transactional_timeout = transactional_timeout
|
9
|
+
end
|
10
|
+
|
11
|
+
def api_key
|
12
|
+
INIT_PRODUCER_ID_API
|
13
|
+
end
|
14
|
+
|
15
|
+
def response_class
|
16
|
+
InitProducerIDResponse
|
17
|
+
end
|
18
|
+
|
19
|
+
def encode(encoder)
|
20
|
+
encoder.write_string(@transactional_id)
|
21
|
+
# Timeout is in ms unit
|
22
|
+
encoder.write_int32(@transactional_timeout * 1000)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|