nsq-ruby-fastly 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +421 -0
- data/lib/nsq/client_base.rb +120 -0
- data/lib/nsq/connection.rb +466 -0
- data/lib/nsq/consumer.rb +100 -0
- data/lib/nsq/discovery.rb +98 -0
- data/lib/nsq/exceptions.rb +5 -0
- data/lib/nsq/frames/error.rb +6 -0
- data/lib/nsq/frames/frame.rb +16 -0
- data/lib/nsq/frames/message.rb +33 -0
- data/lib/nsq/frames/response.rb +6 -0
- data/lib/nsq/logger.rb +38 -0
- data/lib/nsq/producer.rb +94 -0
- data/lib/nsq.rb +13 -0
- data/lib/version.rb +9 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ba51761d5abfe9acbb63eceac3e053b8323fd31cee0d03e165ad4263c63dc985
|
4
|
+
data.tar.gz: 26f1770323d24d83bd828cb2f0073472299d41b174177ce4bd351bcf5b876626
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fe327495f3c7487d679257c73b020ecbbbc64c91d6c3a36caff79c23bcc171cb4c898d1675b011d717c34c19b3d5f7226f81408b9af09931dac6b90bec85a861
|
7
|
+
data.tar.gz: 87eb4b1748c1c068b53df2e53befdd31bf2894f906c6273bc5576e99f1febf54f54e174a174ea3f4cefc65e2b4129cee09cd87c37014087afba6f45a4826c92b
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 2.3.1
|
4
|
+
|
5
|
+
* Fix `max_attempts` bug (#46)
|
6
|
+
|
7
|
+
## 2.3.0
|
8
|
+
|
9
|
+
* Add support for `max_attempts` (#43)
|
10
|
+
|
11
|
+
## 2.2.0
|
12
|
+
|
13
|
+
* Fix memory leak in producer by using a limited `SizedQueue` (#42)
|
14
|
+
|
15
|
+
## 2.1.0
|
16
|
+
|
17
|
+
* Now compatible with NSQ 1.0 thanks to @pacoguzman (#37).
|
18
|
+
|
19
|
+
## 2.0.5
|
20
|
+
|
21
|
+
* Bugfix: Do not register `at_exit` handlers for consumers and producers to avoid memory leaks (#34)
|
22
|
+
|
23
|
+
## 2.0.4
|
24
|
+
|
25
|
+
* Bugfix: Close connection socket when calling `.terminate`.
|
26
|
+
|
27
|
+
## 2.0.3
|
28
|
+
|
29
|
+
* Bugfix: Ensure write_loop ends; pass message via write_queue (#32)
|
30
|
+
|
31
|
+
## 2.0.2
|
32
|
+
|
33
|
+
* Bugfix: Use `File.read` instead of `File.open` to avoid leaving open file handles around.
|
34
|
+
|
35
|
+
## 2.0.1
|
36
|
+
|
37
|
+
* Bugfix: Allow discovery of ephemeral topics when using JRuby.
|
38
|
+
|
39
|
+
## 2.0.0
|
40
|
+
|
41
|
+
* Enable TLS connections without any client-side keys, certificates or certificate authorities.
|
42
|
+
* Deprecate `ssl_context` connection setting, rename it to `tls_options` moving forward.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Wistia, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,421 @@
|
|
1
|
+
# nsq-ruby
|
2
|
+
|
3
|
+
nsq-ruby is a simple NSQ client library written in Ruby.
|
4
|
+
|
5
|
+
- The code is straightforward.
|
6
|
+
- It has no dependencies.
|
7
|
+
- It's well tested.
|
8
|
+
- It's being used in production and has processed billions of messages.
|
9
|
+
|
10
|
+
[![Build Status](https://travis-ci.org/wistia/nsq-ruby.svg?branch=master)](https://travis-ci.org/wistia/nsq-ruby)
|
11
|
+
|
12
|
+
|
13
|
+
## Quick start
|
14
|
+
|
15
|
+
### Publish messages
|
16
|
+
|
17
|
+
```Ruby
|
18
|
+
require 'nsq'
|
19
|
+
producer = Nsq::Producer.new(
|
20
|
+
nsqd: '127.0.0.1:4150',
|
21
|
+
topic: 'some-topic'
|
22
|
+
)
|
23
|
+
|
24
|
+
# Write a message to NSQ
|
25
|
+
producer.write('some-message')
|
26
|
+
|
27
|
+
# Write a bunch of messages to NSQ (uses mpub)
|
28
|
+
producer.write('one', 'two', 'three', 'four', 'five')
|
29
|
+
|
30
|
+
# Write a deferred message to NSQ (uses dpub)
|
31
|
+
|
32
|
+
# Message deferred of 10s
|
33
|
+
producer.deferred_write(10, 'one')
|
34
|
+
|
35
|
+
# Message deferred of 1250ms
|
36
|
+
producer.deferred_write(1.25, 'one')
|
37
|
+
|
38
|
+
# Close the connection
|
39
|
+
producer.terminate
|
40
|
+
```
|
41
|
+
|
42
|
+
### Consume messages
|
43
|
+
|
44
|
+
```Ruby
|
45
|
+
require 'nsq'
|
46
|
+
consumer = Nsq::Consumer.new(
|
47
|
+
nsqlookupd: '127.0.0.1:4161',
|
48
|
+
topic: 'some-topic',
|
49
|
+
channel: 'some-channel'
|
50
|
+
)
|
51
|
+
|
52
|
+
# Pop a message off the queue
|
53
|
+
msg = consumer.pop
|
54
|
+
puts msg.body
|
55
|
+
msg.finish
|
56
|
+
|
57
|
+
# Close the connections
|
58
|
+
consumer.terminate
|
59
|
+
```
|
60
|
+
|
61
|
+
|
62
|
+
## Producer
|
63
|
+
|
64
|
+
### Initialization
|
65
|
+
|
66
|
+
The Nsq::Producer constructor takes the following options:
|
67
|
+
|
68
|
+
| Option | Description | Default |
|
69
|
+
|---------------|----------------------------------------|--------------------|
|
70
|
+
| `topic` | Topic to which to publish messages | |
|
71
|
+
| `nsqd` | Host and port of the nsqd instance | '127.0.0.1:4150' |
|
72
|
+
| `nsqlookupd` | Use lookupd to auto discover nsqds | |
|
73
|
+
| `tls_v1` | Flag for tls v1 connections | false |
|
74
|
+
| `tls_options` | Optional keys+certs for TLS connections| |
|
75
|
+
|
76
|
+
For example, if you'd like to publish messages to a single nsqd.
|
77
|
+
|
78
|
+
```Ruby
|
79
|
+
producer = Nsq::Producer.new(
|
80
|
+
nsqd: '6.7.8.9:4150',
|
81
|
+
topic: 'topic-of-great-esteem'
|
82
|
+
)
|
83
|
+
```
|
84
|
+
|
85
|
+
Alternatively, you can use nsqlookupd to find all nsqd nodes in the cluster.
|
86
|
+
When you instantiate Nsq::Producer in this way, it will automatically maintain
|
87
|
+
connections to all nsqd instances. When you publish a message, it will be sent
|
88
|
+
to a random nsqd instance.
|
89
|
+
|
90
|
+
```Ruby
|
91
|
+
producer = Nsq::Producer.new(
|
92
|
+
nsqlookupd: ['1.2.3.4:4161', '6.7.8.9:4161'],
|
93
|
+
topic: 'topic-of-great-esteem'
|
94
|
+
)
|
95
|
+
```
|
96
|
+
|
97
|
+
If you need to connect using SSL/TLS Authentication via `tls_options`
|
98
|
+
|
99
|
+
```Ruby
|
100
|
+
producer = Nsq::Producer.new(
|
101
|
+
nsqlookupd: ['1.2.3.4:4161', '6.7.8.9:4161'],
|
102
|
+
topic: 'topic-of-great-esteem',
|
103
|
+
tls_v1: true,
|
104
|
+
tls_options: {
|
105
|
+
key: '/path/to/ssl/key.pem',
|
106
|
+
certificate: '/path/to/ssl/certificate.pem',
|
107
|
+
ca_certificate: '/path/to/ssl/ca_certificate.pem',
|
108
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER
|
109
|
+
}
|
110
|
+
)
|
111
|
+
```
|
112
|
+
|
113
|
+
If you need to connect using simple `tls_v1`
|
114
|
+
|
115
|
+
```Ruby
|
116
|
+
producer = Nsq::Producer.new(
|
117
|
+
nsqlookupd: ['1.2.3.4:4161', '6.7.8.9:4161'],
|
118
|
+
topic: 'topic-of-great-esteem',
|
119
|
+
tls_v1: true
|
120
|
+
)
|
121
|
+
```
|
122
|
+
|
123
|
+
### `#write`
|
124
|
+
|
125
|
+
Publishes one or more messages to nsqd. If you give it a single argument, it will
|
126
|
+
send it to nsqd via `PUB`. If you give it multiple arguments, it will send all
|
127
|
+
those messages to nsqd via `MPUB`. It will automatically call `to_s` on any
|
128
|
+
arguments you give it.
|
129
|
+
|
130
|
+
```Ruby
|
131
|
+
# Send a single message via PUB
|
132
|
+
producer.write(123)
|
133
|
+
|
134
|
+
# Send three messages via MPUB
|
135
|
+
producer.write(456, 'another-message', { key: 'value' }.to_json)
|
136
|
+
```
|
137
|
+
|
138
|
+
If its connection to nsqd fails, it will automatically try to reconnect with
|
139
|
+
exponential backoff. Any messages that were sent to `#write` will be queued
|
140
|
+
and transmitted after reconnecting.
|
141
|
+
|
142
|
+
**Note:** Internally, we use a `SizedQueue` that can hold 10,000 messages. If you're
|
143
|
+
producing messages faster than we're able to send them to nsqd or nsqd is
|
144
|
+
offline for an extended period and you accumulate 10,000 messages in the queue,
|
145
|
+
calls to `#write` will block until there's room in the queue.
|
146
|
+
|
147
|
+
**Note:** We don't wait for nsqd to acknowledge our writes. As a result, if the
|
148
|
+
connection to nsqd fails, you can lose messages. This is acceptable for our use
|
149
|
+
cases, mostly because we are sending messages to a local nsqd instance and
|
150
|
+
failure is very rare.
|
151
|
+
|
152
|
+
|
153
|
+
### `#write_to_topic`
|
154
|
+
|
155
|
+
Publishes one or more messages to nsqd. Like `#write`, but allows you to specify
|
156
|
+
the topic. Use this method if you want a single producer instance to write to
|
157
|
+
multiple topics.
|
158
|
+
|
159
|
+
```Ruby
|
160
|
+
# Send a single message via PUB to the topic 'rutabega'
|
161
|
+
producer.write_to_topic('rutabega', 123)
|
162
|
+
|
163
|
+
# Send multiple messages via MPUB to the topic 'kohlrabi'
|
164
|
+
producer.write_to_topic('kohlrabi', 'a', 'b', 'c')
|
165
|
+
```
|
166
|
+
|
167
|
+
|
168
|
+
### `#connected?`
|
169
|
+
|
170
|
+
Returns true if it's currently connected to nsqd and false if not.
|
171
|
+
|
172
|
+
### `#terminate`
|
173
|
+
|
174
|
+
Closes the connection to nsqd and stops it from trying to automatically
|
175
|
+
reconnect.
|
176
|
+
|
177
|
+
This is automatically called `at_exit`, but it's good practice to close your
|
178
|
+
producers when you're done with them.
|
179
|
+
|
180
|
+
**Note:** This terminates the connection to NSQ immediately. If you're writing
|
181
|
+
messages faster than they can be sent to NSQ, you may have messages in the
|
182
|
+
producer's internal queue. Calling `#terminate` before they're sent will cause
|
183
|
+
these messages to be lost. After you write your last message, consider sleeping
|
184
|
+
for a second before you call `#terminate`.
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
|
189
|
+
## Consumer
|
190
|
+
|
191
|
+
### Initialization
|
192
|
+
|
193
|
+
| Option | Description | Default |
|
194
|
+
|----------------------|-----------------------------------------------|--------------------|
|
195
|
+
| `topic` | Topic to consume messages from | |
|
196
|
+
| `channel` | Channel name for this consumer | |
|
197
|
+
| `nsqlookupd` | Use lookupd to automatically discover nsqds | |
|
198
|
+
| `nsqd` | Connect directly to a single nsqd instance | '127.0.0.1:4150' |
|
199
|
+
| `max_in_flight` | Max number of messages for this consumer to have in flight at a time | 1 |
|
200
|
+
| `discovery_interval` | Seconds between queue discovery via nsqlookupd | 60.0 |
|
201
|
+
| `msg_timeout` | Milliseconds before nsqd will timeout a message | 60000 |
|
202
|
+
| `max_attempts` | Number of times a message will be attempted before being finished | |
|
203
|
+
| `tls_v1` | Flag for tls v1 connections | false |
|
204
|
+
| `tls_options` | Optional keys and certificates for TLS connections | |
|
205
|
+
|
206
|
+
|
207
|
+
For example:
|
208
|
+
|
209
|
+
```Ruby
|
210
|
+
consumer = Nsq::Consumer.new(
|
211
|
+
topic: 'the-topic',
|
212
|
+
channel: 'my-channel',
|
213
|
+
nsqlookupd: ['127.0.0.1:4161', '4.5.6.7:4161'],
|
214
|
+
max_in_flight: 100,
|
215
|
+
discovery_interval: 30,
|
216
|
+
msg_timeout: 120_000,
|
217
|
+
max_attempts: 10,
|
218
|
+
tls_v1: true,
|
219
|
+
tls_options: {
|
220
|
+
key: '/path/to/ssl/key.pem',
|
221
|
+
certificate: '/path/to/ssl/certificate.pem',
|
222
|
+
ca_certificate: '/path/to/ssl/ca_certificate.pem',
|
223
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER
|
224
|
+
}
|
225
|
+
)
|
226
|
+
```
|
227
|
+
|
228
|
+
Notes:
|
229
|
+
|
230
|
+
- `nsqlookupd` can be a string or array of strings for each nsqlookupd service
|
231
|
+
you'd like to use. The format is `"<host>:<http-port>"`. If you specify
|
232
|
+
`nsqlookupd`, it ignores the `nsqd` option.
|
233
|
+
- `max_in_flight` is for the total max in flight across all the connections,
|
234
|
+
but to make the implementation of `nsq-ruby` as simple as possible, the minimum
|
235
|
+
`max_in_flight` _per_ connection is 1. So if you set `max_in_flight` to 1 and
|
236
|
+
are connected to 3 nsqds, you may have up to 3 messages in flight at a time.
|
237
|
+
- `max_attempts` is optional and if not set messages will be attempted until they
|
238
|
+
are explicitly finshed.
|
239
|
+
|
240
|
+
### `#pop`
|
241
|
+
|
242
|
+
`nsq-ruby` works by maintaining a local queue of in flight messages from NSQ.
|
243
|
+
To get at these messages, just call pop.
|
244
|
+
|
245
|
+
```Ruby
|
246
|
+
message = consumer.pop
|
247
|
+
```
|
248
|
+
|
249
|
+
If there are messages on the queue, `pop` will return one immediately. If there
|
250
|
+
are no messages on the queue, `pop` will block execution until one arrives.
|
251
|
+
|
252
|
+
Be aware, while `#pop` is blocking, your process will be unresponsive. This
|
253
|
+
can be a problem in certain cases, like if you're trying to gracefully restart
|
254
|
+
a worker process by sending it a `TERM` signal. See `#pop_without_blocking` for
|
255
|
+
information on how to mitigate this issue.
|
256
|
+
|
257
|
+
|
258
|
+
### `#pop_without_blocking`
|
259
|
+
|
260
|
+
This is just like `#pop` except it doesn't block. It always returns immediately.
|
261
|
+
If there are no messages in the queue, it will return `nil`.
|
262
|
+
|
263
|
+
If you're consuming from a low-volume topic and don't want to get stuck in a
|
264
|
+
blocking state, you can use this method to consume messages like so:
|
265
|
+
|
266
|
+
```Ruby
|
267
|
+
loop do
|
268
|
+
if msg = @messages.pop_without_blocking
|
269
|
+
# do something
|
270
|
+
msg.finish
|
271
|
+
else
|
272
|
+
# wait for a bit before checking for new messages
|
273
|
+
sleep 0.01
|
274
|
+
end
|
275
|
+
end
|
276
|
+
```
|
277
|
+
|
278
|
+
|
279
|
+
### `#size`
|
280
|
+
|
281
|
+
`size` returns the size of the local message queue.
|
282
|
+
|
283
|
+
|
284
|
+
### `#terminate`
|
285
|
+
|
286
|
+
Gracefully closes all connections and stops the consumer. You should call this
|
287
|
+
when you're finished with a consumer object.
|
288
|
+
|
289
|
+
|
290
|
+
## Message
|
291
|
+
|
292
|
+
The `Message` object is what you get when you call `pop` on a consumer.
|
293
|
+
Once you have a message, you'll likely want to get its contents using the `#body`
|
294
|
+
method, and then call `#finish` once you're done with it.
|
295
|
+
|
296
|
+
### `body`
|
297
|
+
|
298
|
+
Returns the body of the message as a UTF-8 encoded string.
|
299
|
+
|
300
|
+
### `attempts`
|
301
|
+
|
302
|
+
Returns the number of times this message was attempted to be processed. For
|
303
|
+
most messages this should be 1 (since it will be your first attempt processing
|
304
|
+
them). If it's more than 1, that means that you requeued the message or it
|
305
|
+
timed out in flight.
|
306
|
+
|
307
|
+
### `timestamp`
|
308
|
+
|
309
|
+
Returns the time this message was originally sent to NSQ as a `Time` object.
|
310
|
+
|
311
|
+
### `#finish`
|
312
|
+
|
313
|
+
Notify NSQ that you've completed processing of this message.
|
314
|
+
|
315
|
+
### `#touch`
|
316
|
+
|
317
|
+
Tells NSQ to reset the message timeout for this message so you have more time
|
318
|
+
to process it.
|
319
|
+
|
320
|
+
### `#requeue(timeout = 0)`
|
321
|
+
|
322
|
+
Tells NSQ to requeue this message. Called with no arguments, this will requeue
|
323
|
+
the message and it will be available to be received immediately.
|
324
|
+
|
325
|
+
Optionally you can pass a number of milliseconds as an argument. This tells
|
326
|
+
NSQ to delay its requeueing by that number of milliseconds.
|
327
|
+
|
328
|
+
|
329
|
+
## Logging
|
330
|
+
|
331
|
+
By default, `nsq-ruby` doesn't log anything. To enable logging, use
|
332
|
+
`Nsq.logger=` and point it at a Ruby Logger instance. Like this:
|
333
|
+
|
334
|
+
```Ruby
|
335
|
+
Nsq.logger = Logger.new(STDOUT)
|
336
|
+
```
|
337
|
+
|
338
|
+
|
339
|
+
## Requirements
|
340
|
+
|
341
|
+
NSQ v0.2.29 or later for IDENTIFY metadata specification (0.2.28) and per-
|
342
|
+
connection timeout support (0.2.29).
|
343
|
+
|
344
|
+
|
345
|
+
### Supports
|
346
|
+
|
347
|
+
- Discovery via nsqlookupd
|
348
|
+
- Automatic reconnection to nsqd
|
349
|
+
- Producing to all nsqd instances automatically via nsqlookupd
|
350
|
+
- TLS
|
351
|
+
|
352
|
+
|
353
|
+
### Does not support
|
354
|
+
|
355
|
+
- Compression
|
356
|
+
- Backoff
|
357
|
+
|
358
|
+
If you need more advanced features, like these, you should check out
|
359
|
+
[Krakow](https://github.com/chrisroberts/krakow), a more fully featured NSQ
|
360
|
+
client for Ruby.
|
361
|
+
|
362
|
+
|
363
|
+
## Testing
|
364
|
+
|
365
|
+
Run the tests like this:
|
366
|
+
|
367
|
+
```
|
368
|
+
rake spec
|
369
|
+
```
|
370
|
+
|
371
|
+
Want a deluge of logging while running the specs to help determine what is
|
372
|
+
going on?
|
373
|
+
|
374
|
+
```
|
375
|
+
VERBOSE=true rake spec
|
376
|
+
```
|
377
|
+
|
378
|
+
|
379
|
+
## Is this production ready?
|
380
|
+
|
381
|
+
Yes! It's used in several critical parts of our infrastructure at
|
382
|
+
[Wistia](http://wistia.com) and currently produces and consumes hundreds of
|
383
|
+
millions of messages a day.
|
384
|
+
|
385
|
+
|
386
|
+
## Authors & Contributors
|
387
|
+
|
388
|
+
- Robby Grossman ([@freerobby](https://github.com/freerobby))
|
389
|
+
- Brendan Schwartz ([@bschwartz](https://github.com/bschwartz))
|
390
|
+
- Marshall Moutenot ([@mmoutenot](https://github.com/mmoutenot))
|
391
|
+
- Danielle Sucher ([@DanielleSucher](https://github.com/DanielleSucher))
|
392
|
+
- Anders Chen ([@chen-anders](https://github.com/chen-anders))
|
393
|
+
- Thomas O'Neil ([@alieander](https://github.com/alieander))
|
394
|
+
- Unbekandt Léo ([@soulou](https://github.com/Soulou))
|
395
|
+
- Matthias Schneider ([@mschneider82](https://github.com/mschneider82))
|
396
|
+
- Lukas Eklund ([@leklund](https://github.com/leklund))
|
397
|
+
- Paco Guzmán ([@pacoguzman](https://github.com/pacoguzman))
|
398
|
+
|
399
|
+
|
400
|
+
## MIT License
|
401
|
+
|
402
|
+
Copyright (C) 2018 Wistia, Inc.
|
403
|
+
|
404
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
405
|
+
this software and associated documentation files (the "Software"), to deal in
|
406
|
+
the Software without restriction, including without limitation the rights to
|
407
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
408
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
409
|
+
so, subject to the following conditions:
|
410
|
+
|
411
|
+
The above copyright notice and this permission notice shall be included in all
|
412
|
+
copies or substantial portions of the Software.
|
413
|
+
|
414
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
415
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
416
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
417
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
418
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
419
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
420
|
+
SOFTWARE.
|
421
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require_relative 'discovery'
|
2
|
+
require_relative 'connection'
|
3
|
+
require_relative 'logger'
|
4
|
+
|
5
|
+
module Nsq
|
6
|
+
class ClientBase
|
7
|
+
include Nsq::AttributeLogger
|
8
|
+
@@log_attributes = [:topic]
|
9
|
+
|
10
|
+
attr_reader :topic
|
11
|
+
attr_reader :connections
|
12
|
+
|
13
|
+
def connected?
|
14
|
+
@connections.values.any?(&:connected?)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def terminate
|
19
|
+
@discovery_thread.kill if @discovery_thread
|
20
|
+
drop_all_connections
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# discovers nsqds from an nsqlookupd repeatedly
|
27
|
+
#
|
28
|
+
# opts:
|
29
|
+
# nsqlookups: ['127.0.0.1:4161'],
|
30
|
+
# topic: 'topic-to-find-nsqds-for',
|
31
|
+
# interval: 60
|
32
|
+
#
|
33
|
+
def discover_repeatedly(opts = {})
|
34
|
+
@discovery_thread = Thread.new do
|
35
|
+
|
36
|
+
@discovery = Discovery.new(opts[:nsqlookupds])
|
37
|
+
|
38
|
+
loop do
|
39
|
+
begin
|
40
|
+
nsqds = nsqds_from_lookupd(opts[:topic])
|
41
|
+
drop_and_add_connections(nsqds)
|
42
|
+
rescue DiscoveryException
|
43
|
+
# We can't connect to any nsqlookupds. That's okay, we'll just
|
44
|
+
# leave our current nsqd connections alone and try again later.
|
45
|
+
warn 'Could not connect to any nsqlookupd instances in discovery loop'
|
46
|
+
end
|
47
|
+
sleep opts[:interval]
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
@discovery_thread.abort_on_exception = true
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def nsqds_from_lookupd(topic = nil)
|
57
|
+
if topic
|
58
|
+
@discovery.nsqds_for_topic(topic)
|
59
|
+
else
|
60
|
+
@discovery.nsqds
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def drop_and_add_connections(nsqds)
|
66
|
+
# drop nsqd connections that are no longer in lookupd
|
67
|
+
missing_nsqds = @connections.keys - nsqds
|
68
|
+
missing_nsqds.each do |nsqd|
|
69
|
+
drop_connection(nsqd)
|
70
|
+
end
|
71
|
+
|
72
|
+
# add new ones
|
73
|
+
new_nsqds = nsqds - @connections.keys
|
74
|
+
new_nsqds.each do |nsqd|
|
75
|
+
begin
|
76
|
+
add_connection(nsqd)
|
77
|
+
rescue Exception => ex
|
78
|
+
error "Failed to connect to nsqd @ #{nsqd}: #{ex}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# balance RDY state amongst the connections
|
83
|
+
connections_changed
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def add_connection(nsqd, options = {})
|
88
|
+
info "+ Adding connection #{nsqd}"
|
89
|
+
host, port = nsqd.split(':')
|
90
|
+
connection = Connection.new({
|
91
|
+
host: host,
|
92
|
+
port: port,
|
93
|
+
ssl_context: @ssl_context,
|
94
|
+
tls_options: @tls_options,
|
95
|
+
tls_v1: @tls_v1
|
96
|
+
}.merge(options))
|
97
|
+
@connections[nsqd] = connection
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def drop_connection(nsqd)
|
102
|
+
info "- Dropping connection #{nsqd}"
|
103
|
+
connection = @connections.delete(nsqd)
|
104
|
+
connection.close if connection
|
105
|
+
connections_changed
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def drop_all_connections
|
110
|
+
@connections.keys.each do |nsqd|
|
111
|
+
drop_connection(nsqd)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# optional subclass hook
|
117
|
+
def connections_changed
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|