forked from wooga/eredis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoverview.edoc
306 lines (250 loc) · 8.63 KB
/
overview.edoc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
@title eredis
@doc
[![Build Status](https://travis-ci.org/emedia-project/eredis.svg?branch=master)](https://travis-ci.org/emedia-project/eredis)
<p>Non-blocking Redis client with focus on performance and robustness.</p>
<p>
Supported Redis features:
<ul>
<li>Any command, through <tt>eredis:q/2</tt></li>
<li>Transactions</li>
<li>Pipelining</li>
<li>Authentication & multiple dbs</li>
<li>Pubsub</li>
</ul>
</p>
<h2>Example</h2>
<p>
If you have Redis running on localhost, with default settings, you may
copy and paste the following into a shell to try out Eredis:
</p>
<pre>
git clone git://github.com/wooga/eredis.git
cd eredis
./rebar compile
erl -pa ebin/
{ok, C} = eredis:start_link().
{ok, <<"OK">>} = eredis:q(C, ["SET", "foo", "bar"]).
{ok, <<"bar">>} = eredis:q(C, ["GET", "foo"]).
</pre>
<p>
MSET and MGET:
<pre>
KeyValuePairs = ["key1", "value1", "key2", "value2", "key3", "value3"].
{ok, <<"OK">>} = eredis:q(C, ["MSET" | KeyValuePairs]).
{ok, Values} = eredis:q(C, ["MGET" | ["key1", "key2", "key3"]]).
</pre>
</p>
<p>
HASH
<pre>
HashObj = ["id", "objectId", "message", "message", "receiver", "receiver", "status", "read"].
eredis:q(C, ["HMSET", "key" | HashObj]).
{ok, Values} = eredis:q(C, ["HGETALL", "key"]).
</pre>
</p>
<p>
LIST
<pre>
eredis:q(C, ["LPUSH", "keylist", "value"]).
eredis:q(C, ["RPUSH", "keylist", "value"]).
eredis:q(C, ["LRANGE", "keylist",0,-1]).
</pre>
</p>
<p>
Transactions:
<pre>
{ok, <<"OK">>} = eredis:q(C, ["MULTI"]).
{ok, <<"QUEUED">>} = eredis:q(C, ["SET", "foo", "bar"]).
{ok, <<"QUEUED">>} = eredis:q(C, ["SET", "bar", "baz"]).
{ok, [<<"OK">>, <<"OK">>]} = eredis:q(C, ["EXEC"]).
</pre>
</p>
<p>
Pipelining:
<pre>
P1 = [["SET", a, "1"],
["LPUSH", b, "3"],
["LPUSH", b, "2"]].
[{ok, <<"OK">>}, {ok, <<"1">>}, {ok, <<"2">>}] = eredis:qp(C, P1).
</pre>
</p>
<p>
Pubsub:
<pre>
1> eredis_sub:sub_example().
received {subscribed,<<"foo">>,<0.34.0>}
{<0.34.0>,<0.37.0>}
2> eredis_sub:pub_example().
received {message,<<"foo">>,<<"bar">>,<0.34.0>}
</pre>
</p>
<p>
Pattern Subscribe:
<pre>
1> eredis_sub:psub_example().
received {subscribed,<<"foo*">>,<0.33.0>}
{<0.33.0>,<0.36.0>}
2> eredis_sub:ppub_example().
received {pmessage,<<"foo*">>,<<"foo123">>,<<"bar">>,<0.33.0>}
ok
3>
</pre>
</p>
<p>
EUnit tests:
<pre>
make tests
</pre>
</p>
<h2>Commands</h2>
<p>
Eredis has one main function to interact with redis, which is
<tt>eredis:q(Client::pid(), Command::iolist())</tt>. The response will either
be <tt>{ok, Value::binary() | [binary()]}</tt> or <tt>{error, Message::binary()}</tt>.
The value is always the exact value returned by
Redis, without any type conversion. If Redis returns a list of values,
this list is returned in the exact same order without any type
conversion.
</p>
<p>
To send multiple requests to redis in a batch, aka. pipelining
requests, you may use <tt>eredis:qp(Client::pid(), [Command::iolist()])</tt>.
This function returns <tt>{ok, [Value::binary()]}</tt>
where the values are the redis responses in the same order as the
commands you provided.
</p>
<p>
To start the client, use any of the <tt>eredis:start_link/0,1,2,3,4,5</tt>
functions. They all include sensible defaults. <tt>start_link/5</tt> takes
the following arguments:
<ul>
<li>Host, dns name or ip adress as string</li>
<li>Port, integer, default is 6379</li>
<li>Database, integer or 0 for default database</li>
<li>Password, string or empty string([]) for no password</li>
<li>Reconnect sleep, integer of milliseconds to sleep between reconnect attempts</li>
</ul>
</p>
<h2>Reconnecting on Redis down / network failure / timeout / etc</h2>
<p>
When Eredis for some reason looses the connection to Redis, Eredis
will keep trying to reconnect until a connection is successfully
established, which includes the <tt>AUTH</tt> and <tt>SELECT</tt> calls. The sleep
time between attempts to reconnect can be set in the
<tt>eredis:start_link/5</tt> call.
</p>
<p>
As long as the connection is down, Eredis will respond to any request
immediately with <tt>{error, no_connection}</tt> without actually trying to
connect. This serves as a kind of circuit breaker and prevents a
stampede of clients just waiting for a failed connection attempt or
<tt>gen_server:call</tt> timeout.
</p>
<p>
Note: If Eredis is starting up and cannot connect, it will fail
immediately with <tt>{connection_error, Reason}</tt>.
</p>
<h2>Pubsub</h2>
<p>
Thanks to Dave Peticolas (jdavisp3), eredis supports
pubsub. <tt>eredis_sub</tt> offers a separate client that will forward
channel messages from Redis to an Erlang process in a "active-once"
pattern similar to gen_tcp sockets. After every message sent, the
controlling process must acknowledge receipt using
<tt>eredis_sub:ack_message/1</tt>.
</p>
<p>
If the controlling process does not process messages fast enough,
eredis will queue the messages up to a certain queue size controlled
by configuration. When the max size is reached, eredis will either
drop messages or crash, also based on configuration.
</p>
<p>
Subscriptions are managed using <tt>eredis_sub:subscribe/2</tt> and
<tt>eredis_sub:unsubscribe/2</tt>. When Redis acknowledges the change in
subscription, a message is sent to the controlling process for each
channel.
</p>
<p>
eredis also supports Pattern Subscribe using <tt>eredis_sub:psubscribe/2</tt>
and <tt>eredis_sub:unsubscribe/2</tt>. As with normal subscriptions, a message
is sent to the controlling process for each channel.
</p>
<p>
As of v1.0.7 the controlling process will be notified in case of
reconnection attempts or failures. See <tt>test/eredis_sub_tests</tt> for
details.
</p>
<h2>AUTH and SELECT</h2>
<p>
Eredis also implements the AUTH and SELECT calls for you. When the
client is started with something else than default values for password
and database, it will issue the <tt>AUTH</tt> and <tt>SELECT</tt> commands
appropriately, even when reconnecting after a timeout.
</p>
<h2>Benchmarking</h2>
<p>
Using basho_bench(https://github.com/basho/basho_bench/) you may
benchmark Eredis on your own hardware using the provided config and
driver. See <tt>priv/basho_bench_driver_eredis.config</tt> and
<tt>src/basho_bench_driver_eredis.erl</tt>.
</p>
<h2>Queueing</h2>
<p>
Eredis uses the same queueing mechanism as Erldis. <tt>eredis:q/2</tt> uses
<tt>gen_server:call/2</tt> to do a blocking call to the client
gen_server. The client will immediately send the request to Redis, add
the caller to the queue and reply with <tt>noreply</tt>. This frees the
gen_server up to accept new requests and parse responses as they come
on the socket.
</p>
<p>
When data is received on the socket, we call <tt>eredis_parser:parse/2</tt>
until it returns a value, we then use <tt>gen_server:reply/2</tt> to reply to
the first process waiting in the queue.
</p>
<p>
This queueing mechanism works because Redis guarantees that the
response will be in the same order as the requests.
</p>
<h2>Response parsing</h2>
<p>
The response parser is the biggest difference between Eredis and other
libraries like Erldis, redis-erl and redis_pool. The common approach
is to either directly block or use active once to get the first part
of the response, then repeatedly use <tt>gen_tcp:recv/2</tt> to get more data
when needed. Profiling identified this as a bottleneck, in particular
for <tt>MGET</tt> and <tt>HMGET</tt>.
</p>
<p>
To be as fast as possible, Eredis takes a different approach. The
socket is always set to active once, which will let us receive data
fast without blocking the gen_server. The tradeoff is that we must
parse partial responses, which makes the parser more complex.
</p>
<p>
In order to make multibulk responses more efficient, the parser
will parse all data available and continue where it left off when more
data is available.
</p>
<h2>Future improvements</h2>
<p>
When the parser is accumulating data, a new binary is generated for
every call to <tt>parse/2</tt>. This might create binaries that will be
reference counted. This could be improved by replacing it with an
iolist.
</p>
<p>
When parsing bulk replies, the parser knows the size of the bulk. If the
bulk is big and would come in many chunks, this could improved by
having the client explicitly use <tt>gen_tcp:recv/2</tt> to fetch the entire
bulk at once.
</p>
<h2>Credits</h2>
<p>
Although this project is almost a complete rewrite, many patterns are
the same as you find in Erldis, most notably the queueing of requests.
</p>
<p>
<tt>create_multibulk/1</tt> and <tt>to_binary/1</tt> were taken verbatim from Erldis.
</p>