ruby-kafka 1.1.0.beta1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +111 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +21 -0
- data/README.md +141 -0
- data/lib/kafka/async_producer.rb +57 -42
- data/lib/kafka/client.rb +41 -7
- data/lib/kafka/cluster.rb +30 -24
- data/lib/kafka/consumer.rb +8 -3
- data/lib/kafka/consumer_group/assignor.rb +63 -0
- data/lib/kafka/consumer_group.rb +26 -6
- data/lib/kafka/crc32_hash.rb +15 -0
- data/lib/kafka/datadog.rb +12 -3
- data/lib/kafka/digest.rb +22 -0
- data/lib/kafka/interceptors.rb +33 -0
- data/lib/kafka/murmur2_hash.rb +17 -0
- data/lib/kafka/offset_manager.rb +12 -1
- data/lib/kafka/partitioner.rb +8 -3
- data/lib/kafka/producer.rb +9 -4
- data/lib/kafka/protocol/add_offsets_to_txn_response.rb +2 -0
- data/lib/kafka/protocol/encoder.rb +1 -1
- data/lib/kafka/protocol/join_group_request.rb +2 -2
- data/lib/kafka/protocol/join_group_response.rb +9 -1
- data/lib/kafka/protocol/record_batch.rb +2 -2
- data/lib/kafka/protocol/sync_group_response.rb +5 -2
- data/lib/kafka/protocol/txn_offset_commit_response.rb +34 -5
- data/lib/kafka/round_robin_assignment_strategy.rb +37 -39
- data/lib/kafka/ssl_context.rb +6 -5
- data/lib/kafka/transaction_manager.rb +30 -10
- data/lib/kafka/version.rb +1 -1
- data/ruby-kafka.gemspec +2 -1
- metadata +25 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd754956b907d18ac2a17b3d8f5bc374a8851dea21826277b8941f163403161d
|
4
|
+
data.tar.gz: 7a1b74d4c3d3f8cfb0772bd6521a8a5c726bc1efc5ade83f814802ce08fb0f24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45038342acd388b7b797b64261addb3bcd90f43fb27d9dfe43a5071f5cb2a5ab6e25df1557fcadd528c5c40056384183cdd427d4fdac66de5cb93c67d246d45f
|
7
|
+
data.tar.gz: af738b6a58f1cc7d1ee8c0fda72edad9fc94eb21c409f8b48a935a990b563736d30060ea209ef25d11f46ef2252f0d4adae726455c295637c2ef71522473c712
|
data/.circleci/config.yml
CHANGED
@@ -7,6 +7,7 @@ jobs:
|
|
7
7
|
LOG_LEVEL: DEBUG
|
8
8
|
steps:
|
9
9
|
- checkout
|
10
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
10
11
|
- run: bundle install --path vendor/bundle
|
11
12
|
- run: bundle exec rspec
|
12
13
|
- run: bundle exec rubocop
|
@@ -40,6 +41,7 @@ jobs:
|
|
40
41
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
41
42
|
steps:
|
42
43
|
- checkout
|
44
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
43
45
|
- run: bundle install --path vendor/bundle
|
44
46
|
- run: bundle exec rspec --profile --tag functional spec/functional
|
45
47
|
|
@@ -72,6 +74,7 @@ jobs:
|
|
72
74
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
73
75
|
steps:
|
74
76
|
- checkout
|
77
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
75
78
|
- run: bundle install --path vendor/bundle
|
76
79
|
- run: bundle exec rspec --profile --tag functional spec/functional
|
77
80
|
|
@@ -104,6 +107,7 @@ jobs:
|
|
104
107
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
105
108
|
steps:
|
106
109
|
- checkout
|
110
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
107
111
|
- run: bundle install --path vendor/bundle
|
108
112
|
- run: bundle exec rspec --profile --tag functional spec/functional
|
109
113
|
|
@@ -136,6 +140,7 @@ jobs:
|
|
136
140
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
137
141
|
steps:
|
138
142
|
- checkout
|
143
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
139
144
|
- run: bundle install --path vendor/bundle
|
140
145
|
- run: bundle exec rspec --profile --tag functional spec/functional
|
141
146
|
|
@@ -168,6 +173,7 @@ jobs:
|
|
168
173
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
169
174
|
steps:
|
170
175
|
- checkout
|
176
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
171
177
|
- run: bundle install --path vendor/bundle
|
172
178
|
- run: bundle exec rspec --profile --tag functional spec/functional
|
173
179
|
|
@@ -200,6 +206,7 @@ jobs:
|
|
200
206
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
201
207
|
steps:
|
202
208
|
- checkout
|
209
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
203
210
|
- run: bundle install --path vendor/bundle
|
204
211
|
- run: bundle exec rspec --profile --tag functional spec/functional
|
205
212
|
|
@@ -232,6 +239,7 @@ jobs:
|
|
232
239
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
233
240
|
steps:
|
234
241
|
- checkout
|
242
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
235
243
|
- run: bundle install --path vendor/bundle
|
236
244
|
- run: bundle exec rspec --profile --tag functional spec/functional
|
237
245
|
|
@@ -264,6 +272,106 @@ jobs:
|
|
264
272
|
KAFKA_DELETE_TOPIC_ENABLE: true
|
265
273
|
steps:
|
266
274
|
- checkout
|
275
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
276
|
+
- run: bundle install --path vendor/bundle
|
277
|
+
- run: bundle exec rspec --profile --tag functional spec/functional
|
278
|
+
|
279
|
+
kafka-2.5:
|
280
|
+
docker:
|
281
|
+
- image: circleci/ruby:2.5.1-node
|
282
|
+
environment:
|
283
|
+
LOG_LEVEL: DEBUG
|
284
|
+
- image: wurstmeister/zookeeper
|
285
|
+
- image: wurstmeister/kafka:2.12-2.5.0
|
286
|
+
environment:
|
287
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
288
|
+
KAFKA_ADVERTISED_PORT: 9092
|
289
|
+
KAFKA_PORT: 9092
|
290
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
291
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
292
|
+
- image: wurstmeister/kafka:2.12-2.5.0
|
293
|
+
environment:
|
294
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
295
|
+
KAFKA_ADVERTISED_PORT: 9093
|
296
|
+
KAFKA_PORT: 9093
|
297
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
298
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
299
|
+
- image: wurstmeister/kafka:2.12-2.5.0
|
300
|
+
environment:
|
301
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
302
|
+
KAFKA_ADVERTISED_PORT: 9094
|
303
|
+
KAFKA_PORT: 9094
|
304
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
305
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
306
|
+
steps:
|
307
|
+
- checkout
|
308
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
309
|
+
- run: bundle install --path vendor/bundle
|
310
|
+
- run: bundle exec rspec --profile --tag functional spec/functional
|
311
|
+
|
312
|
+
kafka-2.6:
|
313
|
+
docker:
|
314
|
+
- image: circleci/ruby:2.5.1-node
|
315
|
+
environment:
|
316
|
+
LOG_LEVEL: DEBUG
|
317
|
+
- image: wurstmeister/zookeeper
|
318
|
+
- image: wurstmeister/kafka:2.13-2.6.0
|
319
|
+
environment:
|
320
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
321
|
+
KAFKA_ADVERTISED_PORT: 9092
|
322
|
+
KAFKA_PORT: 9092
|
323
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
324
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
325
|
+
- image: wurstmeister/kafka:2.13-2.6.0
|
326
|
+
environment:
|
327
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
328
|
+
KAFKA_ADVERTISED_PORT: 9093
|
329
|
+
KAFKA_PORT: 9093
|
330
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
331
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
332
|
+
- image: wurstmeister/kafka:2.13-2.6.0
|
333
|
+
environment:
|
334
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
335
|
+
KAFKA_ADVERTISED_PORT: 9094
|
336
|
+
KAFKA_PORT: 9094
|
337
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
338
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
339
|
+
steps:
|
340
|
+
- checkout
|
341
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
342
|
+
- run: bundle install --path vendor/bundle
|
343
|
+
- run: bundle exec rspec --profile --tag functional spec/functional
|
344
|
+
|
345
|
+
kafka-2.7:
|
346
|
+
docker:
|
347
|
+
- image: circleci/ruby:2.5.1-node
|
348
|
+
environment:
|
349
|
+
LOG_LEVEL: DEBUG
|
350
|
+
- image: wurstmeister/zookeeper
|
351
|
+
- image: wurstmeister/kafka:2.13-2.7.0
|
352
|
+
environment:
|
353
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
354
|
+
KAFKA_ADVERTISED_PORT: 9092
|
355
|
+
KAFKA_PORT: 9092
|
356
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
357
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
358
|
+
- image: wurstmeister/kafka:2.13-2.7.0
|
359
|
+
environment:
|
360
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
361
|
+
KAFKA_ADVERTISED_PORT: 9093
|
362
|
+
KAFKA_PORT: 9093
|
363
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
364
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
365
|
+
- image: wurstmeister/kafka:2.13-2.7.0
|
366
|
+
environment:
|
367
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
368
|
+
KAFKA_ADVERTISED_PORT: 9094
|
369
|
+
KAFKA_PORT: 9094
|
370
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
371
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
372
|
+
steps:
|
373
|
+
- checkout
|
374
|
+
- run: sudo apt-get update && sudo apt-get install -y cmake # For installing snappy
|
267
375
|
- run: bundle install --path vendor/bundle
|
268
376
|
- run: bundle exec rspec --profile --tag functional spec/functional
|
269
377
|
|
@@ -280,3 +388,6 @@ workflows:
|
|
280
388
|
- kafka-2.2
|
281
389
|
- kafka-2.3
|
282
390
|
- kafka-2.4
|
391
|
+
- kafka-2.5
|
392
|
+
- kafka-2.6
|
393
|
+
- kafka-2.7
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.7.1
|
data/CHANGELOG.md
CHANGED
@@ -4,8 +4,29 @@ Changes and additions to the library will be listed here.
|
|
4
4
|
|
5
5
|
## Unreleased
|
6
6
|
|
7
|
+
## 1.4.0
|
8
|
+
|
9
|
+
- Refresh a stale cluster's metadata if necessary on `Kafka::Client#deliver_message` (#901).
|
10
|
+
- Fix `Kafka::TransactionManager#send_offsets_to_txn` (#866).
|
11
|
+
- Add support for `murmur2` based partitioning.
|
12
|
+
- Add `resolve_seed_brokers` option to support seed brokers' hostname with multiple addresses (#877).
|
13
|
+
- Handle SyncGroup responses with a non-zero error and no assignments (#896).
|
14
|
+
- Add support for non-identical topic subscriptions within the same consumer group (#525 / #764).
|
15
|
+
|
16
|
+
## 1.3.0
|
17
|
+
|
18
|
+
- Support custom assignment strategy (#846).
|
19
|
+
- Improved Exceptions in TransactionManager (#862).
|
20
|
+
|
21
|
+
## 1.2.0
|
22
|
+
|
23
|
+
- Add producer consumer interceptors (#837).
|
24
|
+
- Add support for configuring the client partitioner (#848).
|
25
|
+
|
7
26
|
## 1.1.0
|
8
27
|
|
28
|
+
- Extra sanity checking when marking offsets as processed (#824).
|
29
|
+
- Make `verify_hostname` settable for SSL contexts (#828).
|
9
30
|
- Instrument `create_time` from last message in batch (#811).
|
10
31
|
- Add client function for fetching topic replica count (#822).
|
11
32
|
- Allow consumers to refresh the topic lists (#818).
|
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)
|
@@ -118,10 +119,26 @@ Or install it yourself as:
|
|
118
119
|
<td>Limited support</td>
|
119
120
|
<td>Limited support</td>
|
120
121
|
</tr>
|
122
|
+
<tr>
|
121
123
|
<th>Kafka 2.4</th>
|
122
124
|
<td>Limited support</td>
|
123
125
|
<td>Limited support</td>
|
124
126
|
</tr>
|
127
|
+
<tr>
|
128
|
+
<th>Kafka 2.5</th>
|
129
|
+
<td>Limited support</td>
|
130
|
+
<td>Limited support</td>
|
131
|
+
</tr>
|
132
|
+
<tr>
|
133
|
+
<th>Kafka 2.6</th>
|
134
|
+
<td>Limited support</td>
|
135
|
+
<td>Limited support</td>
|
136
|
+
</tr>
|
137
|
+
<tr>
|
138
|
+
<th>Kafka 2.7</th>
|
139
|
+
<td>Limited support</td>
|
140
|
+
<td>Limited support</td>
|
141
|
+
</tr>
|
125
142
|
</table>
|
126
143
|
|
127
144
|
This library is targeting Kafka 0.9 with the v0.4.x series and Kafka 0.10 with the v0.5.x series. There's limited support for Kafka 0.8, and things should work with Kafka 0.11, although there may be performance issues due to changes in the protocol.
|
@@ -136,6 +153,9 @@ This library is targeting Kafka 0.9 with the v0.4.x series and Kafka 0.10 with t
|
|
136
153
|
- **Kafka 2.2:** Everything that works with Kafka 2.1 should still work, but so far no features specific to Kafka 2.2 have been added.
|
137
154
|
- **Kafka 2.3:** Everything that works with Kafka 2.2 should still work, but so far no features specific to Kafka 2.3 have been added.
|
138
155
|
- **Kafka 2.4:** Everything that works with Kafka 2.3 should still work, but so far no features specific to Kafka 2.4 have been added.
|
156
|
+
- **Kafka 2.5:** Everything that works with Kafka 2.4 should still work, but so far no features specific to Kafka 2.5 have been added.
|
157
|
+
- **Kafka 2.6:** Everything that works with Kafka 2.5 should still work, but so far no features specific to Kafka 2.6 have been added.
|
158
|
+
- **Kafka 2.7:** Everything that works with Kafka 2.6 should still work, but so far no features specific to Kafka 2.7 have been added.
|
139
159
|
|
140
160
|
This library requires Ruby 2.1 or higher.
|
141
161
|
|
@@ -156,6 +176,12 @@ require "kafka"
|
|
156
176
|
kafka = Kafka.new(["kafka1:9092", "kafka2:9092"], client_id: "my-application")
|
157
177
|
```
|
158
178
|
|
179
|
+
You can also use a hostname with seed brokers' IP addresses:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
kafka = Kafka.new("seed-brokers:9092", client_id: "my-application", resolve_seed_brokers: true)
|
183
|
+
```
|
184
|
+
|
159
185
|
### Producing Messages to Kafka
|
160
186
|
|
161
187
|
The simplest way to write a message to a Kafka topic is to call `#deliver_message`:
|
@@ -342,6 +368,36 @@ partition = PartitioningScheme.assign(partitions, event)
|
|
342
368
|
producer.produce(event, topic: "events", partition: partition)
|
343
369
|
```
|
344
370
|
|
371
|
+
Another option is to configure a custom client partitioner that implements `call(partition_count, message)` and uses the same schema as the other client. For example:
|
372
|
+
|
373
|
+
```ruby
|
374
|
+
class CustomPartitioner
|
375
|
+
def call(partition_count, message)
|
376
|
+
...
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
partitioner = CustomPartitioner.new
|
381
|
+
Kafka.new(partitioner: partitioner, ...)
|
382
|
+
```
|
383
|
+
|
384
|
+
Or, simply create a Proc handling the partitioning logic instead of having to add a new class. For example:
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
partitioner = -> (partition_count, message) { ... }
|
388
|
+
Kafka.new(partitioner: partitioner, ...)
|
389
|
+
```
|
390
|
+
|
391
|
+
##### Supported partitioning schemes
|
392
|
+
|
393
|
+
In order for semantic partitioning to work a `partition_key` must map to the same partition number every time. The general approach, and the one used by this library, is to hash the key and mod it by the number of partitions. There are many different algorithms that can be used to calculate a hash. By default `crc32` is used. `murmur2` is also supported for compatibility with Java based Kafka producers.
|
394
|
+
|
395
|
+
To use `murmur2` hashing pass it as an argument to `Partitioner`. For example:
|
396
|
+
|
397
|
+
```ruby
|
398
|
+
Kafka.new(partitioner: Kafka::Partitioner.new(hash_function: :murmur2))
|
399
|
+
```
|
400
|
+
|
345
401
|
#### Buffering and Error Handling
|
346
402
|
|
347
403
|
The producer is designed for resilience in the face of temporary network errors, Kafka broker failovers, and other issues that prevent the client from writing messages to the destination topics. It does this by employing local, in-memory buffers. Only when messages are acknowledged by a Kafka broker will they be removed from the buffer.
|
@@ -716,6 +772,88 @@ consumer.each_message do |message|
|
|
716
772
|
end
|
717
773
|
```
|
718
774
|
|
775
|
+
#### Customizing Partition Assignment Strategy
|
776
|
+
|
777
|
+
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.
|
778
|
+
You can use a custom assignment strategy by passing an object that implements `#call` as the argument `assignment_strategy` like below:
|
779
|
+
|
780
|
+
```ruby
|
781
|
+
class CustomAssignmentStrategy
|
782
|
+
def initialize(user_data)
|
783
|
+
@user_data = user_data
|
784
|
+
end
|
785
|
+
|
786
|
+
# Assign the topic partitions to the group members.
|
787
|
+
#
|
788
|
+
# @param cluster [Kafka::Cluster]
|
789
|
+
# @param members [Hash<String, Kafka::Protocol::JoinGroupResponse::Metadata>] a hash
|
790
|
+
# mapping member ids to metadata
|
791
|
+
# @param partitions [Array<Kafka::ConsumerGroup::Assignor::Partition>] a list of
|
792
|
+
# partitions the consumer group processes
|
793
|
+
# @return [Hash<String, Array<Kafka::ConsumerGroup::Assignor::Partition>] a hash
|
794
|
+
# mapping member ids to partitions.
|
795
|
+
def call(cluster:, members:, partitions:)
|
796
|
+
...
|
797
|
+
end
|
798
|
+
end
|
799
|
+
|
800
|
+
strategy = CustomAssignmentStrategy.new("some-host-information")
|
801
|
+
consumer = kafka.consumer(group_id: "some-group", assignment_strategy: strategy)
|
802
|
+
```
|
803
|
+
|
804
|
+
`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:
|
805
|
+
|
806
|
+
```ruby
|
807
|
+
class RandomAssignmentStrategy
|
808
|
+
def call(cluster:, members:, partitions:)
|
809
|
+
member_ids = members.keys
|
810
|
+
partitions.each_with_object(Hash.new {|h, k| h[k] = [] }) do |partition, partitions_per_member|
|
811
|
+
partitions_per_member[member_ids[rand(member_ids.count)]] << partition
|
812
|
+
end
|
813
|
+
end
|
814
|
+
end
|
815
|
+
```
|
816
|
+
|
817
|
+
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:
|
818
|
+
|
819
|
+
```ruby
|
820
|
+
class NetworkTopologyAssignmentStrategy
|
821
|
+
def user_data
|
822
|
+
Socket.ip_address_list.find(&:ipv4_private?).ip_address
|
823
|
+
end
|
824
|
+
|
825
|
+
def call(cluster:, members:, partitions:)
|
826
|
+
# Display the pair of the member ID and IP address
|
827
|
+
members.each do |id, metadata|
|
828
|
+
puts "#{id}: #{metadata.user_data}"
|
829
|
+
end
|
830
|
+
|
831
|
+
# Assign partitions considering the network topology
|
832
|
+
...
|
833
|
+
end
|
834
|
+
end
|
835
|
+
```
|
836
|
+
|
837
|
+
Note that the strategy uses the class name as the default protocol name. You can change it by defining the method `protocol_name`:
|
838
|
+
|
839
|
+
```ruby
|
840
|
+
class NetworkTopologyAssignmentStrategy
|
841
|
+
def protocol_name
|
842
|
+
"networktopology"
|
843
|
+
end
|
844
|
+
|
845
|
+
def user_data
|
846
|
+
Socket.ip_address_list.find(&:ipv4_private?).ip_address
|
847
|
+
end
|
848
|
+
|
849
|
+
def call(cluster:, members:, partitions:)
|
850
|
+
...
|
851
|
+
end
|
852
|
+
end
|
853
|
+
```
|
854
|
+
|
855
|
+
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.
|
856
|
+
|
719
857
|
|
720
858
|
### Thread Safety
|
721
859
|
|
@@ -945,6 +1083,8 @@ This configures the store to look up CA certificates from the system default cer
|
|
945
1083
|
|
946
1084
|
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.
|
947
1085
|
|
1086
|
+
**NOTE**: You can disable hostname validation by passing `ssl_verify_hostname: false`.
|
1087
|
+
|
948
1088
|
```ruby
|
949
1089
|
kafka = Kafka.new(
|
950
1090
|
["kafka1:9092"],
|
@@ -952,6 +1092,7 @@ kafka = Kafka.new(
|
|
952
1092
|
ssl_client_cert: File.read('my_client_cert.pem'),
|
953
1093
|
ssl_client_cert_key: File.read('my_client_cert_key.pem'),
|
954
1094
|
ssl_client_cert_key_password: 'my_client_cert_key_password',
|
1095
|
+
ssl_verify_hostname: false,
|
955
1096
|
# ...
|
956
1097
|
)
|
957
1098
|
```
|
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,
|
@@ -203,31 +212,45 @@ module Kafka
|
|
203
212
|
@logger.push_tags(@producer.to_s)
|
204
213
|
@logger.info "Starting async producer in the background..."
|
205
214
|
|
215
|
+
do_loop
|
216
|
+
rescue Exception => e
|
217
|
+
@logger.error "Unexpected Kafka error #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
218
|
+
@logger.error "Async producer crashed!"
|
219
|
+
ensure
|
220
|
+
@producer.shutdown
|
221
|
+
@logger.pop_tags
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def do_loop
|
206
227
|
loop do
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
produce
|
212
|
-
|
213
|
-
|
214
|
-
deliver_messages
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
228
|
+
begin
|
229
|
+
operation, payload = @queue.pop
|
230
|
+
|
231
|
+
case operation
|
232
|
+
when :produce
|
233
|
+
produce(payload[0], **payload[1])
|
234
|
+
deliver_messages if threshold_reached?
|
235
|
+
when :deliver_messages
|
236
|
+
deliver_messages
|
237
|
+
when :shutdown
|
238
|
+
begin
|
239
|
+
# Deliver any pending messages first.
|
240
|
+
@producer.deliver_messages
|
241
|
+
rescue Error => e
|
242
|
+
@logger.error("Failed to deliver messages during shutdown: #{e.message}")
|
243
|
+
|
244
|
+
@instrumenter.instrument("drop_messages.async_producer", {
|
245
|
+
message_count: @producer.buffer_size + @queue.size,
|
246
|
+
})
|
247
|
+
end
|
248
|
+
|
249
|
+
# Stop the run loop.
|
250
|
+
break
|
251
|
+
else
|
252
|
+
raise "Unknown operation #{operation.inspect}"
|
225
253
|
end
|
226
|
-
|
227
|
-
# Stop the run loop.
|
228
|
-
break
|
229
|
-
else
|
230
|
-
raise "Unknown operation #{operation.inspect}"
|
231
254
|
end
|
232
255
|
end
|
233
256
|
rescue Kafka::Error => e
|
@@ -236,20 +259,12 @@ module Kafka
|
|
236
259
|
|
237
260
|
sleep 10
|
238
261
|
retry
|
239
|
-
rescue Exception => e
|
240
|
-
@logger.error "Unexpected Kafka error #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
241
|
-
@logger.error "Async producer crashed!"
|
242
|
-
ensure
|
243
|
-
@producer.shutdown
|
244
|
-
@logger.pop_tags
|
245
262
|
end
|
246
263
|
|
247
|
-
|
248
|
-
|
249
|
-
def produce(*args)
|
264
|
+
def produce(value, **kwargs)
|
250
265
|
retries = 0
|
251
266
|
begin
|
252
|
-
@producer.produce(
|
267
|
+
@producer.produce(value, **kwargs)
|
253
268
|
rescue BufferOverflow => e
|
254
269
|
deliver_messages
|
255
270
|
if @max_retries == -1
|