Commit Graph

126 Commits

Author SHA1 Message Date
Daniele Alessandri 89840412d5 [tests] Fix preassigned slots in cluster tests. 2016-06-18 21:53:49 +02:00
Daniele Alessandri 67c0fb8eb1 Separate id and alias in predis cluster (client-side sharding).
This change does not affect distribution but simply separates the two
concepts of connection ID (ip:port pair) and alias (value set via the
"alias" connection parameter), the method getConnectionByAlias() has
been added to reflect this change.

The method getConnectionBySlot() has also been added.
2016-06-18 17:12:20 +02:00
Daniele Alessandri 184d583895 Implement the concept of role for connections.
When using replication backends, now the role of a connection is not
defined by its alias but by the new connection parameter "role" that
can be set to "master", "slave" and (for redis-sentinel) "sentinel".

This also led to a redesign of how connections can be retrieved from
replication backends: the method getConnectionById() now retrieves a
connection only by its ID (ip:port pair), to get a connection by its
alias there is the new method getConnectionByAlias(). This method is
not supported by the redis-sentinel backend due to its dynamic nature
(connections are retrieved and initialized at runtime from sentinels)
but it is still possible to get a single connection from the pool by
using its ID. It is also possible to retrive a connection by its role
using the method getConnectionByRole().

NOTE: the "role" parameter is an hint for the internals of aggregate
connection backends so it is still possible that the actual role of
a connection changes during the execution of a script (e.g. a slave
gets promoted to the role of master): in this case the parameters of
that connection will not be changed as they are immutable, but the
method getConnectionByRole() will return the appropriate connection.

Predis\Client::getClientBy() has been updated with the addition of
"role" and "alias" as supported selectors.
2016-06-18 15:32:53 +02:00
Daniele Alessandri 69e40dab2b Extract slot map logic to separate class.
In addition to that, the methods askSlotsMap() and buildSlotsMap() of
the redis-cluster connection backend have been renamed respectively
to askSlotMap() and buildSlotMap().
2016-06-17 22:18:38 +02:00
Daniele Alessandri 9d8adee1b1 Rename to getConnectionByCommand($command). 2016-06-16 10:39:44 +02:00
Pascal Hofmann ce8b3fb683 Don't send AUTH and/or SELECT command after connecting to sentinels 2016-06-14 16:38:59 +02:00
Daniele Alessandri 2640062803 Use master for connect() when sentinel reports no slaves.
Fixes #342.
2016-06-13 16:32:45 +02:00
Daniele Alessandri eb1e686ff9 [tests] Change indentation of mock method expectations. 2016-06-10 11:08:26 +02:00
Daniele Alessandri 8b466d05df Get string of basic connection parameters from parameters class. 2016-06-08 18:36:35 +02:00
Daniele Alessandri c10479e238 Remove executeCommandOnNodes() from predis cluster backend.
It is still possible to achieve the same simply by iterating over the
connection or, even better, over the client instance.
2016-06-08 12:01:54 +02:00
Daniele Alessandri 1028599ac5 Split Predis\Connection\Aggregate namespace.
Now we have two namespaces for cluster and replication backends:

  - Predis\Connection\Cluster
  - Predis\Connection\Replication
2016-06-07 15:23:32 +02:00
Daniele Alessandri 62b421f20e Switch from server profiles to commands factory.
This change reduces some unnecessary complexity in the library, Redis
commands do not change much after all. Developers can still implement
their own commands factory, inject new commands or override existing
ones. The "profile" client options has been renamed to "commands" and
it accepts instances of Predis\Command\FactoryInterface.

The test suite checks at runtime the version of the running instance
of Redis for integration tests to adapt itself automatically.
2016-06-04 15:36:21 +02:00
Daniele Alessandri 1065edc8d1 Apply last round of CS fixes. 2016-06-01 22:31:06 +02:00
Daniele Alessandri 843ad23ea7 [tests] Move utility method into base test class. 2016-06-01 22:27:36 +02:00
Daniele Alessandri 922e56b480 Iterate only over connections mapped in slots map.
Iterating over Predis\Connection\Aggregate\RedisCluster returns all
the connections currently mapped in the slots map instead of just the
ones initialized in the pool.

When the slots map is retrieved from Redis (which by default is done
automatically) this allows to iterate over all of the current master
nodes of the cluster. When the underlying use of "CLUSTER SLOTS" is
disabled the iteration returns only connections with a slots range
associated in their parameters or initialized by `-MOVED` responses
to make the behaviour of the iteration consistent between the two
modes of operation.
2016-05-28 17:58:38 +02:00
Daniele Alessandri 5a0dfc3602 Fix parameters overriding for sentinels.
Different fix than PR #339 but thanks @phofmann-trust for spotting.
2016-05-28 15:53:23 +02:00
Daniele Alessandri 2d01a27e17 Fix fetching slots map from unreachable nodes.
When various nodes in the configuration are unreachable while trying
to send a command, we should attempt to contact a reachable node to
fetch an updated slots map up to $retryLimit times or until there are
no more servers in the pool before giving up.

It is possible that the slots map fetched from Redis contains stale
data and points to a dead server, this happens when the nodes still
have to agree that a master server is down before promoting a slave
to the role of master. In this case no further attempts to execute
the command are performed and an exception is thrown.

This still needs some more testing and will delay v1.0.4 a few days
past its scheduled release.
2016-05-27 14:30:07 +02:00
Daniele Alessandri 0477499418 Fix ROLE expectation for read commands with no slaves.
This commit fixes #337.
2016-05-25 15:56:33 +02:00
Daniele Alessandri 5b3a5bbef9 Run php-cs-fixer. 2016-05-21 15:46:58 +02:00
Daniele Alessandri 349a70a08a Merge branch 'v1.1-sentinel'
This merge resolves #131.
2016-05-21 15:30:22 +02:00
Daniele Alessandri c1de65c4ee Swap params order in redis-sentinel constructor. 2016-05-21 15:23:24 +02:00
Daniele Alessandri 05209e6e7d Switch to next slave on -LOADING error response.
This prevents an early failure of the command execution on the client
when one slave gets back online but is still loading the dataset from
disk (when this happens, Redis returns the -LOADING error response).

This commit fixes #280.
2016-05-20 21:59:40 +02:00
Daniele Alessandri d0cc7a5947 Try again on connection failure to node in cluster.
When the connection to a node in the cluster fails in the attempt to
execute a command, Predis now removes the failed connection from the
cluster pool and contacts a random node to ask for a fresh slots map
and tries to execute the command once again.

When the cluster is configured to have each master replicated to one
or more slaves, one the slaves is automatically promoted to the role
of master by redis-cluster with this change being reflected in the
output of CLUSTER SLOTS, so the next execution should run just fine.

Our current approach is relatively naive as CLUSTER SLOTS is executed
against a random master node, meaning that the client must open a new
connection and execute one more roundtrip only to fetch the new slots
map. For now it is enough, it is still better than having the client
fail when you actually have somes slaves in your redis-cluster setup,
but one improvement could consist in caching the list of slaves for
each master returned in the response of CLUSTER SLOTS so that when a
connection fails the client can try to guess which connection should
use for the next attempt.

This commit closes #173, closes #215, and closes #314.
2016-05-20 13:17:06 +02:00
Daniele Alessandri d6d307696a [tests] Add tests for redis-sentinel connection. 2016-05-19 22:15:55 +02:00
Daniele Alessandri 354e5e26da Improve handling of slots mapping via parameters.
When using redis-cluster it is now also possible to pass one slot or
non-contiguous ranges of slots via connection parameters in order to
improve the ability to pre-configure the slots map on the client.

Here is an example:

  $parameters = [
    'tcp://10.0.0.1:6379?slots=0-5460,5500-5600,11000',
    'tcp://10.0.0.2:6379?slots=5461-5499,5600-10921',
    'tcp://10.0.0.3:6379?slots=10922-10999,11001-16383',
  ];

This commit fixes #312 (props to @kenotr0n for the original PR).
2016-05-18 15:04:37 +02:00
Daniele Alessandri d55826f35c [tests] Fix tests from previous commit.
They did work, but I am not exactly sure why. Also added a missing
test for Predis\Connection\CompositeStreamConnection.
2016-05-17 20:06:30 +02:00
Daniele Alessandri 973e8592e3 Throw when command sent in connect() returns error.
Common failures are the use of SELECT with a database index outside
the bound of the configured number of databases in redis.conf or the
use of a wrong password for authentication with AUTH.

This resolves #322.
2016-05-17 19:15:21 +02:00
Daniele Alessandri 9f6759ca1c Implement discovery in basic replication.
Now the client can discover the whole replication configuration by
asking to one of the servers (master has the precedence) using the
INFO REPLICATION command. This is obviously a best-effort fallback
and there is no strong guarantee about reliability and efficiency.

By enabling auto-discovery, the client automates this process when
the execution of a command fails because one of the target servers
is unreachable. The replication connection requires an instance of
connection factory associated to it in order to be able to create
new connections on the fly.

It is possible to enable the auto-discovery procedure easily via
client options:

  $client = new Predis\Client($servers, [
    'replication' => true,
    'autodiscovery' => true,
  ]);
2016-05-17 14:24:52 +02:00
Daniele Alessandri 89a1e236ce Use custom ID when adding connection with no alias. 2016-05-16 17:28:13 +02:00
Daniele Alessandri a933a13087 [tests] Fix wrong method in mock. 2016-05-16 15:38:29 +02:00
Daniele Alessandri 629329ac76 Fix failing test.
This was exactly what I meant with the @todo annotation, too bad I
forgot to temporarily adjust the test accordingly.
2016-05-15 21:54:00 +02:00
Daniele Alessandri c9366212d0 Add methods to switch to master or random slave. 2016-05-15 21:27:43 +02:00
Daniele Alessandri 5b76b41fda Use master for connect() on empty slaves pool.
Internally the replication class uses this order to pick which server
it should connect to: current connection, one of the slaves, master.

If there is at least 1 slave, connect() will not fail even if master
is undefined. If there are no slaves, connect() will pick master. If
there are no connections registered for replication, connect() will
fail immediatly.
2016-05-15 20:53:09 +02:00
Daniele Alessandri b491dff126 Send read-only commands on next slave on failure.
If no other slave is available try again on master as last resort
before giving up and throwing an exception.
2016-05-15 20:53:06 +02:00
Daniele Alessandri 774b4014d9 Use master for read requests on empty slaves pool.
This is the last resort in case all of the slaves are unreachable.
2016-05-15 17:20:58 +02:00
Daniele Alessandri 2fc0e56a09 Preserve remainder of path in URI after database (redis scheme). 2015-07-30 11:11:30 +02:00
Daniele Alessandri a7ee80702c Implement full support for IPv6.
Using IPv6 with Predis was basically impossible due to various inconsistencies
and bugs through the library, now it is supported by all the connection classes.

Following the standard for IPv6 literal addresses in URI strings, the IP literal
must be enclosed within square brackets when passing the parameters as a string:

  $parameters = 'tcp://[2001:db8:0:f101::1]:6379';

See https://tools.ietf.org/html/rfc3986#section-3.2.2 for further details.

This commit also fixes #239 making redis-cluster usable with nodes using IPv6.
2015-07-29 23:01:55 +02:00
Daniele Alessandri a26390915d Strip brackets from host when parsing embedded IPv6 address.
I don't know why PHP's parse_url() does not do that, it does not make
sense when the IP is by itself so maybe it is a bug?
2015-07-29 16:44:11 +02:00
Daniele Alessandri 3dfe62a5b5 Change format required for URI strings when using "unix" scheme.
Instead of using "unix://" you should just use "unix:":

  $old = 'unix:///path/to/redis.sock';
  $new = 'unix:/path/to/redis.sock';

The old format should be considered obsolete and will not be supported
starting from the next major release of Predis.
Meh
2015-07-28 11:23:33 +02:00
Daniele Alessandri ebb72377bb Implement TLS/SSL-encrypted connections.
This is handy for accessing remote Redis instances over a secure SSL connection
which is currently a popular option or even requirement with many cloud hosting
environments.

In order to configure the client to use an SSL-encrypted connection the scheme
in the connection parameters must be either "tsl" or "rediss" and a set of SSL
options (see http://php.net/manual/en/context.ssl.php) must be provided via the
"ssl" parameter as a named array.

The following example (which does not necessarily represent an example of good
practices!) illustrates how to set the "ssl" parameter using a named array and
the equivalent URI string:

  // Parameters as named array
  $parameters = [
    'scheme' => 'tls',
    'host'   => '127.0.0.1',
    'ssl'    => [
        'cafile'            => '/home/adaniele/redis.pem',
        'verify_peer_name'  => false,
    ],
  ];

  // Parameters as URI string
  $parameters = 'tls://127.0.0.1?ssl[cafile]=redis.pem&ssl[verify_peer_name]=1';

Support for SSL is currently limited to the Predis\Connection\StreamConnection
backend but we intend to investigate if it is possible to extend this feature
to Predis\Connection\PhpiredisStreamConnection in the future.

Be aware that using encrypted connections may lead to a performance degradation
especially in the connect() operation due to the overhead of the TLS handshake.
Unfortunately there is no real way to reuse SSL sessions from userland, aside
from enabling persistent connections, but this will work only on PHP >= 7.0.0
because previous versions of PHP do not provide enough info about a stream from
get_stream_meta_data().

NOTE: Redis does not have built-in support for SSL-encrypted connections, but if
you want to expose it to public networks you may want to rely on "stunnel".
2015-07-27 19:16:20 +02:00
Daniele Alessandri abf2ce0bd1 [tests] Exlude persistent connections tests under PHP 5.3.
The get_resource_type() function does not differentiate between normal streams
and persistent streams, so we cannot really test this case.
2015-07-25 21:52:55 +02:00
Daniele Alessandri c7cae66a97 [tests] Improve code-reuse in tests for the Predis\Connection namespace. 2015-07-25 21:32:36 +02:00
Daniele Alessandri 1c8eb7ff6b [tests] Share common test among connection classes. 2015-07-25 19:13:01 +02:00
Daniele Alessandri cb09a7a2b5 [tests] Use @requires annotation. 2015-07-25 18:37:11 +02:00
Daniele Alessandri 2b0c8fbb26 Remove "timeout" as a default parameter in Connection\Parameters.
Falling back to a default timeout values should be done by the connection class
as it is an implementation detail that may vary depending on the backend.
2015-07-25 18:37:11 +02:00
Daniele Alessandri 55aab86800 Add support for default connection parameters in Connection\Factory.
These parameters augment the set of user-supplied parameters when creating a new
connection, but they do not override specific parameters when already defined.

An example of self-contained configuration using client options:

  $client = new Predis\Client('tcp://127.0.0.1', [
    'parameters' => [
      'timeout' => 10,
    ],
    'connections' => function ($options) {
      $factory = $options->getDefault('connections');
      $factory->setDefaultParameters($options->parameters);

      return $factory;
    },
  ]);

This change will be useful for both redis-cluster and redis-sentinel as it makes
it easy to apply shared parameters such as a common password for authentication
when the server returns one ore more new nodes from response (think of -MOVED).
2015-07-25 18:37:11 +02:00
Daniele Alessandri 8277afc7a8 Use "persistent" with non-bool strings to open different persistent connections.
stream_socket_client() has the undocumented ability to open different persistent
streams by providing a path in the $address string. Previously we supported this
behaviour with a combination of "persistent" and "path" (see #139) but this can
be confusing, especially now that we support the redis:// scheme which uses the
path part of an URI string to specify a database number.

After this change, instead of using an URI string such as:

  $parameters = 'tcp://127.0.0.1/first?persistent=1&database=5';

You should use the following ones:

  $parameters = 'tcp://127.0.0.1?persistent=first&database=5';
  $parameters = 'redis://127.0.0.1/5?persistent=first';

Avoiding "path" makes even more sense when using array connection parameters:

  $parameters = [
    'host'       => '127.0.0.1',
    'database'   => 5,
    'persistent' => 'first',
  ]

This feature is not supported when using UNIX domain sockets because the path
trick of stream_socket_client() does not play well with the actual path of the
socket file. The client will throw an InvalidArgumentException exception to
notify the user.

NOTE: unfortunately we have to disable the tests for persistent connections when
running under HHVM due to a bug in their implementation of get_resource_type()
preventing us to recognize a persistent stream from userland code.
2015-07-25 18:34:51 +02:00
Daniele Alessandri 8dd9893a2f Run php-cs-fixer with new configuration. 2015-07-24 23:17:02 +02:00
Daniele Alessandri 6590c44a27 Run php-cs-fixer against codebase in src/ and tests/. 2015-07-24 18:04:42 +02:00
Daniele Alessandri 7c1d324f30 [tests] Apply some fixes and improvements and remove old stuff. 2015-07-24 17:26:06 +02:00