nsq-ruby-fastly 2.4.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 +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
|
+
[](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
|