Giter Club home page Giter Club logo

eredis's Introduction

eredis

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

Example

If you have Redis running on localhost, with default settings, you may copy and paste the following into a shell to try out Eredis:

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"]).

To connect to a Redis instance listening on a Unix domain socket:

{ok, C1} = eredis:start_link({local, "/var/run/redis.sock"}, 0).

MSET and MGET:

KeyValuePairs = ["key1", "value1", "key2", "value2", "key3", "value3"].
{ok, <<"OK">>} = eredis:q(C, ["MSET" | KeyValuePairs]).
{ok, Values} = eredis:q(C, ["MGET" | ["key1", "key2", "key3"]]).

HASH

HashObj = ["id", "objectId", "message", "message", "receiver", "receiver", "status", "read"].
eredis:q(C, ["HMSET", "key" | HashObj]).
{ok, Values} = eredis:q(C, ["HGETALL", "key"]).

LIST

eredis:q(C, ["LPUSH", "keylist", "value"]).
eredis:q(C, ["RPUSH", "keylist", "value"]).
eredis:q(C, ["LRANGE", "keylist",0,-1]).

Transactions:

{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"]).

Pipelining:

P1 = [["SET", a, "1"],
      ["LPUSH", b, "3"],
      ["LPUSH", b, "2"]].
[{ok, <<"OK">>}, {ok, <<"1">>}, {ok, <<"2">>}] = eredis:qp(C, P1).

Pubsub:

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>}

Pattern Subscribe:

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> 

EUnit tests:

./rebar eunit

Commands

Eredis has one main function to interact with redis, which is eredis:q(Client::pid(), Command::iolist()). The response will either be {ok, Value::binary() | [binary()]} or {error, Message::binary()}. 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 eredis:qp(Client::pid(), [Command::iolist()]). This function returns {ok, [Value::binary()]} where the values are the redis responses in the same order as the commands you provided.

To start the client, use any of the eredis:start_link/0,1,2,3,4,5,6,7 functions. They all include sensible defaults. start_link/7 takes the following arguments:

  • Host, dns name or ip adress as string; or unix domain socket as {local, Path} (available in OTP 19+)
  • 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
  • Connect timeout, timeout value in milliseconds to use in gen_tcp:connect, default is 5000
  • Socket options, proplist of options to be sent to gen_tcp:connect, default is ?SOCKET_OPTS

Reconnecting on Redis down / network failure / timeout / etc

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 AUTH and SELECT calls. The sleep time between attempts to reconnect can be set in the eredis:start_link/5 call.

As long as the connection is down, Eredis will respond to any request immediately with {error, no_connection} 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 gen_server:call timeout.

Note: If Eredis is starting up and cannot connect, it will fail immediately with {connection_error, Reason}.

Pubsub

Thanks to Dave Peticolas (jdavisp3), eredis supports pubsub. eredis_sub 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 eredis_sub:ack_message/1.

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 eredis_sub:subscribe/2 and eredis_sub:unsubscribe/2. When Redis acknowledges the change in subscription, a message is sent to the controlling process for each channel.

eredis also supports Pattern Subscribe using eredis_sub:psubscribe/2 and eredis_sub:unsubscribe/2. 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 test/eredis_sub_tests for details.

AUTH and SELECT

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 AUTH and SELECT commands appropriately, even when reconnecting after a timeout.

Benchmarking

Using basho_bench(https://github.com/basho/basho_bench/) you may benchmark Eredis on your own hardware using the provided config and driver. See priv/basho_bench_driver_eredis.config and src/basho_bench_driver_eredis.erl.

Queueing

Eredis uses the same queueing mechanism as Erldis. eredis:q/2 uses gen_server:call/2 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 noreply. 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 eredis_parser:parse/2 until it returns a value, we then use gen_server:reply/2 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.

Response parsing

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 gen_tcp:recv/2 to get more data when needed. Profiling identified this as a bottleneck, in particular for MGET and HMGET.

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.

Future improvements

When the parser is accumulating data, a new binary is generated for every call to parse/2. 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 gen_tcp:recv/2 to fetch the entire bulk at once.

Credits

Although this project is almost a complete rewrite, many patterns are the same as you find in Erldis, most notably the queueing of requests.

create_multibulk/1 and to_binary/1 were taken verbatim from Erldis.

eredis's People

Contributors

basiliscos avatar dialtone avatar drobakowski avatar fenek avatar flyinghail avatar georgeye avatar getong avatar hairyhum avatar hernanrivasacosta avatar igors avatar jdavisp3 avatar kevinwilson541 avatar knutin avatar kwisatx avatar lafka avatar loxs avatar mgregson avatar michalwski avatar mkurkov avatar mrnovalles avatar nacmartin avatar nifoc avatar olgeni avatar pmembrey avatar sumerman avatar th0114nd avatar tnt-dev avatar tomlion avatar tuxofil avatar vnikitin-reksoft avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eredis's Issues

eredis Crash is killing my microservice.

Hi

we are using eredis-client with otp-19 and elixir 1.6.6 in our application whenever following things appeared in crash log , it is killing my application. I am using Elastic Cache Single node Redis. Any Idea?


crasher:
initial call: eredis_client:init/1
pid: <0.24202.0>
registered_name: []
exception exit: {bad_return_value,empty_queue}
in function gen_server:terminate/7 (gen_server.erl, line 812)
ancestors: [<0.24201.0>]
messages: [{tcp,#Port<0.90462>,
<<"00\blanguagem\x00\x00\x00\x02hid\x00\x13profile_image_smallm\x00\x00\x00\x00d\x00\x04typed\x00\x03nild\x00\rcreated_at_tsb[\xc5\xe0\xd1d\x00\akeywordm\x00\x00\x00\x06PMMODId\x00\ais_pagem\x00\x00\x00\x00d\x00\x0fis_voke_allowedd\x00\x04trued\x00\ahashtagl\x00\x00\x00\x04t\x00\x00\x00\bm\x00\x00\x00\x04iconm\x00\x00\x00Yhttps://s3.ap-south-1.amazonaws.com/ok.bots.image/Testing+image/ta_icon_tags/politics.pngm\x00\x00\x00\x02idaXm\x00\x00\x00\x0cis_followingd\x00\x04truem\x00\x00\x00\x05titlem\x00\x00\x00\bpoliticsm\x00\x00\x00\btitle_enm\x00\x00\x00\bPoliticsm\x00\x00\x00\btitle_him\x00\x00\x00\x15\xe0\xa4\xb0\xe0\xa4\xbe\xe0\xa4\x9c\xe0\xa4\xa8\xe0\xa5\x80\xe0\xa4\xa4\xe0\xa4\xbfm\x00\x00\x00\btitle_knm\x00\x00\x00\x00m\x00\x00\x00\btitle_tam\x00\x00\x00\x15\xe0\xae\x85\xe0\xae\xb0\xe0\xae\x9a\xe0\xae\xbf\xe0\xae\xaf\xe0\xae\xb2\xe0\xaf\x8dt\x00\x00\x00\bm\x00\x00\x00\x04iconm\x00\x00\x00\x00m\x00\x00\x00\x02idasm\x00\x00\x00\x0cis_followingd\x00\x05falsem\x00\x00\x00\x05titlem\x00\x00\x00\x06pmmodim\x00\x00\x00\btitle_enm\x00\x00\x00\aPM Modim\x00\x00\x00\btitle_him\x00\x00\x00\x0fPM \xe0\xa4\xae\xe0\xa5\x8b\xe0\xa4\xa6\xe0\xa5\x80m\x00\x00\x00\btitle_knm\x00\x00\x00\x00m\x00\x00\x00\btitle_tam\x00\x00\x00\x0f\xe0\xae\xae\xe0\xaf\x87\xe0\xae\xbe\xe0\xae\x9f\xe0\xae\xbft\x00\x00\x00\bm\x00\x00\x00\x04iconm\x00\x00\x00\x00m\x00\x00\x00\x02idb\x00\x00i\x93m\x00\x00\x00\x0cis_followingd\x00\x05falsem\x00\x00\x00\x05titlem\x00\x00\x00\rprim">>}]
links: [<0.24201.0>]
dictionary: []
trap_exit: false
status: running
heap_size: 610
stack_size: 27
reductions: 833
neighbours:
neighbour: [{pid,<0.24201.0>},
{registered_name,[]},
{initial_call,{cowboy_protocol,init,4}},
{current_function,{gen,do_call,4}},
{ancestors,[]},
{messages,[{#Ref<0.0.4.61368>,{ok,<<"OK">>}}]},
{links,[<0.641.0>,<0.24202.0>,#Port<0.90452>]},
{dictionary,
[{logger_metadata,
{true,
[{request_id,
<<"4gp55n1e0skebdc78j5lcgptsteplg5f">>}]}}]},
{trap_exit,false},
{status,runnable},
{heap_size,1598},
{stack_size,64},
{reductions,1969}]
[os_mon] cpu supervisor port (cpu_sup): Erlang has closed
[os_mon] memory supervisor port (memsup): Erlang has closed

reuseaddr setting to true cased eaddrnotavail in SOCKET_OPTS

Hi,

The concurrent connections in my project was about 30,000. I could find CRASH: {connection_error,{connection_error,eaddrnotavail}} in a percentage of 0.1%.
The reuseaddr is set to true and looks like it is the root cause. The client would try to reuse the same port in time_wait status, but encounters eaddrnotavail error.
Please refer to http://stackoverflow.com/questions/17606503/redis-exception-raised-in-gen-server/26523211#26523211

After change the code to:
-define(SOCKET_OPTS, [binary, {active, once}, {packet, raw}, {reuseaddr, false}]).
There is no more eaddrnotavail error.

crash in eredis

I got a crash with the following core stack. By using redis pool, no obvious reason why {unhandled_message,{connection_ready,#Port<0.1914682>}} is unhandled here.
------------------------------------>

[20601]: 17:03:36.790 [error] GenServer #PID<0.3744.3> terminating
[20601]: ** (stop) {:unhandled_message, {:connection_ready, #Port<0.1914682>}}
[20601]:
[20601]: =CRASH REPORT==== 14-Jul-2017::17:03:36 ===
[20601]: crasher:
[20601]: initial call: eredis_client:init/1
[20601]: pid: <0.3744.3>
[20601]: registered_name: []
[20601]: exception exit: {unhandled_message,{connection_ready,#Port<0.1914682>}}
[20601]: in function gen_server:terminate/7 (gen_server.erl, line 826)
[20601]: ancestors: [<0.1633.0>,eredis_pool_sup,<0.1378.0>]
[20601]: messages: []
[20601]: links: [<0.1632.0>,<0.1633.0>,#Port<0.1914682>]
[20601]: dictionary: []
[20601]: trap_exit: false
[20601]: status: running
[20601]: heap_size: 610
[20601]: stack_size: 27
[20601]: reductions: 1796
[20601]: neighbours:
[20601]:
[20601]: =SUPERVISOR REPORT==== 14-Jul-2017::17:03:36 ===
[20601]: Supervisor: {<0.1633.0>,poolboy_sup}
[20601]: Context: child_terminated
[20601]: Reason: {unhandled_message,{connection_ready,#Port<0.1914682>}}
[20601]: Offender: [{pid,<0.3744.3>},
[20601]: {id,eredis},
[20601]: {mfargs,{eredis,start_link,undefined}},
[20601]: {restart_type,temporary},
[20601]: {shutdown,5000},
[20601]: {child_type,worker}]

Is there any method like qp_noreply?

Hi there,
I use eredis as client to store erlang server's log, and I don't care whether redis command executed success or failure, so is there any method like qp_noreply? For log storage, LPUSH+LTRIM is a common pipeline. thanks!

Parser doesn't handle multibulks in multibulks

A MULTI ... EXEC sequence can include calls that return multibulks, and will then be embedded inside the multibulk reply to EXEC.

e.g., to pop stuff older than a certain time from a sorted set:
MULTI
ZRANGEBYSCORE somekey -inf
ZREMRANGEBYSCORE somekey -inf
EXEC

This will currently crash eredis with an error like "no function clause matching eredis_parser:parse_bulk(<<"*2\r\n$3\r\nhah\r\n$4\r\nwhat\r\n:2\r\n">>)"

Add support for starting without requiring redis server being available

I have a slightly larger application. Redis (and eredis) are in no way essential for its functionality. Only "good to have sometimes".

If I shut down redis after starting my application, I'm totally fine now. Eredis (via poolboy) gets reconnected when and if redis comes back up.

But I can't seem to be able to start my applications without having redis up. Poolboy/eredis crashes and brings down my app that spawns it. I could flag that pool as transient, but that means it will never start again if redis becomes available.

So, do you think it's a good idea modify eredis in a way that it's able to start, and connect to redis later if at all?

I am willing to do the required changes myself, if there is a will for them being accepted, and after we have an understanding what to change exactly.

So what do you guys think?

Makefile broken on mac os X

When using the provided Makefile, some commands fail.

This is due to the long options (--xxxx) used in mkdir, cp, rm.

Switching to short options fixes the problem.

mismatch between docs and api

in README eredis:start_link/4 is documented to be called like eredis:start_link(Host, Port, Password, DB). This is not true. The code requres that you pass the arduments in different order eredis:start_link(Host, Port, DB, Password)

exit messages {'EXIT',<0.13696.0>,normal}

Hello there, I've been using eredis and it has worked perfect all this time.
I am using eredis through a small wrapper module to simplify calls and query patterns.

The next function wraps a query abstracting the conection to eredis:

redisq(Q) -> C = redis_conn(), {ok, V} = eredis:q(C, Q), eredis:stop(C), V.

This worked just fine until otp21. The data gets retrieved but now
the caller process gets exit messages with the form {'EXIT',<0.13696.0>,normal}

I had to modify the wrapper query function to this to avoid leaking messages:
redisq(Q) -> Parent = self(), Ref = make_ref(), spawn(fun() -> C = redis_conn(), {ok, V} = eredis:q(C, Q), eredis:stop(C), Parent ! {Ref, V} end), receive {Ref, V} -> V end.

Is it normal to receive that 'EXIT' messages or am I using eredis badly?

I can not compile eredis (Erlang/OTP 18)

Compiling /deps/eredis/src/eredis_sub_client.erl
Line 24: error {undefined_type,{queue,0}} in "/deps/eredis/include/eredis.hrl"
ERROR: []
escript: exception error: no function clause matching
lists:flatten({return,{return,[{error,[]}]}}) (lists.erl, line 615)
in function mad:main/1 (/Users/5HT/depot/synrc/mad/src/mad.erl, line 17)
in call from escript:run/2 (escript.erl, line 757)
in call from escript:start/1 (escript.erl, line 277)
in call from init:start_it/1
in call from init:start_em/1

Makefile doesn't use rebar and is broken for >R17

$ make
mkdir -p ebin
cp -f -- src/eredis.app.src ebin/eredis.app
sed "s/{{EXTRA_OPTS}}//" Emakefile.src > Emakefile
erl -noinput -eval 'up_to_date = make:all()' -s erlang halt
Recompile: src/eredis_sub_client
include/eredis.hrl:24: type queue() undefined
{"init terminating in do_boot",{{badmatch,error},[{erl_eval,expr,3,[]}]}}

Crash dump is being written to: erl_crash.dump...done
init terminating in do_boot ()
Makefile:8: recipe for target 'compile' failed
make: *** [compile] Error 1
$ rebar compile
==> eredis (compile)
Compiled src/basho_bench_driver_erldis.erl
Compiled src/eredis_sub_client.erl
Compiled src/eredis_client.erl
Compiled src/eredis_sub.erl
Compiled src/basho_bench_driver_eredis.erl
Compiled src/eredis.erl
Compiled src/eredis_parser.erl

BTW embeded rebar is quite outdated.

$ ./rebar --version
rebar version: 2 date: 20110304_023706 vcs: git d725e5f

Failing mkdir with --parents option.

screen shot 2014-08-12 at 2 49 43 pm
I'm not sure why exactly, but I'm having trouble with the --parents option to mkdir.

This is on OSX 10.9.4, also using erlang.mk for the main project.
screen shot 2014-08-12 at 2 49 00 pm

rebar doc error

E:\github\eredis>d:/tools/rebar/rebar.cmd doc
==> eredis (doc)
./src/eredis.erl, function q_noreply/2: at line 101: unknown error parsing refer
ence: {102,edoc_parser,
                                  ["syntax error before: ",["Executes"]]}.
edoc: skipping source file './src/eredis.erl': {'EXIT',error}.
edoc: error in doclet 'edoc_doclet': {'EXIT',error}.
ERROR: doc failed while processing E:/github/eredis: {'EXIT',error}

Add pools

Why there is not a pool to control the connections like reddy?

:nxdomain when using redis cluster endpoint with large number of shards

This is with eredis_cluster ~> 0.5.12

When using eredis pointing to a redis cluster endpoint with large number of shards, I get error :nxdomain. The only workaround is to use a list of ip addresses.

It is because, :inet.getaddr/2 unable to handle dns lookups that result in a large answers. Note however that :inet_res.gethostbyname/1 works fine likely because it reissues the dns lookup over TCP when it finds that the first query had the truncate bit set.

Offending code here: https://github.com/wooga/eredis/blob/master/src/eredis_client.erl#L335 (line 335, and line 307)

Below is a sample iex output that shows the error
Note the :nxdomain error in the second command, same hostname works fine in the fourth command.


iex(tango@2e76e9718be1)69> :inet.getaddr('small-redis-cluster.kk5zzw.clustercfg.use1.cache.amazonaws.com', :inet)   
{:ok, {10, 99, 17, 14}}

iex(tango@2e76e9718be1)70> :inet.getaddr('large-redis-cluster.kk5zzw.clustercfg.use1.cache.amazonaws.com', :inet)         
{:error, :nxdomain}

iex(tango@2e76e9718be1)71> :inet_res.gethostbyname('small-redis-cluster.kk5zzw.clustercfg.use1.cache.amazonaws.com')
{:ok,
 {:hostent,
  'small-redis-cluster.kk5zzw.clustercfg.use1.cache.amazonaws.com', [],
  :inet, 4,
  [
    {10, 99, 23, 178},
    {10, 99, 31, 149},
    {10, 99, 22, 232},
    {10, 99, 6, 27},
    {10, 99, 17, 14},
    {10, 99, 1, 221},
    {10, 99, 2, 67},
    {10, 99, 1, 59}
  ]}}
  
iex(tango@2e76e9718be1)72> :inet_res.gethostbyname('large-redis-cluster.kk5zzw.clustercfg.use1.cache.amazonaws.com')      
{:ok,
 {:hostent, 'large-redis-cluster.kk5zzw.clustercfg.use1.cache.amazonaws.com', [],
  :inet, 4,
  [
    {10, 99, 59, 135},
    {10, 99, 57, 16},
    {10, 99, 59, 160},
    {10, 99, 56, 194},
    {10, 99, 56, 232},
    {10, 99, 57, 166},
    {10, 99, 57, 110},
    {10, 99, 60, 8},
    {10, 99, 57, 71},
    {10, 99, 59, 89},
    {10, 99, 57, 36},
    {10, 99, 59, 202},
    {10, 99, 56, 205},
    {10, 99, 57, 149},
    {10, 99, 59, 14},
    {10, 99, 59, 230},
    {10, 99, 61, 124},
    {10, 99, 61, 224},
    {10, 99, 60, 182},
    {10, 99, 60, 109}
  ]}}

Race condition while reconnecting.

I've discovered race condition while testing reconnection logic. Here below is setup I'm using:

  1. Erlang (R16) is running on Macos, I start erlang shell and create new connection via eredis:start_link
    and do basic put/get test. I'm using default database, and no authentication
  2. Redis is started in docker container. (I'm using native docker for macos)
  3. I stop redis service in docker and see race condition results either in process crash or closed socket saved in eredis_client state.

When connection dies and socket receives tcp_closed message eredis_client:reconnect_loop is called in a separate process where it:

  1. Creates new socket
  2. Changes controlling process to original eredis_client PID
  3. Sends message to eredis_client PID to replace connection

There are the following issues with described approach:

  • Between steps 1) and 2) newly created socket(connection) dies and spawned process (where reconnect_loop is executed) receives tcp_closed message what causes eredis client process using dead socket
  • Between steps 2. and 3. socket(connection) dies and eredis_client receives tcp_closed prior to connection_ready message while socket is undefined what causes process termination on unhandled_message

problem on ubuntu 10.04

This happens when I try to use eredis on ubuntu. It compiles fine, without errors, connects fine (or so it seems), but is not able to execute commands.

5> {ok, C} = eredis:start_link("localhost", 6379, 10, []).
{ok,<0.43.0>}
6> {ok, <<"OK">>} = eredis:q(C, ["SET", "un:bla", "1234"]).

=ERROR REPORT==== 11-Aug-2011::13:01:44 ===
** Generic server <0.43.0> terminating
** Last message in was {tcp,#Port<0.728>,<<"+OK\r\n">>}
** When Server state == {state,"localhost",6379,<<>>,<<"10">>,#Port<0.728>,
{pstate,undefined,undefined},
{[{<0.40.0>,#Ref<0.0.0.55>}],[]}}
** Reason for termination ==
** {'module could not be loaded',[{binary,match,[<<"OK\r\n">>,<<"\r\n">>]},
{eredis_parser,get_newline_pos,1},
{eredis_parser,parse_simple,1},
{eredis_parser,parse,2},
{eredis_client,handle_response,2},
{eredis_client,handle_info,2},
{gen_server,handle_msg,5},
{proc_lib,init_p_do_apply,3}]}
** exception exit: undef
in function binary:match/2
called as binary:match(<<"OK\r\n">>,<<"\r\n">>)
in call from eredis_parser:get_newline_pos/1
in call from eredis_parser:parse_simple/1
in call from eredis_parser:parse/2
in call from eredis_client:handle_response/2
in call from eredis_client:handle_info/2
in call from gen_server:handle_msg/5
in call from proc_lib:init_p_do_apply/3
7>

eunit tests fail

I may do something wrong or maybe there are some prerequisites. I am running it on Erlang 18.3.

$ rebar -v eunit
==> eredis (eunit)
INFO:  sh info:
    cwd: ".../eredis"
    cmd: cp -R src/basho_bench_driver_eredis.erl src/basho_bench_driver_erldis.erl src/eredis_client.erl src/eredis_sub_client.erl src/eredis_sub.erl src/eredis.erl src/eredis_parser.erl test/eredis_sub_tests.erl test/eredis_tests.erl test/eredis_parser_tests.erl ".eunit"
Compiled src/eredis_parser.erl
Compiled src/eredis.erl
Compiled src/basho_bench_driver_erldis.erl
Compiled src/basho_bench_driver_eredis.erl
Compiled src/eredis_sub.erl
Compiled src/eredis_client.erl
Compiled src/eredis_sub_client.erl
Compiled test/eredis_sub_tests.erl
Compiled test/eredis_tests.erl
Compiled test/eredis_parser_tests.erl
INFO:  Cover compiling .../eredis
======================== EUnit ========================
module 'basho_bench_driver_erldis'
module 'eredis_sub_client'
module 'eredis_sub'
  module 'eredis_sub_tests'
    eredis_sub_tests: pubsub_test...*skipped*
undefined
*unexpected termination of test process*
::{connection_error,{connection_error,econnrefused}}

=======================================================
  Failed: 0.  Skipped: 0.  Passed: 0.
One or more tests were cancelled.
Cover analysis: .../eredis/.eunit/index.html
ERROR: One or more eunit tests failed.
ERROR: eunit failed while processing .../eredis: rebar_abort

Recovery after network failure

Hello,

From my reading of the eredis documentation, it seems to me that if the connection to redis fails, eredis itself should not fail and, when the connection recovers, requests should work again. Is that correct or have I misread what is possible (highly likely as I am very new to erlang)?

So, my example is that if I compile eredis and point it at a redis server, I can successfully do commands, like this:

rebar compile
erl -pa /ebin
{ok,C} = eredis:start_link("my-redis-location", 6379, 0, "", 5000).
{ok,<<"PONG">>} = eredis:q(C, ["PING"]).

And, when I physically disconnect my network cable, I get an error (which I would expect):

eredis:q(C, ["PING"]).
** exception exit: {timeout,{gen_server,call,
                                        [<0.35.0>,
                                         {request,[[<<"*">>,"1",<<"\r\n">>],
                                                   [[<<"$">>,"4",<<"\r\n">>,<<"PING">>,<<"\r\n">>]]]},
                                         5000]}}
     in function  gen_server:call/3 (gen_server.erl, line 212)

When I reconnect my network cable (and wait for the connection to start working again), I was hoping that requests would start working again but, instead, I only get errors:

eredis:q(C, ["PING"]).
** exception exit: {noproc,{gen_server,call,
                                       [<0.35.0>,
                                        {request,[[<<"*">>,"1",<<"\r\n">>],
                                                  [[<<"$">>,"4",<<"\r\n">>,<<"PING">>,<<"\r\n">>]]]},
                                        5000]}}
     in function  gen_server:call/3 (gen_server.erl, line 212)

The difference between the second error and the first is that the second mentions noproc instead of timeout. I assume that the eredis process has died.

Is it possible to make it such that the process doesn't die and it starts working again once the connection is working again?

Of course, I am, probably, misunderstanding how processes should work in the erlang world!

authenticate fail , wrong password.

i change the source and authenticate success

eredis_client.erl #351

authenticate(Socket, Password) ->
    do_sync_command(Socket, eredis:create_multibulk(["AUTH", Password])).

PubSub client can get overloaded

Hi guys,

I've picked up an issue with the current pub/sub client. Basically from what I can tell, the process handling the TCP connection waits for the controlling process (the one that ack's receipt of a message) to respond before it process the next message. I guess that's to provide a level of flow control.

In my case though, we received a large batch of messages in one go (around 7,000 or so I think) this filled up the message queue for the process handling the TCP connection (it's a gen_server) which then continues to grow and grow until I kill it.

I'm not sure how to fix this apart from adding a queue internally to the gen_server so that it serves messages from that rather than keeping messages in the TCP queue...

In short, I'm receiving a burst of messages which fills up the message queue on the gen_server which never manages to recover...

Any ideas?

How do you guarantee FIFO order from eredis to a haproxy/twemproxy which connects to multiple redis server?

Hi,

I am reading the code in https://github.com/wooga/eredis/blob/master/src/eredis_client.erl#L257

There is an assumption that redis server will return response based on FIFO order of requests.

My set up is eredis is connected to a haproxy/twemproxy which connects to multiple redis servers, so multiple redis server can return data in different order.

Does it break your assumption? Will it still work?

Cheers,
Howard

Maintenance question

I wanted to ask if you guys are actively maintaining this. I have just noticed that the latest tag (v1.0.8) is nearly two years old and the new changes have not been tagged yet (or documented for that matter). I also noticed that some people are eager (as on #90) to migrate to rebar3 and publishing on hex. So the question is if you have any plans/enough time to do this, is it desired at all, and if I should send a pull request?

eredis only offers q() api

Hi, I think it's a bit spartan to only offer a q(...)-style api from eredis.
One of the examples is:

eredis:q(["SET", "foo", "bar"]).

It would be nicer to offer this style I think:

eredis:set("foo", "bar").

Is there a particular reason why this was not done (besides save some work on the api-typing)? If there is interest in it, I can add this feature.

Getting wrong value for HINCRBYFLOAT

I'm using Erlang R15B03 (erts-5.9.3.1) and eredis-1.1.0 for a project. when I execute HINCRBYFLOAT command as below I always get <<543>> as value.

# erl -pa ebin/
Erlang R15B03 (erts-5.9.3.1) [source] [64-bit] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.9.3.1  (abort with ^G)
1> {ok, C} = eredis:start_link().
{ok,<0.34.0>}
2>  eredis:q(C, ["HINCRBYFLOAT", "foo", "bar", 0]).
{ok,<<"543">>}

Error after running test

OS: Fedora 27

$ ./rebar eunit

=ERROR REPORT==== 16-Feb-2018::22:12:18 ===
** Generic server <0.118.0> terminating 
** Last message in was {tcp,#Port<0.34473>,
                            <<"*3\r\n$7\r\nmessage\r\n$3\r\nfoo\r\n$2\r\n12\r\n">>}
** When Server state == {state,"127.0.0.1",6379,<<>>,100,#Port<0.34473>,
                               {pstate,undefined,undefined},
                               [<<"foo">>],
                               {#Ref<0.279932551.3633315841.178106>,<0.91.0>},
                               {[{message,<<"foo">>,<<"11">>,<0.118.0>},
                                 {message,<<"foo">>,<<"10">>,<0.118.0>},
                                 {message,<<"foo">>,<<"9">>,<0.118.0>},
                                 {message,<<"foo">>,<<"8">>,<0.118.0>},
                                 {message,<<"foo">>,<<"7">>,<0.118.0>},
                                 {message,<<"foo">>,<<"6">>,<0.118.0>},
                                 {message,<<"foo">>,<<"5">>,<0.118.0>},
                                 {message,<<"foo">>,<<"4">>,<0.118.0>},
                                 {message,<<"foo">>,<<"3">>,<0.118.0>}],
                                [{message,<<"foo">>,<<"2">>,<0.118.0>}]},
                               10,exit,need_ack}
** Reason for termination == 
** max_queue_size
eredis_tests: connect_test...*skipped*
undefined
*unexpected termination of test process*
::{connection_error,econnrefused}

=======================================================
  Failed: 0.  Skipped: 0.  Passed: 43.
One or more tests were cancelled.
Cover analysis: /home/maqbool/OpenSource/eredis/.eunit/index.html
ERROR: One or more eunit tests failed.
ERROR: eunit failed while processing /home/maqbool/OpenSource/eredis: rebar_abort

Closing a connection

Is there a way to close a redis connection? I looked around in the source code, and it looks like there's no way currently.

Spawned reconnect_loop process is not terminated when the client is terminated

If an eredis client fails its first connection-attempt it will spawn_link a re-connection process.
This re-connection process will run until it succeeds.
But, if the connection attempts continuously fails and the user stops the eredis client, the spawned process is not terminated.
You will have an unexpected process running that attempts to connect to a previously used Redis instance.

A probable reason for this can be that eredis_client is stopped with reason normal, and an exit signal with reason normal is ignored by the re-connection process. See http://erlang.org/doc/reference_manual/processes.html#receiving-exit-signals

Don't log error on start

As is, eredis logs something like
... [error] CRASH REPORT Process <0.x.0> with 0 neighbours exited with reason: {error, connection_error} in gen_server:init_it/6
when eredis_client:init fails due to a connection error.

{stop, {connection_error, Reason}} could be changed to {stop, {shutdown, {connection_error, Reason}}} and this would prevent the log message the supervisor creates, while also informing that connection didn't go as expected.

@knutin: would you be interested in such a change with a PR? This doesn't seem to break interface, because it's documented as {ok, _} | {error, _}, thus all initialization errors are handled the same.

subscribe reconnection

Hi,

Thanks for the lib. It's been a pleasure to use so far.

I am testing the pub/sub part of eredis, and from what I can see there' is no subscriber automatic reconnection, or am I missing something?

(STR: i connect to a redis instance, send some msg via redis-cli, receive them then, restart the redis-server, relaunch redis-cli, send more command -> nothing is received on the process listenening to subscribe.)

cheers,

Max

Query timeout shorter than specified

Using a timeout of 60s like this

eredis:q(pid(0,17393,766), ["SMEMBERS", "access_64628"], 60000).

I still get a timeout after 5s. It looks to me as if another gen_server:call is triggered internally with the default timeout.

** exception exit: {{timeout,{gen_server,call,
                                         [<0.17393.766>,
                                          {request,[[<<"*">>,"3",<<"\r\n">>],
                                                    [[<<"$">>,"4",<<"\r\n">>,<<"SADD">>,<<"\r\n">>],
                                                     [<<"$">>,"12",<<"\r\n">>,<<"access_64645">>,<<"\r\n">>],
                                                     [<<"$">>,"57",<<"\r\n">>,
                                                      <<"some data "...>>,
                                                      <<"\r\n">>]]]},
                                          5000]}},
                    {gen_server,call,
                                [<0.17393.766>,
                                 {request,[[<<"*">>,"2",<<"\r\n">>],
                                           [[<<"$">>,"8",<<"\r\n">>,<<"SMEMBERS">>,<<"\r\n">>],
                                            [<<"$">>,"12",<<"\r\n">>,<<"access_64628">>,<<"\r\n">>]]]},
                                 60000]}}

Any pointers where to start looking for a fix?

Cheers,
Martin

Unix socket support?

Using eredis, can I connect to local redis server through unix socket? e.g. /var/run/redis/redis.sock

I heard that communicating with redis by unix socket has better performance.

Thanks

ipv6 support cause issues in some cases

Hi, sometimes we have Docker configured with ipv6 resolving, but Redis server is only listening on ipv4.
So redis_client would resolve ipv6, try to connect and fail with a pretty non-descriptive eaddrnotavail. After that people would try to do telnet localhost 6379 and it would work. They would try to do gen_tcp:connect("localhost", 6379, []). and it would work. But eredis would not work.

eredis would basically do that behind the scene:

gen_tcp:connect({0,0,0,0,0,0,0,1}, 6379, []) -> {error,eaddrnotavail}

I doubt this code is even needed:

get_addr({local, Path}) ->
    {ok, {local, {local, Path}}};
get_addr(Hostname) ->
    case inet:parse_address(Hostname) of
        {ok, {_,_,_,_} = Addr} ->         {ok, {inet, Addr}};
        {ok, {_,_,_,_,_,_,_,_} = Addr} -> {ok, {inet6, Addr}};
        {error, einval} ->
            case inet:getaddr(Hostname, inet6) of
                 {error, _} ->
                     case inet:getaddr(Hostname, inet) of
                         {ok, Addr}-> {ok, {inet, Addr}};
                         {error, _} = Res -> Res
                     end;
                 {ok, Addr} -> {ok, {inet6, Addr}}
            end
    end.

It would be much cleaner solution to just use gen_tcp:connect/3 logic and pass State#state.host as a first argument, without resolving it first.
If people really need to use ipv6, they could pass inet6 inside of SocketOpts.

{error,{unexpected_data,{error,closed}}} - during Authentication

I am passing the Eredis password param in the start_link,
Previously my password param was empty, now we have added a authentication to our redis server.
When i try to pass the password param "xcvvcnbmn", i am seeing the below crash
Please do the needful
I am able to connect through redis-cli

 =SUPERVISOR REPORT==== 28-Aug-2018::16:31:16 ===
 Supervisor: {<0.268.0>,eredis_sup}
 Context:    start_error
 Reason:     {'EXIT',
                 {{badmatch,
                      {error,
                          {connection_error,
                              {authentication_error,
                                  {unexpected_data,{error,closed}}}}}},
                  [{eredis,start_link,6,
                       [{file,"src/eredis.erl"},{line,76}]},
                   {supervisor,do_start_child,2,
                       [{file,"supervisor.erl"},{line,365}]},
                   {supervisor,start_children,3,
                       [{file,"supervisor.erl"},{line,348}]},
                   {supervisor,init_children,2,
                       [{file,"supervisor.erl"},{line,314}]},
                   {gen_server,init_it,6,
                       [{file,"gen_server.erl"},{line,328}]},
                   {proc_lib,init_p_do_apply,3,
                       [{file,"proc_lib.erl"},{line,247}]}]}}
 Offender:   [{pid,undefined},
              {id,eredis},
              {mfargs,{eredis,start_link,[]}},
              {restart_type,temporary},
              {shutdown,5000},
              {child_type,worker}]

Support for Slave in eredis_cluster

When we use cluster in eredis_cluster, the library does not uses the replicas of Redis. Is there a plan to add replica support in the library ?

Where is my RAM?

Hello!

When I use this library my RAM decrease from 600MB to 150MB (yes, i get site homepage many times via wrk utility). But after that free RAM not restores -:(( I have no such problems with Erlang Mnesia for example. Maybe there is exists function eredis:CLOSE_link() in this lib???

Unable to compile project against rebar 2.5.1

I am coming from Elixir, so the stack trace from Erlang is a little foreign to me; sorry if this is a easy fix.

I am able to compile against the provided version of rebar

root@33175fa19b42:/src/eredis# ./rebar --version
rebar version: 2 date: 20110304_023706 vcs: git d725e5f
root@33175fa19b42:/src/eredis# ./rebar compile
==> eredis (compile)

But against the latest version, I am not.

root@33175fa19b42:/src/eredis# rebar --version
rebar 2.5.1 17 20150421_125738 git 2.5.1-180-g858fb4f
root@33175fa19b42:/src/eredis# rebar compile
==> eredis (compile)
ERROR: compile failed while processing /src/eredis: {'EXIT',
    {function_clause,
        [{filename,join,[[]],[{file,"filename.erl"},{line,392}]},
         {rebar_erlc_compiler,expand_include_lib_path,1,
             [{file,"src/rebar_erlc_compiler.erl"},{line,647}]},
         {rebar_erlc_compiler,process_attr,3,
             [{file,"src/rebar_erlc_compiler.erl"},{line,597}]},
         {rebar_erlc_compiler,parse_attrs,2,
             [{file,"src/rebar_erlc_compiler.erl"},{line,565}]},
         {rebar_erlc_compiler,modify_erlcinfo,4,
             [{file,"src/rebar_erlc_compiler.erl"},{line,400}]},
         {rebar_erlc_compiler,'-update_erlcinfo_fun/2-fun-0-',4,
             [{file,"src/rebar_erlc_compiler.erl"},{line,359}]},
         {lists,foldl,3,[{file,"lists.erl"},{line,1261}]},
         {rebar_erlc_compiler,init_erlcinfo,2,
             [{file,"src/rebar_erlc_compiler.erl"},{line,349}]}]}}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.