Non-blocking Redis client with focus on performance and robustness.
Supported Redis features:
Any command, through eredis:q/2
Transactions
Pipelining
Authentication & multiple dbs
Pubsub
If you have Redis running on localhost, with default settings, you may copy and paste the following into a shell to try out Eredis:
MSET and MGET:
HASH
LIST
Transactions:
Pipelining:
Pubsub:
Pattern Subscribe:
EUnit tests:
Eredis has one main function to interact with redis, which is <code>eredis:q(Client::pid(), Command::iolist())</code>. The response will either be <code>{ok, Value::binary() | [binary()]}</code> or <code>{error, Message::binary()}</code>. 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.
To send multiple requests to redis in a batch, aka. pipelining requests, you may use <code>eredis:qp(Client::pid(), [Command::iolist()])</code>. This function returns <code>{ok, [Value::binary()]}</code> where the values are the redis responses in the same order as the commands you provided.
To start the client, use any of the <code>eredis:start_link/0,1,2,3,4,5</code> functions. They all include sensible defaults. <code>start_link/5</code>takes the following arguments:
Host, dns name or ip adress as string
Port, integer, default is 6379
Database, integer or 0 for default database
Password, string or empty string([]) for no password
Reconnect sleep, integer of milliseconds to sleep between reconnect attempts
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 <code>AUTH</code> and <code>SELECT</code> calls. The sleep time between attempts to reconnect can be set in the <code>eredis:start_link/5</code> call.
As long as the connection is down, Eredis will respond to any request immediately with <code>{error, no_connection}</code> 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 <code>gen_server:call</code> timeout.
Note: If Eredis is starting up and cannot connect, it will fail immediately with <code>{connection_error, Reason}</code>.
Thanks to Dave Peticolas (jdavisp3), eredis supports pubsub. <code>eredis_sub</code> 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 <code>eredis_sub:ack_message/1</code>.
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.
Subscriptions are managed using <code>eredis_sub:subscribe/2</code> and <code>eredis_sub:unsubscribe/2</code>. When Redis acknowledges the change in subscription, a message is sent to the controlling process for each channel.
eredis also supports Pattern Subscribe using <code>eredis_sub:psubscribe/2</code> and <code>eredis_sub:unsubscribe/2</code>. As with normal subscriptions, a message is sent to the controlling process for each channel.
As of v1.0.7 the controlling process will be notified in case of reconnection attempts or failures. See <code>test/eredis_sub_tests</code>for details.
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 <code>AUTH</code> and <code>SELECT</code> commands appropriately, even when reconnecting after a timeout.
Using basho_bench(https://github.com/basho/basho_bench/) you may benchmark Eredis on your own hardware using the provided config and driver. See <code>priv/basho_bench_driver_eredis.config</code> and <code>src/basho_bench_driver_eredis.erl</code>.
Eredis uses the same queueing mechanism as Erldis. <code>eredis:q/2</code> uses <code>gen_server:call/2</code> 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 <code>noreply</code>. This frees the gen_server up to accept new requests and parse responses as they come on the socket.
When data is received on the socket, we call <code>eredis_parser:parse/2</code> until it returns a value, we then use <code>gen_server:reply/2</code>to reply to the first process waiting in the queue.
This queueing mechanism works because Redis guarantees that the response will be in the same order as the requests.
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 <code>gen_tcp:recv/2</code> to get more data when needed. Profiling identified this as a bottleneck, in particular for <code>MGET</code> and <code>HMGET</code>.
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.
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.
When the parser is accumulating data, a new binary is generated for every call to <code>parse/2</code>. This might create binaries that will be reference counted. This could be improved by replacing it with an iolist.
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 <code>gen_tcp:recv/2</code> to fetch the entire bulk at once.
Although this project is almost a complete rewrite, many patterns are the same as you find in Erldis, most notably the queueing of requests.
<code>create_multibulk/1</code> and <code>to_binary/1</code> were taken verbatim from Erldis.
本文作者:陈群
本文来自云栖社区合作伙伴rediscn,了解相关信息可以关注redis.cn网站。