ruby-kafka 1.2.0 → 1.3.0
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/CHANGELOG.md +5 -0
- data/README.md +85 -1
- data/lib/kafka/async_producer.rb +19 -10
- data/lib/kafka/client.rb +5 -1
- data/lib/kafka/consumer_group.rb +14 -5
- data/lib/kafka/consumer_group/assignor.rb +63 -0
- data/lib/kafka/protocol/join_group_request.rb +2 -2
- data/lib/kafka/protocol/join_group_response.rb +9 -1
- data/lib/kafka/round_robin_assignment_strategy.rb +15 -38
- data/lib/kafka/transaction_manager.rb +13 -8
- data/lib/kafka/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b16e9e52014e784610725bb2ba5c5a431694ce6da7878b6c1ff024504d6ef6c4
|
4
|
+
data.tar.gz: '039026011e9cd5e5dce59aed4f53b2a8970aa8025fe3b008620bdef8a802bbf5'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d13c2032f4bd38e09a714fe40d59467ff61b2c51e0a604daf7f9e1a3431af32f77e62433b863862a68e6bc74f53ff4eceed7e88145acc102613ac6fa2600b9f
|
7
|
+
data.tar.gz: f424e6e2bee5f318766880f3b7eb338ce706783ea647908902a8a6d409fcf8f9ac3f0afe7ca8a932710a9d8f192e44a25a346da6639861fb7d377f3dd3257309
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -26,6 +26,7 @@ A Ruby client library for [Apache Kafka](http://kafka.apache.org/), a distribute
|
|
26
26
|
4. [Shutting Down a Consumer](#shutting-down-a-consumer)
|
27
27
|
5. [Consuming Messages in Batches](#consuming-messages-in-batches)
|
28
28
|
6. [Balancing Throughput and Latency](#balancing-throughput-and-latency)
|
29
|
+
7. [Customizing Partition Assignment Strategy](#customizing-partition-assignment-strategy)
|
29
30
|
4. [Thread Safety](#thread-safety)
|
30
31
|
5. [Logging](#logging)
|
31
32
|
6. [Instrumentation](#instrumentation)
|
@@ -743,6 +744,88 @@ consumer.each_message do |message|
|
|
743
744
|
end
|
744
745
|
```
|
745
746
|
|
747
|
+
#### Customizing Partition Assignment Strategy
|
748
|
+
|
749
|
+
In some cases, you might want to assign more partitions to some consumers. For example, in applications inserting some records to a database, the consumers running on hosts nearby the database can process more messages than the consumers running on other hosts.
|
750
|
+
You can use a custom assignment strategy by passing an object that implements `#call` as the argument `assignment_strategy` like below:
|
751
|
+
|
752
|
+
```ruby
|
753
|
+
class CustomAssignmentStrategy
|
754
|
+
def initialize(user_data)
|
755
|
+
@user_data = user_data
|
756
|
+
end
|
757
|
+
|
758
|
+
# Assign the topic partitions to the group members.
|
759
|
+
#
|
760
|
+
# @param cluster [Kafka::Cluster]
|
761
|
+
# @param members [Hash<String, Kafka::Protocol::JoinGroupResponse::Metadata>] a hash
|
762
|
+
# mapping member ids to metadata
|
763
|
+
# @param partitions [Array<Kafka::ConsumerGroup::Assignor::Partition>] a list of
|
764
|
+
# partitions the consumer group processes
|
765
|
+
# @return [Hash<String, Array<Kafka::ConsumerGroup::Assignor::Partition>] a hash
|
766
|
+
# mapping member ids to partitions.
|
767
|
+
def call(cluster:, members:, partitions:)
|
768
|
+
...
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
strategy = CustomAssignmentStrategy.new("some-host-information")
|
773
|
+
consumer = kafka.consumer(group_id: "some-group", assignment_strategy: strategy)
|
774
|
+
```
|
775
|
+
|
776
|
+
`members` is a hash mapping member IDs to metadata, and partitions is a list of partitions the consumer group processes. The method `call` must return a hash mapping member IDs to partitions. For example, the following strategy assigns partitions randomly:
|
777
|
+
|
778
|
+
```ruby
|
779
|
+
class RandomAssignmentStrategy
|
780
|
+
def call(cluster:, members:, partitions:)
|
781
|
+
member_ids = members.keys
|
782
|
+
partitions.each_with_object(Hash.new {|h, k| h[k] = [] }) do |partition, partitions_per_member|
|
783
|
+
partitions_per_member[member_ids[rand(member_ids.count)]] << partition
|
784
|
+
end
|
785
|
+
end
|
786
|
+
end
|
787
|
+
```
|
788
|
+
|
789
|
+
If the strategy needs user data, you should define the method `user_data` that returns user data on each consumer. For example, the following strategy uses the consumers' IP addresses as user data:
|
790
|
+
|
791
|
+
```ruby
|
792
|
+
class NetworkTopologyAssignmentStrategy
|
793
|
+
def user_data
|
794
|
+
Socket.ip_address_list.find(&:ipv4_private?).ip_address
|
795
|
+
end
|
796
|
+
|
797
|
+
def call(cluster:, members:, partitions:)
|
798
|
+
# Display the pair of the member ID and IP address
|
799
|
+
members.each do |id, metadata|
|
800
|
+
puts "#{id}: #{metadata.user_data}"
|
801
|
+
end
|
802
|
+
|
803
|
+
# Assign partitions considering the network topology
|
804
|
+
...
|
805
|
+
end
|
806
|
+
end
|
807
|
+
```
|
808
|
+
|
809
|
+
Note that the strategy uses the class name as the default protocol name. You can change it by defining the method `protocol_name`:
|
810
|
+
|
811
|
+
```ruby
|
812
|
+
class NetworkTopologyAssignmentStrategy
|
813
|
+
def protocol_name
|
814
|
+
"networktopology"
|
815
|
+
end
|
816
|
+
|
817
|
+
def user_data
|
818
|
+
Socket.ip_address_list.find(&:ipv4_private?).ip_address
|
819
|
+
end
|
820
|
+
|
821
|
+
def call(cluster:, members:, partitions:)
|
822
|
+
...
|
823
|
+
end
|
824
|
+
end
|
825
|
+
```
|
826
|
+
|
827
|
+
As the method `call` might receive different user data from what it expects, you should avoid using the same protocol name as another strategy that uses different user data.
|
828
|
+
|
746
829
|
|
747
830
|
### Thread Safety
|
748
831
|
|
@@ -972,7 +1055,7 @@ This configures the store to look up CA certificates from the system default cer
|
|
972
1055
|
|
973
1056
|
In order to authenticate the client to the cluster, you need to pass in a certificate and key created for the client and trusted by the brokers.
|
974
1057
|
|
975
|
-
**NOTE**: You can disable hostname validation by passing `
|
1058
|
+
**NOTE**: You can disable hostname validation by passing `ssl_verify_hostname: false`.
|
976
1059
|
|
977
1060
|
```ruby
|
978
1061
|
kafka = Kafka.new(
|
@@ -981,6 +1064,7 @@ kafka = Kafka.new(
|
|
981
1064
|
ssl_client_cert: File.read('my_client_cert.pem'),
|
982
1065
|
ssl_client_cert_key: File.read('my_client_cert_key.pem'),
|
983
1066
|
ssl_client_cert_key_password: 'my_client_cert_key_password',
|
1067
|
+
ssl_verify_hostname: false,
|
984
1068
|
# ...
|
985
1069
|
)
|
986
1070
|
```
|
data/lib/kafka/async_producer.rb
CHANGED
@@ -59,8 +59,6 @@ module Kafka
|
|
59
59
|
# producer.shutdown
|
60
60
|
#
|
61
61
|
class AsyncProducer
|
62
|
-
THREAD_MUTEX = Mutex.new
|
63
|
-
|
64
62
|
# Initializes a new AsyncProducer.
|
65
63
|
#
|
66
64
|
# @param sync_producer [Kafka::Producer] the synchronous producer that should
|
@@ -94,6 +92,8 @@ module Kafka
|
|
94
92
|
|
95
93
|
# The timer will no-op if the delivery interval is zero.
|
96
94
|
@timer = Timer.new(queue: @queue, interval: delivery_interval)
|
95
|
+
|
96
|
+
@thread_mutex = Mutex.new
|
97
97
|
end
|
98
98
|
|
99
99
|
# Produces a message to the specified topic.
|
@@ -131,6 +131,8 @@ module Kafka
|
|
131
131
|
# @see Kafka::Producer#deliver_messages
|
132
132
|
# @return [nil]
|
133
133
|
def deliver_messages
|
134
|
+
ensure_threads_running!
|
135
|
+
|
134
136
|
@queue << [:deliver_messages, nil]
|
135
137
|
|
136
138
|
nil
|
@@ -142,6 +144,8 @@ module Kafka
|
|
142
144
|
# @see Kafka::Producer#shutdown
|
143
145
|
# @return [nil]
|
144
146
|
def shutdown
|
147
|
+
ensure_threads_running!
|
148
|
+
|
145
149
|
@timer_thread && @timer_thread.exit
|
146
150
|
@queue << [:shutdown, nil]
|
147
151
|
@worker_thread && @worker_thread.join
|
@@ -152,17 +156,22 @@ module Kafka
|
|
152
156
|
private
|
153
157
|
|
154
158
|
def ensure_threads_running!
|
155
|
-
|
156
|
-
@worker_thread = nil unless @worker_thread && @worker_thread.alive?
|
157
|
-
@worker_thread ||= Thread.new { @worker.run }
|
158
|
-
end
|
159
|
+
return if worker_thread_alive? && timer_thread_alive?
|
159
160
|
|
160
|
-
|
161
|
-
@
|
162
|
-
@timer_thread
|
161
|
+
@thread_mutex.synchronize do
|
162
|
+
@worker_thread = Thread.new { @worker.run } unless worker_thread_alive?
|
163
|
+
@timer_thread = Thread.new { @timer.run } unless timer_thread_alive?
|
163
164
|
end
|
164
165
|
end
|
165
166
|
|
167
|
+
def worker_thread_alive?
|
168
|
+
!!@worker_thread && @worker_thread.alive?
|
169
|
+
end
|
170
|
+
|
171
|
+
def timer_thread_alive?
|
172
|
+
!!@timer_thread && @timer_thread.alive?
|
173
|
+
end
|
174
|
+
|
166
175
|
def buffer_overflow(topic, message)
|
167
176
|
@instrumenter.instrument("buffer_overflow.async_producer", {
|
168
177
|
topic: topic,
|
@@ -208,7 +217,7 @@ module Kafka
|
|
208
217
|
|
209
218
|
case operation
|
210
219
|
when :produce
|
211
|
-
produce(
|
220
|
+
produce(payload[0], **payload[1])
|
212
221
|
deliver_messages if threshold_reached?
|
213
222
|
when :deliver_messages
|
214
223
|
deliver_messages
|
data/lib/kafka/client.rb
CHANGED
@@ -357,6 +357,8 @@ module Kafka
|
|
357
357
|
# If it is n (n > 0), the topic list will be refreshed every n seconds
|
358
358
|
# @param interceptors [Array<Object>] a list of consumer interceptors that implement
|
359
359
|
# `call(Kafka::FetchedBatch)`.
|
360
|
+
# @param assignment_strategy [Object] a partition assignment strategy that
|
361
|
+
# implements `protocol_type()`, `user_data()`, and `assign(members:, partitions:)`
|
360
362
|
# @return [Consumer]
|
361
363
|
def consumer(
|
362
364
|
group_id:,
|
@@ -368,7 +370,8 @@ module Kafka
|
|
368
370
|
offset_retention_time: nil,
|
369
371
|
fetcher_max_queue_size: 100,
|
370
372
|
refresh_topic_interval: 0,
|
371
|
-
interceptors: []
|
373
|
+
interceptors: [],
|
374
|
+
assignment_strategy: nil
|
372
375
|
)
|
373
376
|
cluster = initialize_cluster
|
374
377
|
|
@@ -387,6 +390,7 @@ module Kafka
|
|
387
390
|
rebalance_timeout: rebalance_timeout,
|
388
391
|
retention_time: retention_time,
|
389
392
|
instrumenter: instrumenter,
|
393
|
+
assignment_strategy: assignment_strategy
|
390
394
|
)
|
391
395
|
|
392
396
|
fetcher = Fetcher.new(
|
data/lib/kafka/consumer_group.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "set"
|
4
|
+
require "kafka/consumer_group/assignor"
|
4
5
|
require "kafka/round_robin_assignment_strategy"
|
5
6
|
|
6
7
|
module Kafka
|
7
8
|
class ConsumerGroup
|
8
9
|
attr_reader :assigned_partitions, :generation_id, :group_id
|
9
10
|
|
10
|
-
def initialize(cluster:, logger:, group_id:, session_timeout:, rebalance_timeout:, retention_time:, instrumenter:)
|
11
|
+
def initialize(cluster:, logger:, group_id:, session_timeout:, rebalance_timeout:, retention_time:, instrumenter:, assignment_strategy:)
|
11
12
|
@cluster = cluster
|
12
13
|
@logger = TaggedLogger.new(logger)
|
13
14
|
@group_id = group_id
|
@@ -19,7 +20,10 @@ module Kafka
|
|
19
20
|
@members = {}
|
20
21
|
@topics = Set.new
|
21
22
|
@assigned_partitions = {}
|
22
|
-
@
|
23
|
+
@assignor = Assignor.new(
|
24
|
+
cluster: cluster,
|
25
|
+
strategy: assignment_strategy || RoundRobinAssignmentStrategy.new
|
26
|
+
)
|
23
27
|
@retention_time = retention_time
|
24
28
|
end
|
25
29
|
|
@@ -113,9 +117,12 @@ module Kafka
|
|
113
117
|
|
114
118
|
Protocol.handle_error(response.error_code)
|
115
119
|
end
|
116
|
-
rescue ConnectionError, UnknownMemberId,
|
120
|
+
rescue ConnectionError, UnknownMemberId, IllegalGeneration => e
|
117
121
|
@logger.error "Error sending heartbeat: #{e}"
|
118
122
|
raise HeartbeatError, e
|
123
|
+
rescue RebalanceInProgress => e
|
124
|
+
@logger.warn "Error sending heartbeat: #{e}"
|
125
|
+
raise HeartbeatError, e
|
119
126
|
rescue NotCoordinatorForGroup
|
120
127
|
@logger.error "Failed to find coordinator for group `#{@group_id}`; retrying..."
|
121
128
|
sleep 1
|
@@ -144,6 +151,8 @@ module Kafka
|
|
144
151
|
rebalance_timeout: @rebalance_timeout,
|
145
152
|
member_id: @member_id,
|
146
153
|
topics: @topics,
|
154
|
+
protocol_name: @assignor.protocol_name,
|
155
|
+
user_data: @assignor.user_data,
|
147
156
|
)
|
148
157
|
|
149
158
|
Protocol.handle_error(response.error_code)
|
@@ -180,8 +189,8 @@ module Kafka
|
|
180
189
|
if group_leader?
|
181
190
|
@logger.info "Chosen as leader of group `#{@group_id}`"
|
182
191
|
|
183
|
-
group_assignment = @
|
184
|
-
members: @members
|
192
|
+
group_assignment = @assignor.assign(
|
193
|
+
members: @members,
|
185
194
|
topics: @topics,
|
186
195
|
)
|
187
196
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "kafka/protocol/member_assignment"
|
4
|
+
|
5
|
+
module Kafka
|
6
|
+
class ConsumerGroup
|
7
|
+
|
8
|
+
# A consumer group partition assignor
|
9
|
+
class Assignor
|
10
|
+
Partition = Struct.new(:topic, :partition_id)
|
11
|
+
|
12
|
+
# @param cluster [Kafka::Cluster]
|
13
|
+
# @param strategy [Object] an object that implements #protocol_type,
|
14
|
+
# #user_data, and #assign.
|
15
|
+
def initialize(cluster:, strategy:)
|
16
|
+
@cluster = cluster
|
17
|
+
@strategy = strategy
|
18
|
+
end
|
19
|
+
|
20
|
+
def protocol_name
|
21
|
+
@strategy.respond_to?(:protocol_name) ? @strategy.protocol_name : @strategy.class.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def user_data
|
25
|
+
@strategy.user_data if @strategy.respond_to?(:user_data)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Assign the topic partitions to the group members.
|
29
|
+
#
|
30
|
+
# @param members [Hash<String, Kafka::Protocol::JoinGroupResponse::Metadata>] a hash
|
31
|
+
# mapping member ids to metadata.
|
32
|
+
# @param topics [Array<String>] topics
|
33
|
+
# @return [Hash<String, Kafka::Protocol::MemberAssignment>] a hash mapping member
|
34
|
+
# ids to assignments.
|
35
|
+
def assign(members:, topics:)
|
36
|
+
topic_partitions = topics.flat_map do |topic|
|
37
|
+
begin
|
38
|
+
partition_ids = @cluster.partitions_for(topic).map(&:partition_id)
|
39
|
+
rescue UnknownTopicOrPartition
|
40
|
+
raise UnknownTopicOrPartition, "unknown topic #{topic}"
|
41
|
+
end
|
42
|
+
partition_ids.map {|partition_id| Partition.new(topic, partition_id) }
|
43
|
+
end
|
44
|
+
|
45
|
+
group_assignment = {}
|
46
|
+
|
47
|
+
members.each_key do |member_id|
|
48
|
+
group_assignment[member_id] = Protocol::MemberAssignment.new
|
49
|
+
end
|
50
|
+
@strategy.call(cluster: @cluster, members: members, partitions: topic_partitions).each do |member_id, partitions|
|
51
|
+
Array(partitions).each do |partition|
|
52
|
+
group_assignment[member_id].assign(partition.topic, [partition.partition_id])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
group_assignment
|
57
|
+
rescue Kafka::LeaderNotAvailable
|
58
|
+
sleep 1
|
59
|
+
retry
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -7,14 +7,14 @@ module Kafka
|
|
7
7
|
class JoinGroupRequest
|
8
8
|
PROTOCOL_TYPE = "consumer"
|
9
9
|
|
10
|
-
def initialize(group_id:, session_timeout:, rebalance_timeout:, member_id:, topics: [])
|
10
|
+
def initialize(group_id:, session_timeout:, rebalance_timeout:, member_id:, topics: [], protocol_name:, user_data: nil)
|
11
11
|
@group_id = group_id
|
12
12
|
@session_timeout = session_timeout * 1000 # Kafka wants ms.
|
13
13
|
@rebalance_timeout = rebalance_timeout * 1000 # Kafka wants ms.
|
14
14
|
@member_id = member_id || ""
|
15
15
|
@protocol_type = PROTOCOL_TYPE
|
16
16
|
@group_protocols = {
|
17
|
-
|
17
|
+
protocol_name => ConsumerGroupProtocol.new(topics: topics, user_data: user_data),
|
18
18
|
}
|
19
19
|
end
|
20
20
|
|
@@ -3,6 +3,8 @@
|
|
3
3
|
module Kafka
|
4
4
|
module Protocol
|
5
5
|
class JoinGroupResponse
|
6
|
+
Metadata = Struct.new(:version, :topics, :user_data)
|
7
|
+
|
6
8
|
attr_reader :error_code
|
7
9
|
|
8
10
|
attr_reader :generation_id, :group_protocol
|
@@ -25,7 +27,13 @@ module Kafka
|
|
25
27
|
group_protocol: decoder.string,
|
26
28
|
leader_id: decoder.string,
|
27
29
|
member_id: decoder.string,
|
28
|
-
members: Hash[
|
30
|
+
members: Hash[
|
31
|
+
decoder.array do
|
32
|
+
member_id = decoder.string
|
33
|
+
d = Decoder.from_string(decoder.bytes)
|
34
|
+
[member_id, Metadata.new(d.int16, d.array { d.string }, d.bytes)]
|
35
|
+
end
|
36
|
+
],
|
29
37
|
)
|
30
38
|
end
|
31
39
|
end
|
@@ -1,54 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "kafka/protocol/member_assignment"
|
4
|
-
|
5
3
|
module Kafka
|
6
4
|
|
7
5
|
# A consumer group partition assignment strategy that assigns partitions to
|
8
6
|
# consumers in a round-robin fashion.
|
9
7
|
class RoundRobinAssignmentStrategy
|
10
|
-
def
|
11
|
-
|
8
|
+
def protocol_name
|
9
|
+
"roundrobin"
|
12
10
|
end
|
13
11
|
|
14
12
|
# Assign the topic partitions to the group members.
|
15
13
|
#
|
16
|
-
# @param
|
17
|
-
# @param
|
18
|
-
#
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
begin
|
29
|
-
partitions = @cluster.partitions_for(topic).map(&:partition_id)
|
30
|
-
rescue UnknownTopicOrPartition
|
31
|
-
raise UnknownTopicOrPartition, "unknown topic #{topic}"
|
32
|
-
end
|
33
|
-
Array.new(partitions.count) { topic }.zip(partitions)
|
34
|
-
end
|
35
|
-
|
36
|
-
partitions_per_member = topic_partitions.group_by.with_index do |_, index|
|
37
|
-
index % members.count
|
38
|
-
end.values
|
39
|
-
|
40
|
-
members.zip(partitions_per_member).each do |member_id, member_partitions|
|
41
|
-
unless member_partitions.nil?
|
42
|
-
member_partitions.each do |topic, partition|
|
43
|
-
group_assignment[member_id].assign(topic, [partition])
|
44
|
-
end
|
45
|
-
end
|
14
|
+
# @param cluster [Kafka::Cluster]
|
15
|
+
# @param members [Hash<String, Kafka::Protocol::JoinGroupResponse::Metadata>] a hash
|
16
|
+
# mapping member ids to metadata
|
17
|
+
# @param partitions [Array<Kafka::ConsumerGroup::Assignor::Partition>] a list of
|
18
|
+
# partitions the consumer group processes
|
19
|
+
# @return [Hash<String, Array<Kafka::ConsumerGroup::Assignor::Partition>] a hash
|
20
|
+
# mapping member ids to partitions.
|
21
|
+
def call(cluster:, members:, partitions:)
|
22
|
+
member_ids = members.keys
|
23
|
+
partitions_per_member = Hash.new {|h, k| h[k] = [] }
|
24
|
+
partitions.each_with_index do |partition, index|
|
25
|
+
partitions_per_member[member_ids[index % member_ids.count]] << partition
|
46
26
|
end
|
47
27
|
|
48
|
-
|
49
|
-
rescue Kafka::LeaderNotAvailable
|
50
|
-
sleep 1
|
51
|
-
retry
|
28
|
+
partitions_per_member
|
52
29
|
end
|
53
30
|
end
|
54
31
|
end
|
@@ -95,7 +95,7 @@ module Kafka
|
|
95
95
|
force_transactional!
|
96
96
|
|
97
97
|
if @transaction_state.uninitialized?
|
98
|
-
raise 'Transaction is uninitialized'
|
98
|
+
raise Kafka::InvalidTxnStateError, 'Transaction is uninitialized'
|
99
99
|
end
|
100
100
|
|
101
101
|
# Extract newly created partitions
|
@@ -138,8 +138,8 @@ module Kafka
|
|
138
138
|
|
139
139
|
def begin_transaction
|
140
140
|
force_transactional!
|
141
|
-
raise 'Transaction has already started' if @transaction_state.in_transaction?
|
142
|
-
raise 'Transaction is not ready' unless @transaction_state.ready?
|
141
|
+
raise Kafka::InvalidTxnStateError, 'Transaction has already started' if @transaction_state.in_transaction?
|
142
|
+
raise Kafka::InvalidTxnStateError, 'Transaction is not ready' unless @transaction_state.ready?
|
143
143
|
@transaction_state.transition_to!(TransactionStateMachine::IN_TRANSACTION)
|
144
144
|
|
145
145
|
@logger.info "Begin transaction #{@transactional_id}, Producer ID: #{@producer_id} (Epoch #{@producer_epoch})"
|
@@ -159,7 +159,7 @@ module Kafka
|
|
159
159
|
end
|
160
160
|
|
161
161
|
unless @transaction_state.in_transaction?
|
162
|
-
raise 'Transaction is not valid to commit'
|
162
|
+
raise Kafka::InvalidTxnStateError, 'Transaction is not valid to commit'
|
163
163
|
end
|
164
164
|
|
165
165
|
@transaction_state.transition_to!(TransactionStateMachine::COMMITTING_TRANSACTION)
|
@@ -192,7 +192,8 @@ module Kafka
|
|
192
192
|
end
|
193
193
|
|
194
194
|
unless @transaction_state.in_transaction?
|
195
|
-
|
195
|
+
@logger.warn('Aborting transaction that was never opened on brokers')
|
196
|
+
return
|
196
197
|
end
|
197
198
|
|
198
199
|
@transaction_state.transition_to!(TransactionStateMachine::ABORTING_TRANSACTION)
|
@@ -221,7 +222,7 @@ module Kafka
|
|
221
222
|
force_transactional!
|
222
223
|
|
223
224
|
unless @transaction_state.in_transaction?
|
224
|
-
raise 'Transaction is not valid to send offsets'
|
225
|
+
raise Kafka::InvalidTxnStateError, 'Transaction is not valid to send offsets'
|
225
226
|
end
|
226
227
|
|
227
228
|
add_response = transaction_coordinator.add_offsets_to_txn(
|
@@ -250,6 +251,10 @@ module Kafka
|
|
250
251
|
@transaction_state.error?
|
251
252
|
end
|
252
253
|
|
254
|
+
def ready?
|
255
|
+
@transaction_state.ready?
|
256
|
+
end
|
257
|
+
|
253
258
|
def close
|
254
259
|
if in_transaction?
|
255
260
|
@logger.warn("Aborting pending transaction ...")
|
@@ -264,11 +269,11 @@ module Kafka
|
|
264
269
|
|
265
270
|
def force_transactional!
|
266
271
|
unless transactional?
|
267
|
-
raise 'Please turn on transactional mode to use transaction'
|
272
|
+
raise Kafka::InvalidTxnStateError, 'Please turn on transactional mode to use transaction'
|
268
273
|
end
|
269
274
|
|
270
275
|
if @transactional_id.nil? || @transactional_id.empty?
|
271
|
-
raise 'Please provide a transaction_id to use transactional mode'
|
276
|
+
raise Kafka::InvalidTxnStateError, 'Please provide a transaction_id to use transactional mode'
|
272
277
|
end
|
273
278
|
end
|
274
279
|
|
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: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Schierbeck
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest-crc
|
@@ -376,6 +376,7 @@ files:
|
|
376
376
|
- lib/kafka/connection_builder.rb
|
377
377
|
- lib/kafka/consumer.rb
|
378
378
|
- lib/kafka/consumer_group.rb
|
379
|
+
- lib/kafka/consumer_group/assignor.rb
|
379
380
|
- lib/kafka/datadog.rb
|
380
381
|
- lib/kafka/fetch_operation.rb
|
381
382
|
- lib/kafka/fetched_batch.rb
|
@@ -494,8 +495,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
494
495
|
- !ruby/object:Gem::Version
|
495
496
|
version: '0'
|
496
497
|
requirements: []
|
497
|
-
|
498
|
-
rubygems_version: 2.7.6
|
498
|
+
rubygems_version: 3.1.2
|
499
499
|
signing_key:
|
500
500
|
specification_version: 4
|
501
501
|
summary: A client library for the Kafka distributed commit log.
|