perf: 增加原生缓存
This commit is contained in:
167
vendor/illuminate/redis/Limiters/ConcurrencyLimiter.php
vendored
Normal file
167
vendor/illuminate/redis/Limiters/ConcurrencyLimiter.php
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Redis\Limiters;
|
||||
|
||||
use Illuminate\Contracts\Redis\LimiterTimeoutException;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class ConcurrencyLimiter
|
||||
{
|
||||
/**
|
||||
* The Redis factory implementation.
|
||||
*
|
||||
* @var \Illuminate\Redis\Connections\Connection
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
/**
|
||||
* The name of the limiter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The allowed number of concurrent tasks.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxLocks;
|
||||
|
||||
/**
|
||||
* The number of seconds a slot should be maintained.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $releaseAfter;
|
||||
|
||||
/**
|
||||
* Create a new concurrency limiter instance.
|
||||
*
|
||||
* @param \Illuminate\Redis\Connections\Connection $redis
|
||||
* @param string $name
|
||||
* @param int $maxLocks
|
||||
* @param int $releaseAfter
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($redis, $name, $maxLocks, $releaseAfter)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->redis = $redis;
|
||||
$this->maxLocks = $maxLocks;
|
||||
$this->releaseAfter = $releaseAfter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire the lock for the given number of seconds.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @param callable|null $callback
|
||||
* @param int $sleep
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function block($timeout, $callback = null, $sleep = 250)
|
||||
{
|
||||
$starting = time();
|
||||
|
||||
$id = Str::random(20);
|
||||
|
||||
while (! $slot = $this->acquire($id)) {
|
||||
if (time() - $timeout >= $starting) {
|
||||
throw new LimiterTimeoutException;
|
||||
}
|
||||
|
||||
usleep($sleep * 1000);
|
||||
}
|
||||
|
||||
if (is_callable($callback)) {
|
||||
try {
|
||||
return tap($callback(), function () use ($slot, $id) {
|
||||
$this->release($slot, $id);
|
||||
});
|
||||
} catch (Throwable $exception) {
|
||||
$this->release($slot, $id);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire the lock.
|
||||
*
|
||||
* @param string $id A unique identifier for this lock
|
||||
* @return mixed
|
||||
*/
|
||||
protected function acquire($id)
|
||||
{
|
||||
$slots = array_map(function ($i) {
|
||||
return $this->name.$i;
|
||||
}, range(1, $this->maxLocks));
|
||||
|
||||
return $this->redis->eval(...array_merge(
|
||||
[$this->lockScript(), count($slots)],
|
||||
array_merge($slots, [$this->name, $this->releaseAfter, $id])
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Lua script for acquiring a lock.
|
||||
*
|
||||
* KEYS - The keys that represent available slots
|
||||
* ARGV[1] - The limiter name
|
||||
* ARGV[2] - The number of seconds the slot should be reserved
|
||||
* ARGV[3] - The unique identifier for this lock
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function lockScript()
|
||||
{
|
||||
return <<<'LUA'
|
||||
for index, value in pairs(redis.call('mget', unpack(KEYS))) do
|
||||
if not value then
|
||||
redis.call('set', KEYS[index], ARGV[3], "EX", ARGV[2])
|
||||
return ARGV[1]..index
|
||||
end
|
||||
end
|
||||
LUA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $id
|
||||
* @return void
|
||||
*/
|
||||
protected function release($key, $id)
|
||||
{
|
||||
$this->redis->eval($this->releaseScript(), 1, $key, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Lua script to atomically release a lock.
|
||||
*
|
||||
* KEYS[1] - The name of the lock
|
||||
* ARGV[1] - The unique identifier for this lock
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function releaseScript()
|
||||
{
|
||||
return <<<'LUA'
|
||||
if redis.call('get', KEYS[1]) == ARGV[1]
|
||||
then
|
||||
return redis.call('del', KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
LUA;
|
||||
}
|
||||
}
|
||||
142
vendor/illuminate/redis/Limiters/ConcurrencyLimiterBuilder.php
vendored
Normal file
142
vendor/illuminate/redis/Limiters/ConcurrencyLimiterBuilder.php
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Redis\Limiters;
|
||||
|
||||
use Illuminate\Contracts\Redis\LimiterTimeoutException;
|
||||
use Illuminate\Support\InteractsWithTime;
|
||||
|
||||
class ConcurrencyLimiterBuilder
|
||||
{
|
||||
use InteractsWithTime;
|
||||
|
||||
/**
|
||||
* The Redis connection.
|
||||
*
|
||||
* @var \Illuminate\Redis\Connections\Connection
|
||||
*/
|
||||
public $connection;
|
||||
|
||||
/**
|
||||
* The name of the lock.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The maximum number of entities that can hold the lock at the same time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxLocks;
|
||||
|
||||
/**
|
||||
* The number of seconds to maintain the lock until it is automatically released.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $releaseAfter = 60;
|
||||
|
||||
/**
|
||||
* The amount of time to block until a lock is available.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 3;
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait between attempts to acquire the lock.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $sleep = 250;
|
||||
|
||||
/**
|
||||
* Create a new builder instance.
|
||||
*
|
||||
* @param \Illuminate\Redis\Connections\Connection $connection
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($connection, $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of locks that can be obtained per time window.
|
||||
*
|
||||
* @param int $maxLocks
|
||||
* @return $this
|
||||
*/
|
||||
public function limit($maxLocks)
|
||||
{
|
||||
$this->maxLocks = $maxLocks;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of seconds until the lock will be released.
|
||||
*
|
||||
* @param int $releaseAfter
|
||||
* @return $this
|
||||
*/
|
||||
public function releaseAfter($releaseAfter)
|
||||
{
|
||||
$this->releaseAfter = $this->secondsUntil($releaseAfter);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of time to block until a lock is available.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @return $this
|
||||
*/
|
||||
public function block($timeout)
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait between lock acquisition attempts.
|
||||
*
|
||||
* @param int $sleep
|
||||
* @return $this
|
||||
*/
|
||||
public function sleep($sleep)
|
||||
{
|
||||
$this->sleep = $sleep;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given callback if a lock is obtained, otherwise call the failure callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param callable|null $failure
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
|
||||
*/
|
||||
public function then(callable $callback, callable $failure = null)
|
||||
{
|
||||
try {
|
||||
return (new ConcurrencyLimiter(
|
||||
$this->connection, $this->name, $this->maxLocks, $this->releaseAfter
|
||||
))->block($this->timeout, $callback, $this->sleep);
|
||||
} catch (LimiterTimeoutException $e) {
|
||||
if ($failure) {
|
||||
return $failure($e);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
203
vendor/illuminate/redis/Limiters/DurationLimiter.php
vendored
Normal file
203
vendor/illuminate/redis/Limiters/DurationLimiter.php
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Redis\Limiters;
|
||||
|
||||
use Illuminate\Contracts\Redis\LimiterTimeoutException;
|
||||
|
||||
class DurationLimiter
|
||||
{
|
||||
/**
|
||||
* The Redis factory implementation.
|
||||
*
|
||||
* @var \Illuminate\Redis\Connections\Connection
|
||||
*/
|
||||
private $redis;
|
||||
|
||||
/**
|
||||
* The unique name of the lock.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* The allowed number of concurrent tasks.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxLocks;
|
||||
|
||||
/**
|
||||
* The number of seconds a slot should be maintained.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $decay;
|
||||
|
||||
/**
|
||||
* The timestamp of the end of the current duration.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $decaysAt;
|
||||
|
||||
/**
|
||||
* The number of remaining slots.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $remaining;
|
||||
|
||||
/**
|
||||
* Create a new duration limiter instance.
|
||||
*
|
||||
* @param \Illuminate\Redis\Connections\Connection $redis
|
||||
* @param string $name
|
||||
* @param int $maxLocks
|
||||
* @param int $decay
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($redis, $name, $maxLocks, $decay)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->decay = $decay;
|
||||
$this->redis = $redis;
|
||||
$this->maxLocks = $maxLocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire the lock for the given number of seconds.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @param callable|null $callback
|
||||
* @param int $sleep
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
|
||||
*/
|
||||
public function block($timeout, $callback = null, $sleep = 750)
|
||||
{
|
||||
$starting = time();
|
||||
|
||||
while (! $this->acquire()) {
|
||||
if (time() - $timeout >= $starting) {
|
||||
throw new LimiterTimeoutException;
|
||||
}
|
||||
|
||||
usleep($sleep * 1000);
|
||||
}
|
||||
|
||||
if (is_callable($callback)) {
|
||||
return $callback();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to acquire the lock.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function acquire()
|
||||
{
|
||||
$results = $this->redis->eval(
|
||||
$this->luaScript(), 1, $this->name, microtime(true), time(), $this->decay, $this->maxLocks
|
||||
);
|
||||
|
||||
$this->decaysAt = $results[1];
|
||||
|
||||
$this->remaining = max(0, $results[2]);
|
||||
|
||||
return (bool) $results[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the key has been "accessed" too many times.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function tooManyAttempts()
|
||||
{
|
||||
[$this->decaysAt, $this->remaining] = $this->redis->eval(
|
||||
$this->tooManyAttemptsLuaScript(), 1, $this->name, microtime(true), time(), $this->decay, $this->maxLocks
|
||||
);
|
||||
|
||||
return $this->remaining <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the limiter.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->redis->del($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Lua script for acquiring a lock.
|
||||
*
|
||||
* KEYS[1] - The limiter name
|
||||
* ARGV[1] - Current time in microseconds
|
||||
* ARGV[2] - Current time in seconds
|
||||
* ARGV[3] - Duration of the bucket
|
||||
* ARGV[4] - Allowed number of tasks
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function luaScript()
|
||||
{
|
||||
return <<<'LUA'
|
||||
local function reset()
|
||||
redis.call('HMSET', KEYS[1], 'start', ARGV[2], 'end', ARGV[2] + ARGV[3], 'count', 1)
|
||||
return redis.call('EXPIRE', KEYS[1], ARGV[3] * 2)
|
||||
end
|
||||
|
||||
if redis.call('EXISTS', KEYS[1]) == 0 then
|
||||
return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
|
||||
end
|
||||
|
||||
if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
|
||||
return {
|
||||
tonumber(redis.call('HINCRBY', KEYS[1], 'count', 1)) <= tonumber(ARGV[4]),
|
||||
redis.call('HGET', KEYS[1], 'end'),
|
||||
ARGV[4] - redis.call('HGET', KEYS[1], 'count')
|
||||
}
|
||||
end
|
||||
|
||||
return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
|
||||
LUA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Lua script to determine if the key has been "accessed" too many times.
|
||||
*
|
||||
* KEYS[1] - The limiter name
|
||||
* ARGV[1] - Current time in microseconds
|
||||
* ARGV[2] - Current time in seconds
|
||||
* ARGV[3] - Duration of the bucket
|
||||
* ARGV[4] - Allowed number of tasks
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function tooManyAttemptsLuaScript()
|
||||
{
|
||||
return <<<'LUA'
|
||||
|
||||
if redis.call('EXISTS', KEYS[1]) == 0 then
|
||||
return {0, ARGV[2] + ARGV[3]}
|
||||
end
|
||||
|
||||
if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
|
||||
return {
|
||||
redis.call('HGET', KEYS[1], 'end'),
|
||||
ARGV[4] - redis.call('HGET', KEYS[1], 'count')
|
||||
}
|
||||
end
|
||||
|
||||
return {0, ARGV[2] + ARGV[3]}
|
||||
LUA;
|
||||
}
|
||||
}
|
||||
142
vendor/illuminate/redis/Limiters/DurationLimiterBuilder.php
vendored
Normal file
142
vendor/illuminate/redis/Limiters/DurationLimiterBuilder.php
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Redis\Limiters;
|
||||
|
||||
use Illuminate\Contracts\Redis\LimiterTimeoutException;
|
||||
use Illuminate\Support\InteractsWithTime;
|
||||
|
||||
class DurationLimiterBuilder
|
||||
{
|
||||
use InteractsWithTime;
|
||||
|
||||
/**
|
||||
* The Redis connection.
|
||||
*
|
||||
* @var \Illuminate\Redis\Connections\Connection
|
||||
*/
|
||||
public $connection;
|
||||
|
||||
/**
|
||||
* The name of the lock.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The maximum number of locks that can be obtained per time window.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxLocks;
|
||||
|
||||
/**
|
||||
* The amount of time the lock window is maintained.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $decay;
|
||||
|
||||
/**
|
||||
* The amount of time to block until a lock is available.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 3;
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait between attempts to acquire the lock.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $sleep = 750;
|
||||
|
||||
/**
|
||||
* Create a new builder instance.
|
||||
*
|
||||
* @param \Illuminate\Redis\Connections\Connection $connection
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($connection, $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of locks that can be obtained per time window.
|
||||
*
|
||||
* @param int $maxLocks
|
||||
* @return $this
|
||||
*/
|
||||
public function allow($maxLocks)
|
||||
{
|
||||
$this->maxLocks = $maxLocks;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of time the lock window is maintained.
|
||||
*
|
||||
* @param \DateTimeInterface|\DateInterval|int $decay
|
||||
* @return $this
|
||||
*/
|
||||
public function every($decay)
|
||||
{
|
||||
$this->decay = $this->secondsUntil($decay);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of time to block until a lock is available.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @return $this
|
||||
*/
|
||||
public function block($timeout)
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait between lock acquisition attempts.
|
||||
*
|
||||
* @param int $sleep
|
||||
* @return $this
|
||||
*/
|
||||
public function sleep($sleep)
|
||||
{
|
||||
$this->sleep = $sleep;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given callback if a lock is obtained, otherwise call the failure callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param callable|null $failure
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
|
||||
*/
|
||||
public function then(callable $callback, callable $failure = null)
|
||||
{
|
||||
try {
|
||||
return (new DurationLimiter(
|
||||
$this->connection, $this->name, $this->maxLocks, $this->decay
|
||||
))->block($this->timeout, $callback, $this->sleep);
|
||||
} catch (LimiterTimeoutException $e) {
|
||||
if ($failure) {
|
||||
return $failure($e);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user