rdkit 0.0.1 → 0.1.4
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/.gitignore +3 -0
- data/Gemfile +10 -1
- data/Gemfile.ci +13 -0
- data/Guardfile +40 -0
- data/README.md +282 -6
- data/Vagrantfile +13 -0
- data/example/blocking.rb +10 -0
- data/example/blocking/command_runner.rb +28 -0
- data/example/blocking/core.rb +24 -0
- data/example/blocking/server.rb +10 -0
- data/example/blocking/version.rb +3 -0
- data/example/callbacks.rb +9 -0
- data/example/callbacks/command_runner.rb +21 -0
- data/example/callbacks/core.rb +18 -0
- data/example/callbacks/server.rb +30 -0
- data/example/counter.rb +0 -2
- data/example/counter/command_runner.rb +4 -0
- data/example/counter/core.rb +4 -0
- data/example/http.rb +9 -0
- data/example/http/core.rb +18 -0
- data/example/http/responder.rb +7 -0
- data/example/http/server.rb +19 -0
- data/lib/rdkit.rb +20 -3
- data/lib/rdkit/callbacks.rb +10 -0
- data/lib/rdkit/client.rb +157 -0
- data/lib/rdkit/configuration.rb +31 -0
- data/lib/rdkit/core.rb +2 -4
- data/lib/rdkit/core_ext.rb +7 -0
- data/lib/rdkit/db.rb +257 -0
- data/lib/rdkit/db_commands.rb +182 -0
- data/lib/rdkit/errors.rb +32 -1
- data/lib/rdkit/http_parser.rb +56 -0
- data/lib/rdkit/http_responder.rb +74 -0
- data/lib/rdkit/introspection.rb +133 -21
- data/lib/rdkit/logger.rb +9 -4
- data/lib/rdkit/memory_monitoring.rb +29 -0
- data/lib/rdkit/notification_center.rb +21 -0
- data/lib/rdkit/rd_object.rb +69 -0
- data/lib/rdkit/resp.rb +9 -1
- data/lib/rdkit/{command_parser.rb → resp_parser.rb} +6 -18
- data/lib/rdkit/resp_responder.rb +105 -0
- data/lib/rdkit/server.rb +242 -86
- data/lib/rdkit/simple_commands.rb +17 -0
- data/lib/rdkit/slow_log.rb +52 -0
- data/lib/rdkit/subcommands.rb +157 -0
- data/lib/rdkit/version.rb +1 -1
- data/rdkit.gemspec +6 -0
- metadata +119 -5
- data/lib/rdkit/inheritable.rb +0 -15
- data/lib/rdkit/resp_runner.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10a0bd157c17b8098e43a87046384ebc7e9ae534
|
4
|
+
data.tar.gz: 7ad179971146d74db65ffbc8a6999b1b9b8bb034
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8fd1464e00be7ce386d4eefb1132984cbfb7632ff498eb7df6ddd97fd07127122f27b8828c1722ad22b515b606a686286fc5a5e550adc964c00409e7950aa62
|
7
|
+
data.tar.gz: 2d71bef8dfac46a9b7d4274ab755e83a3f944a05a401def43b1bda6932d69c1a2cef181a229a015faf5299d73d22361778502e9653c4e52df61d251572e304a6
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in rdkit.gemspec
|
4
3
|
gemspec
|
4
|
+
|
5
|
+
group :development, :test do
|
6
|
+
gem 'pry'
|
7
|
+
gem 'coco'
|
8
|
+
gem 'timecop'
|
9
|
+
gem 'guard'
|
10
|
+
gem 'guard-rspec'
|
11
|
+
end
|
12
|
+
|
13
|
+
gem 'thread', '~> 0.2.1', github: 'meh/ruby-thread'
|
data/Gemfile.ci
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features)
|
6
|
+
|
7
|
+
## Uncomment to clear the screen before every task
|
8
|
+
# clearing :on
|
9
|
+
|
10
|
+
## Guard internally checks for changes in the Guardfile and exits.
|
11
|
+
## If you want Guard to automatically start up again, run guard in a
|
12
|
+
## shell loop, e.g.:
|
13
|
+
##
|
14
|
+
## $ while bundle exec guard; do echo "Restarting Guard..."; done
|
15
|
+
##
|
16
|
+
## Note: if you are using the `directories` clause above and you are not
|
17
|
+
## watching the project directory ('.'), then you will want to move
|
18
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
19
|
+
#
|
20
|
+
# $ mkdir config
|
21
|
+
# $ mv Guardfile config/
|
22
|
+
# $ ln -s config/Guardfile .
|
23
|
+
#
|
24
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
25
|
+
|
26
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
27
|
+
# rspec may be run, below are examples of the most common uses.
|
28
|
+
# * bundler: 'bundle exec rspec'
|
29
|
+
# * bundler binstubs: 'bin/rspec'
|
30
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
31
|
+
# installed the spring binstubs per the docs)
|
32
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
33
|
+
# * 'just' rspec: 'rspec'
|
34
|
+
|
35
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
36
|
+
watch(%r{^spec/.+_spec\.rb$})
|
37
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
38
|
+
|
39
|
+
watch('spec/spec_helper.rb') { "spec" }
|
40
|
+
end
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# RDKit
|
2
2
|
|
3
|
-
RDKit is a simple toolkit to write Redis-like, single-threaded multiplexing-IO server.
|
3
|
+
`RDKit` is a simple toolkit to write Redis-like, single-threaded multiplexing-IO server.
|
4
4
|
|
5
5
|
The server speaks [Redis RESP protocol](http://redis.io/topics/protocol), so you can reuse many Redis-compatible clients and tools such as:
|
6
6
|
|
@@ -10,11 +10,17 @@ The server speaks [Redis RESP protocol](http://redis.io/topics/protocol), so you
|
|
10
10
|
|
11
11
|
And a lot more.
|
12
12
|
|
13
|
-
`RDKit` is used to power
|
13
|
+
`RDKit` is used to power:
|
14
|
+
|
15
|
+
- [520 Love Radio](http://s.weibo.com/weibo/same%2520%25E7%2594%25B5%25E5%258F%25B0) service of [same.com](http://same.com)
|
16
|
+
- AntiSpam blacklisted photo filtering service used at [same.com](http://same.com) (BK-Tree + pHash)
|
17
|
+
- channel unread count service at [same.com](http://same.com)
|
14
18
|
|
15
19
|
[](https://codeclimate.com/github/forresty/rdkit)
|
16
20
|
[](https://travis-ci.org/forresty/rdkit)
|
17
21
|
|
22
|
+
`RDKit` should work without problem on `MRI` 2.2+, may encounter bugs on earlier version of `MRI` or `JRuby` or `Rubinus`, in that case, please kindly open an issue on GitHub
|
23
|
+
|
18
24
|
## Installation
|
19
25
|
|
20
26
|
Add this line to your application's Gemfile:
|
@@ -33,9 +39,100 @@ Or install it yourself as:
|
|
33
39
|
|
34
40
|
## Usage
|
35
41
|
|
36
|
-
|
42
|
+
Generally, you should implement one subclass for each of the 3 classes: `RDKit::RESPResponder`, `RDKit::Core` and `RDKit::Server`, and spawn one object for each class.
|
43
|
+
|
44
|
+
Your server object should have two instance variables `@responder` and `@core` pointed to your spawned instances.
|
45
|
+
|
46
|
+
### RDKit::Server
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class YourServer < RDKit::Server
|
50
|
+
def initialize
|
51
|
+
super('0.0.0.0', 3721)
|
52
|
+
|
53
|
+
@core = YourCore.new
|
54
|
+
@responder = YourResponder.new(core)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
server = YourServer.new
|
59
|
+
|
60
|
+
trap(:INT) { server.stop }
|
61
|
+
|
62
|
+
server.start
|
63
|
+
```
|
64
|
+
|
65
|
+
This will start a `TCPServer` on `0.0.0.0:3721` and stops when you `CTRL-C`.
|
66
|
+
|
67
|
+
### RDKit::RESPResponder
|
68
|
+
|
69
|
+
`@responder` maps Redis commands to its methods and arguments, for example `info` will be sent to `RESPResponder#info`, and `info all` to `RESPResponder#info` with `"all"` as its first argument.
|
70
|
+
|
71
|
+
The return ruby object of each method will be marshaled as RESP strings, for example `'OK'` becomes `"+OK\r\n"`.
|
72
|
+
|
73
|
+
For example, with following implementation in your `RESPResponder` subclass:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
def add(a, b)
|
77
|
+
a.to_i + b.to_i
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
You implemented an adder using RDKit! See it in action:
|
82
|
+
|
83
|
+
```shell
|
84
|
+
$ redis-cli -p 3721
|
85
|
+
127.0.0.1:3721> add 1 2
|
86
|
+
(integer) 3
|
87
|
+
127.0.0.1:3721> add 5
|
88
|
+
(error) ERR wrong number of arguments for 'add' command
|
89
|
+
127.0.0.1:3721>
|
90
|
+
```
|
91
|
+
|
92
|
+
The detailed algorithm can be found in `resp.rb`, at the time of writing it is like this:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
def compose(data)
|
96
|
+
case data
|
97
|
+
when *%w{ OK string list set hash zset none }
|
98
|
+
"+#{data}\r\n"
|
99
|
+
when true
|
100
|
+
":1\r\n"
|
101
|
+
when false
|
102
|
+
":0\r\n"
|
103
|
+
when Integer
|
104
|
+
":#{data}\r\n"
|
105
|
+
when Array
|
106
|
+
"*#{data.size}\r\n" + data.map { |i| compose(i) }.join
|
107
|
+
when NilClass
|
108
|
+
# Null Bulk String, not Null Array of "*-1\r\n"
|
109
|
+
"$-1\r\n"
|
110
|
+
when WrongTypeError
|
111
|
+
"-WRONGTYPE #{data.message}\r\n"
|
112
|
+
when StandardError
|
113
|
+
"-ERR #{data.message}\r\n"
|
114
|
+
else
|
115
|
+
# always Bulk String
|
116
|
+
"$#{data.bytesize}\r\n#{data}\r\n"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
### RDKit::Core
|
122
|
+
|
123
|
+
You are required to implement a `tick!` method. `RDKit` will call it periodically (currently roughly every 0.1 sec), this gives you a chance to do some house-keeping. For example:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
def tick!
|
127
|
+
save_non_critical_data! if server.cycles % 1000 == 0
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
### Examples
|
132
|
+
|
133
|
+
See examples under `example` folder.
|
37
134
|
|
38
|
-
|
135
|
+
#### Implementing a counter server
|
39
136
|
|
40
137
|
A simple counter server source code listing:
|
41
138
|
|
@@ -123,7 +220,7 @@ server.start
|
|
123
220
|
|
124
221
|
```
|
125
222
|
|
126
|
-
|
223
|
+
#### Connect using `redis-cli`
|
127
224
|
|
128
225
|
```shell
|
129
226
|
$ redis-cli -p 3721
|
@@ -166,7 +263,9 @@ total_commands_processed:6
|
|
166
263
|
(error) ERR unknown command 'xx'
|
167
264
|
```
|
168
265
|
|
169
|
-
|
266
|
+
Hint: if you are adventurous, try `info all`
|
267
|
+
|
268
|
+
#### Benchmarking with `redis-benchmark`
|
170
269
|
|
171
270
|
```shell
|
172
271
|
$ redis-benchmark -p 3721 incr
|
@@ -196,6 +295,183 @@ Since it is single-threaded, the count will be correct:
|
|
196
295
|
(integer) 10000
|
197
296
|
```
|
198
297
|
|
298
|
+
#### Implementing blocked commands
|
299
|
+
|
300
|
+
Some commands will be blocking: they may either depend on external services or need some background tasks to be run.
|
301
|
+
|
302
|
+
The clients will expect those commands to be blocking calls, they will not return until the commands are finished, but we don't want the server to be blocked as well.
|
303
|
+
|
304
|
+
Therefore we introduce `Server#blocking` methods, execution wrapped in this method call will be run in a background thread pool, and the client will be on hold until that task is finished.
|
305
|
+
|
306
|
+
Example: see `examples/blocking` folder.
|
307
|
+
|
308
|
+
```ruby
|
309
|
+
# blocking/command_runner.rb
|
310
|
+
|
311
|
+
module Blocking
|
312
|
+
class CommandRunner < RDKit::RESPRunner
|
313
|
+
attr_reader :core
|
314
|
+
|
315
|
+
def initialize(core)
|
316
|
+
@core = core
|
317
|
+
end
|
318
|
+
|
319
|
+
def block_with_callback
|
320
|
+
core.block_with_callback
|
321
|
+
|
322
|
+
# this is ignored, instead `on_success` block of `core.block_with_callback` is evaluated and returned
|
323
|
+
'OK'
|
324
|
+
end
|
325
|
+
|
326
|
+
def block
|
327
|
+
core.block
|
328
|
+
|
329
|
+
'OK'
|
330
|
+
end
|
331
|
+
|
332
|
+
def nonblock
|
333
|
+
core.nonblock
|
334
|
+
|
335
|
+
'OK'
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# blocking/core.rb
|
341
|
+
|
342
|
+
module Blocking
|
343
|
+
class Core < RDKit::Core
|
344
|
+
def block_with_callback
|
345
|
+
on_success = lambda { 'success' }
|
346
|
+
|
347
|
+
server.blocking(on_success) { do_something }
|
348
|
+
end
|
349
|
+
|
350
|
+
def block
|
351
|
+
server.blocking { do_something }
|
352
|
+
end
|
353
|
+
|
354
|
+
def nonblock
|
355
|
+
do_something
|
356
|
+
end
|
357
|
+
|
358
|
+
def do_something
|
359
|
+
sleep 1
|
360
|
+
end
|
361
|
+
|
362
|
+
def tick!
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
```
|
367
|
+
|
368
|
+
Running:
|
369
|
+
|
370
|
+
```shell
|
371
|
+
$ redis-cli -p 3721
|
372
|
+
127.0.0.1:3721> block
|
373
|
+
OK
|
374
|
+
(1.03s)
|
375
|
+
127.0.0.1:3721> nonblock
|
376
|
+
OK
|
377
|
+
(1.01s)
|
378
|
+
127.0.0.1:3721> block_with_callback
|
379
|
+
"success"
|
380
|
+
(1.02s)
|
381
|
+
```
|
382
|
+
|
383
|
+
Benchmarking:
|
384
|
+
|
385
|
+
```shell
|
386
|
+
$ redis-benchmark -p 3721 -n 10 block
|
387
|
+
====== block ======
|
388
|
+
10 requests completed in 1.03 seconds
|
389
|
+
50 parallel clients
|
390
|
+
3 bytes payload
|
391
|
+
keep alive: 1
|
392
|
+
|
393
|
+
10.00% <= 1027 milliseconds
|
394
|
+
100.00% <= 1027 milliseconds
|
395
|
+
9.73 requests per second
|
396
|
+
|
397
|
+
$ redis-benchmark -p 3721 -n 10 nonblock
|
398
|
+
====== nonblock ======
|
399
|
+
10 requests completed in 10.04 seconds
|
400
|
+
50 parallel clients
|
401
|
+
3 bytes payload
|
402
|
+
keep alive: 1
|
403
|
+
|
404
|
+
10.00% <= 1001 milliseconds
|
405
|
+
20.00% <= 2005 milliseconds
|
406
|
+
30.00% <= 3010 milliseconds
|
407
|
+
40.00% <= 4013 milliseconds
|
408
|
+
50.00% <= 5018 milliseconds
|
409
|
+
60.00% <= 6022 milliseconds
|
410
|
+
70.00% <= 7027 milliseconds
|
411
|
+
80.00% <= 8030 milliseconds
|
412
|
+
90.00% <= 9034 milliseconds
|
413
|
+
100.00% <= 10039 milliseconds
|
414
|
+
1.00 requests per second
|
415
|
+
|
416
|
+
```
|
417
|
+
|
418
|
+
See the difference between blocking and non-blocking commands?
|
419
|
+
|
420
|
+
### Implemented Redis Commands
|
421
|
+
|
422
|
+
| command | support | note |
|
423
|
+
|-------------|--------------------------------------|----------------------------------------------|
|
424
|
+
| `info` | full | additional `objspace` and `gc` commands |
|
425
|
+
| `ping` | full | |
|
426
|
+
| `echo` | full | |
|
427
|
+
| `time` | full | |
|
428
|
+
| `select` | partial/compatible | `redis-benchmark` requires `select` command |
|
429
|
+
| `config` | `get`, `set`, `resetstat` | |
|
430
|
+
| `slowlog` | full | |
|
431
|
+
| `client` | `getname`, `setname`, `list`, `kill` | `kill` filter only supports `id`, `addr` |
|
432
|
+
| `monitor` | full | |
|
433
|
+
| `debug` | `sleep`, `segfault` | |
|
434
|
+
| `shutdown` | full | |
|
435
|
+
| `get` | full | |
|
436
|
+
| `set` | without options | |
|
437
|
+
| `del` | full | |
|
438
|
+
| `keys` | without pattern (return all) | |
|
439
|
+
| `lpush` | full | |
|
440
|
+
| `lpop` | full | |
|
441
|
+
| `rpop` | full | |
|
442
|
+
| `llen` | full | |
|
443
|
+
| `lrange` | partial (not fully tested) | |
|
444
|
+
| `exists` | full | |
|
445
|
+
| `flushdb` | full | |
|
446
|
+
| `flushall` | full | |
|
447
|
+
| `mget` | full | |
|
448
|
+
| `mset` | full | |
|
449
|
+
| `strlen` | full | |
|
450
|
+
| `sadd` | full | |
|
451
|
+
| `scard` | full | |
|
452
|
+
| `smembers` | full | |
|
453
|
+
| `sismember` | full | |
|
454
|
+
| `srem` | full | |
|
455
|
+
| `hset` | full | |
|
456
|
+
| `hget` | full | |
|
457
|
+
| `hexists` | full | |
|
458
|
+
| `hlen` | full | |
|
459
|
+
| `hstrlen` | full | |
|
460
|
+
| `hdel` | full | |
|
461
|
+
| `hkeys` | full | |
|
462
|
+
| `hvals` | full | |
|
463
|
+
| `setnx` | full | |
|
464
|
+
| `getset` | full | |
|
465
|
+
|
466
|
+
|
467
|
+
### Implemented Additional Commands
|
468
|
+
|
469
|
+
| command | description |
|
470
|
+
|-------------|----------------------------------------------------------------------------|
|
471
|
+
| `gc` | start garbage collection immediately |
|
472
|
+
| `heapdump` | `ObjectSpace.dump_all` to ./tmp |
|
473
|
+
|
474
|
+
|
199
475
|
## Development
|
200
476
|
|
201
477
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Vagrantfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
VAGRANTFILE_API_VERSION = "2"
|
2
|
+
|
3
|
+
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
4
|
+
config.vm.box = "ubuntu/trusty64"
|
5
|
+
|
6
|
+
config.vm.define 'redis' do |c|
|
7
|
+
config.vm.provision "shell", inline: "sudo apt-get install redis-tools -y"
|
8
|
+
|
9
|
+
c.vm.provision "docker" do |docker|
|
10
|
+
docker.run "redis", args: "-p 6379:6379"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|