mirror of
https://github.com/predis/predis.git
synced 2026-08-18 01:40:38 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be75254ab6 | |||
| 07105e0506 | |||
| 07dc6ba6d2 | |||
| 5142011581 | |||
| c4b9c34d86 | |||
| f49e13ee3a | |||
| 6e3e2c0e78 | |||
| c35c422eda | |||
| 1b5ed7d516 | |||
| 5df852f227 | |||
| 9418df1924 | |||
| 0e4b3829a7 | |||
| 65f6127fab | |||
| 7ff24b19ae | |||
| 2babfc91d7 |
@@ -165,3 +165,32 @@ jobs:
|
||||
|
||||
- name: Search for misspellings
|
||||
run: $(python -m site --user-base)/bin/codespell
|
||||
|
||||
changelog:
|
||||
|
||||
name: Changelog
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
!contains(github.event.head_commit.message, 'nochangelog') &&
|
||||
!contains(github.event.head_commit.message, 'no-changelog') &&
|
||||
!contains(github.event.head_commit.message, 'no changelog') &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'no-changelog')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for CHANGELOG entry
|
||||
env:
|
||||
TARGET: ${{ github.event.pull_request.base.ref }}
|
||||
run: |
|
||||
FILES_CHANGED=$(git diff --name-only origin/$TARGET...HEAD | grep -E 'CHANGELOG\.md' -c)
|
||||
if [ "$FILES_CHANGED" != "1" ]; then
|
||||
echo "CHANGELOG.md was not updated";
|
||||
exit 1;
|
||||
fi;
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
run: |
|
||||
# Mapping of original redis versions to client test containers
|
||||
declare -A redis_clients_version_mapping=(
|
||||
["8.0"]="8.0-M05-pre"
|
||||
["8.0"]="8.0-RC2-pre"
|
||||
["7.4"]="7.4.2"
|
||||
["7.2"]="7.2.7"
|
||||
["6.2"]="6.2.17"
|
||||
|
||||
+7
-1
@@ -1,6 +1,11 @@
|
||||
## Changelog
|
||||
|
||||
## v2.4.0-RC1 (2024-11-21)
|
||||
## v2.4.1 (2025-11-12)
|
||||
### Fixed
|
||||
- Fixed return type for `ZCOUNT` to be `int` (#1546)
|
||||
- Removed automatic `conn_uid` parameter assignment (#1551)
|
||||
|
||||
## v2.4.0 (2025-04-30)
|
||||
### Added
|
||||
- Added new hash-field expiration commands (#1520)
|
||||
- Added missing `FT._LIST` and `BITFIELD_RO` commands (#1521)
|
||||
@@ -14,6 +19,7 @@
|
||||
- Fixed PHP 8.4 compatibility with `stream_context_set_option()` (#1503)
|
||||
- Prevent named arguments runtime failure (#1509)
|
||||
- Mark `GEOSEARCH` as read-only to ensure execution on replica (#1481)
|
||||
- Fixed protocol loss during redis cluster `MOVED` / `ASK` (#1530)
|
||||
|
||||
### Maintenance
|
||||
- Added CI testing with Redis 8.0 (#1510)
|
||||
|
||||
@@ -14,7 +14,7 @@ More details about this project can be found on the [frequently asked questions]
|
||||
|
||||
## Main features ##
|
||||
|
||||
- Support for Redis from __3.0__ to __7.4__.
|
||||
- Support for Redis from __3.0__ to __8.0__.
|
||||
- Support for clustering using client-side sharding and pluggable keyspace distributors.
|
||||
- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
|
||||
- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel).
|
||||
@@ -138,6 +138,50 @@ it is still desired to have control of when the connection is opened or closed:
|
||||
achieved by invoking `$client->connect()` and `$client->disconnect()`. Please note that the effect
|
||||
of these methods on aggregate connections may differ depending on each specific implementation.
|
||||
|
||||
#### Persistent connections ####
|
||||
|
||||
To increase a performance of your application you may set up a client to use persistent TCP connection, this way
|
||||
client saves a time on socket creation and connection handshake. By default, connection is created on first-command
|
||||
execution and will be automatically closed by GC before the process is being killed.
|
||||
However, if your application is backed by PHP-FPM the processes are idle, and you may set up it to be persistent and
|
||||
reusable across multiple script execution within the same process.
|
||||
|
||||
To enable the persistent connection mode you should provide following configuration:
|
||||
|
||||
```php
|
||||
// Standalone
|
||||
$client = new Predis\Client(['persistent' => true]);
|
||||
|
||||
// Cluster
|
||||
$client = new Predis\Client(
|
||||
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
|
||||
);
|
||||
```
|
||||
|
||||
**Important**
|
||||
|
||||
If you operate on multiple clients within the same application, and they communicate with the same resource, by default
|
||||
they will share the same socket (that's the default behaviour of persistent sockets). So in this case you would need
|
||||
to additionally provide a `conn_uid` identifier for each client, this way each client will create its own socket so
|
||||
the connection context won't be shared across clients. This socket behaviour explained
|
||||
[here](https://www.php.net/manual/en/function.stream-socket-client.php#105393)
|
||||
|
||||
```php
|
||||
// Standalone
|
||||
$client1 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_1']);
|
||||
$client2 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_2']);
|
||||
|
||||
// Cluster
|
||||
$client1 = new Predis\Client(
|
||||
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_1']]
|
||||
);
|
||||
$client2 = new Predis\Client(
|
||||
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_2']]
|
||||
);
|
||||
```
|
||||
|
||||
### Client configuration ###
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ use Traversable;
|
||||
*/
|
||||
class Client implements ClientInterface, IteratorAggregate
|
||||
{
|
||||
public const VERSION = '2.4.0-RC1';
|
||||
public const VERSION = '2.4.2-dev';
|
||||
|
||||
/** @var OptionsInterface */
|
||||
private $options;
|
||||
|
||||
@@ -300,7 +300,7 @@ use Predis\Response\Status;
|
||||
* @method array|null xread(int $count = null, int $block = null, array $streams = null, string ...$id)
|
||||
* @method int zadd(string $key, array $membersAndScoresDictionary)
|
||||
* @method int zcard(string $key)
|
||||
* @method string zcount(string $key, int|string $min, int|string $max)
|
||||
* @method int zcount(string $key, int|string $min, int|string $max)
|
||||
* @method array zdiff(array $keys, bool $withScores = false)
|
||||
* @method int zdiffstore(string $destination, array $keys)
|
||||
* @method string zincrby(string $key, int $increment, string $member)
|
||||
|
||||
@@ -254,7 +254,7 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable
|
||||
}
|
||||
|
||||
if (!$connection = $this->getRandomConnection()) {
|
||||
throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`');
|
||||
throw new ClientException('No connections left in the pool for `CLUSTER SLOTS` (' . $exception->getMessage() . ')');
|
||||
}
|
||||
|
||||
usleep($retryAfter * 1000);
|
||||
@@ -337,10 +337,19 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable
|
||||
{
|
||||
$separator = strrpos($connectionID, ':');
|
||||
|
||||
return $this->connections->create([
|
||||
$parameters = [
|
||||
'host' => substr($connectionID, 0, $separator),
|
||||
'port' => substr($connectionID, $separator + 1),
|
||||
]);
|
||||
];
|
||||
|
||||
$existConnection = current($this->pool);
|
||||
if ($existConnection instanceof NodeConnectionInterface) {
|
||||
$existParameters = $existConnection->getParameters()->toArray();
|
||||
unset($existParameters['alias'], $existParameters['slots']);
|
||||
$parameters = array_merge($existParameters, $parameters);
|
||||
}
|
||||
|
||||
return $this->connections->create($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,6 @@ class StreamConnection extends AbstractConnection
|
||||
public function __construct(ParametersInterface $parameters)
|
||||
{
|
||||
parent::__construct($parameters);
|
||||
$this->parameters->conn_uid = spl_object_hash($this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1285,17 +1285,35 @@ class ClientTest extends PredisTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 5.0.0
|
||||
*/
|
||||
public function testClientsCreateDifferentPersistentConnections(): void
|
||||
{
|
||||
$client1 = new Client($this->getParameters(['database' => 14, 'persistent' => true]));
|
||||
$client2 = new Client($this->getParameters(['database' => 15, 'persistent' => true]));
|
||||
$client1 = new Client($this->getParameters(['database' => 14, 'persistent' => true, 'conn_uid' => 1]));
|
||||
$client2 = new Client($this->getParameters(['database' => 15, 'persistent' => true, 'conn_uid' => 2]));
|
||||
|
||||
$client1->set('foo', 'bar');
|
||||
$client2->set('foo', 'baz');
|
||||
|
||||
$this->assertSame('bar', $client1->get('foo'));
|
||||
$this->assertSame('baz', $client2->get('foo'));
|
||||
$this->assertNotSame($client1->client('ID'), $client2->client('ID'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 5.0.0
|
||||
*/
|
||||
public function testClientsCreateSamePersistentConnections(): void
|
||||
{
|
||||
$client1 = new Client($this->getParameters(['persistent' => true]));
|
||||
$client2 = new Client($this->getParameters(['persistent' => true]));
|
||||
|
||||
$client1->set('foo', 'bar');
|
||||
$client2->set('foo', 'baz');
|
||||
|
||||
$this->assertSame('baz', $client2->get('foo'));
|
||||
$this->assertSame($client1->client('ID'), $client2->client('ID'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1307,11 +1325,11 @@ class ClientTest extends PredisTestCase
|
||||
{
|
||||
$client1 = new Client(
|
||||
$this->getDefaultParametersArray(),
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 1]]
|
||||
);
|
||||
$client2 = new Client(
|
||||
$this->getDefaultParametersArray(),
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
|
||||
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 2]]
|
||||
);
|
||||
|
||||
$client1->set('{shard1}foo', 'bar');
|
||||
|
||||
@@ -1008,6 +1008,14 @@ class KeyPrefixProcessorTest extends PredisTestCase
|
||||
['key', 'MAXLEN', 100],
|
||||
['prefix:key', 'MAXLEN', 100],
|
||||
],
|
||||
['ZPOPMIN',
|
||||
['key'],
|
||||
['prefix:key'],
|
||||
],
|
||||
['ZPOPMAX',
|
||||
['key'],
|
||||
['prefix:key'],
|
||||
],
|
||||
/* ---------------- Redis 6.2 ---------------- */
|
||||
['GETDEL',
|
||||
['key'],
|
||||
|
||||
@@ -69,7 +69,7 @@ class TSINFO_Test extends PredisCommandTestCase
|
||||
public function testReturnsInformationAboutGivenTimeSeries(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
$expectedResponse = ['totalSamples', 0, 'memoryUsage', 4239, 'firstTimestamp', 0, 'lastTimestamp', 0,
|
||||
$expectedResponse = ['totalSamples', 0, 'memoryUsage', 5000, 'firstTimestamp', 0, 'lastTimestamp', 0,
|
||||
'retentionTime', 60000, 'chunkCount', 1, 'chunkSize', 4096, 'chunkType', 'compressed', 'duplicatePolicy',
|
||||
'max', 'labels', [['sensor_id', '2'], ['area_id', '32']], 'sourceKey', null, 'rules', [],
|
||||
'ignoreMaxTimeDiff', 0, 'ignoreMaxValDiff', 0];
|
||||
@@ -84,7 +84,7 @@ class TSINFO_Test extends PredisCommandTestCase
|
||||
$redis->tscreate('temperature:2:32', $arguments)
|
||||
);
|
||||
|
||||
$this->assertEquals($expectedResponse, $redis->tsinfo('temperature:2:32'));
|
||||
$this->assertEqualsWithDelta($expectedResponse, $redis->tsinfo('temperature:2:32'), 1000);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
|
||||
@@ -319,12 +319,14 @@ class RedisClusterTest extends PredisTestCase
|
||||
->withConsecutive(
|
||||
[
|
||||
[
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6383',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6384',
|
||||
],
|
||||
@@ -644,6 +646,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '9381',
|
||||
])
|
||||
@@ -710,6 +713,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '9381',
|
||||
])
|
||||
@@ -1020,6 +1024,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6381',
|
||||
])
|
||||
@@ -1108,6 +1113,58 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6381',
|
||||
])
|
||||
->willReturn($connection3);
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
$cluster->useClusterSlots(false);
|
||||
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertCount(3, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testNotTCPMovedResponseWithConnectionNotInPool(): void
|
||||
{
|
||||
$movedResponse = new Response\Error('MOVED 1970 127.0.0.1:6381');
|
||||
|
||||
$command = $this->getCommandFactory()->create('get', ['node:1001']);
|
||||
|
||||
$connection1 = $this->getMockConnection('tls://127.0.0.1:6379');
|
||||
$connection1
|
||||
->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($command)
|
||||
->willReturn($movedResponse);
|
||||
|
||||
$connection2 = $this->getMockConnection('tls://127.0.0.1:6380');
|
||||
$connection2
|
||||
->expects($this->never())
|
||||
->method('executeCommand');
|
||||
|
||||
$connection3 = $this->getMockConnection('tls://127.0.0.1:6381');
|
||||
$connection3
|
||||
->expects($this->exactly(2))
|
||||
->method('executeCommand')
|
||||
->with($command)
|
||||
->willReturnOnConsecutiveCalls('foobar', 'foobar');
|
||||
|
||||
/** @var Connection\FactoryInterface|MockObject */
|
||||
$factory = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
|
||||
$factory
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tls',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6381',
|
||||
])
|
||||
@@ -1153,6 +1210,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '2001:db8:0:f101::2',
|
||||
'port' => '6379',
|
||||
])
|
||||
@@ -1250,6 +1308,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6380',
|
||||
])
|
||||
|
||||
@@ -403,6 +403,17 @@ class ParametersTest extends PredisTestCase
|
||||
$this->assertSame($expected, Parameters::parse($uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testSetParameters(): void
|
||||
{
|
||||
$parameters = new Parameters();
|
||||
$parameters->property = 'value';
|
||||
|
||||
$this->assertEquals('value', $parameters->property);
|
||||
}
|
||||
|
||||
// ******************************************************************** //
|
||||
// ---- HELPER METHODS ------------------------------------------------ //
|
||||
// ******************************************************************** //
|
||||
|
||||
Reference in New Issue
Block a user