Like Pdb, but for Redis. I guess.
Big emphasis on prefixing keys and object serialization.
Also because I don't want a hard dependency on either predis or php-redis. They both have their problems (vague magical commands API, binary extension, etc). Or wouldn't it be wonderful if a 3rd option showed up /s. Also supports credis.
Add as a dependency:
composer require karmabunny/rdb
- php-redis (binary extension)
- predis (composer package)
- credis (composer package)
This library doesn't implement a redis client itself, only wraps existing clients. After much effort trying to normalise the responses from all these client, it might seem like a good idea to just write our own that isn't so inconsistent.
But consider that we only know how inconsistent these libraries are because we've spent so much time trying to make them all behave the same. For example, client 'A' might do 'B' well but 'C' badly. Then client 'D' does 'B' badly but 'C' really well.
So as I sit here and scoff at their feeble attempts, I am reminded of a few things:
- I've already introduced so many of my own bugs during this journey.
- Unit testing is a gift from heaven.
- Normalising these inconsistencies has improved our own consistency, something probably not as achievable when writing a new client from scratch.
- also this: https://xkcd.com/927
This wrapper doesn't try to polyfill any missing features. It targets Redis server v3.0, as that's the common support among all the adapters.
This library wouldn't ever try to hide features behind target versions, but perhaps it could help smooth out any differences. Lua scripting could polyfill a lot of things tbh.
For example, BRPOPLPUSH
is deprecated in v6.2 and might be removed in the distant future. In this case, the library would be able to dynamically replace (based on the server version) this with BLMOVE
.
There is a preference for the millisecond version of a command, particularly TTL parameters. This is clearly misleading and already wildly inconsistent. Ideally this changes so that a 'float' is converted to the millisecond version and integer remains unchanged. Thus the input is always 'seconds'.
Type errors are currently (hopefully) always a null
return. This can quite confusing at times, or helpful in others. Version 2 will likely permit both, defaulting to emitting exceptions.
host
- server name + portprefix
- key prefixadapter
- 'predis' (default), 'php-redis', 'credis'timeout
- connection timeout, in seconds (default: 5)lock_sleep
- tick size for locking, in milliseconds (default: 5)chunk_size
- max key size for mscan methods (default: 50)scan_size
- count hint for scan methods (default: 1000)scan_keys
- replace keys() with scan() (default: false)options
- adapter specific options
Notes:
- The port number is default 6379 unless specified in the
host
option. - The protocol can be adjusted in the
host
option too: prefixtcp://
orudp://
.
return [
'host' => 'localhost',
'prefix' => 'sitecode:',
// Defaults
'adapter' => 'predis',
'lock_sleep' => 5,
'chunk_size' => 50,
'scan_size' => 1000,
'scan_keys' => false,
'options' => [],
];
Basic usage with a TTL. Great for caching.
use karmabunny\rdb\Rdb;
$config = require 'config.php';
$rdb = Rdb::create($config);
// Store 'blah' for 100 ms
$rdb->set('key', 'blah', 100);
$rdb->get('key');
// => blah
usleep(150 * 1000);
$rdb->get('key');
// => NULL
Object extensions will serialize in the PHP format. These have builtin assertions so things are always the correct shape.
$model = new MyModel('etc');
$rdb->setObject('objects:key', $model);
$rdb->getObject('objects:key', MyModel::class);
// => MyModel( etc )
$rdb->getObject('objects:key', OtherModel::class);
// => NULL
Locking provides a mechanism to restrict atomic access to a resource.
// Wait for a lock for up to 10 seconds.
$lock = $rdb->lock('locks:key', 10 * 1000);
if ($lock === null) {
echo "Busy - too much contention\n";
}
else {
// Do atomic things.
$lock->release();
}
Leaky bucket is a rate-limiting algorithm. It's cute, easy to understand, and not too complex.
// A bucket with 60 drips per minute.
$bucket = $rdb->getBucket([
'key' => 'key',
// Defaults.
'capacity' => 60,
'drip_rate' => 1,
// Optional.
'prefix' => 'drips:',
'costs' => [
'GET' => 1,
'POST' => 10,
],
]);
// One drip.
$full = $bucket->drip();
if ($full) {
echo "We're full, please wait {$bucket->getWait()} ms\n";
}
else {
// Do things.
}
// Big drip.
$bucket->drip(20);
// Named drip (10 drips).
$bucket->drip('POST');
// Write out the status to the headers for easy debugging.
$bucket->writeHeaders();
Submit a PR if you like. But before you do, please do the following:
- Run
composer analyse
and fix any complaints there - Run
composer compat
and fix those too - Write some tests and run
composer tests
- Document the methods here
- get
- set
- keys
- scan
- mGet
- mSet
- del
- exists
- sMembers
- sAdd
- lLen
- lRange
- lTrim
- lSet
- lRem
- lIndex
- lPush
- lPop
- blPop
- rPush
- rPop
- brPop
- brPoplPush
- zAdd
- zIncrBy
- zRange
- zRem
- zCard
- zCount
- zScore
- zRank
- zRevRank
TODO: more
sets:
- sScan
- sRandMember
- sDiff (+ store)
- sInter (+ store)
- sUnion (+ store)
- mScan
- getObject
- setObject
- mGetObjects
- mScanObjects
- mSetObjects
- setJson
- getJson
- 'Leaky bucket' rate limiting
- Locking
- more tests
- more methods