Compare commits
1 Commits
master
...
redis-sent
Author | SHA1 | Date |
---|---|---|
|
a7bb060bbd |
402
Connection.php
402
Connection.php
|
@ -3,84 +3,384 @@
|
||||||
namespace dominion\cache;
|
namespace dominion\cache;
|
||||||
|
|
||||||
use Yii;
|
use Yii;
|
||||||
use Predis\Client;
|
use yii\base\Component;
|
||||||
use yii\helpers\Inflector;
|
use yii\helpers\Inflector;
|
||||||
|
use Redis;
|
||||||
|
|
||||||
class Connection extends \yii\redis\Connection
|
/**
|
||||||
|
* Работа череез однк ноду
|
||||||
|
* пример конфига
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* 'class' => 'dominion\cache\Connection',
|
||||||
|
* 'parameters' => [
|
||||||
|
* 'host' => 'node1.redis.service.optiweb',
|
||||||
|
* 'port' => '6379',
|
||||||
|
* 'prefix' => "prefix",
|
||||||
|
* 'username' => '',
|
||||||
|
* 'password' => 'password',
|
||||||
|
* 'database' => 0,
|
||||||
|
* 'read_timeout' => 0,
|
||||||
|
* 'scan' => ,
|
||||||
|
* 'name' => ,
|
||||||
|
* 'serializer' => ,
|
||||||
|
* 'compression' =>,
|
||||||
|
* 'compression_level' =>
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
class Connection extends Component
|
||||||
{
|
{
|
||||||
/**
|
public $parameters = [];
|
||||||
* @var mixed Connection parameters for one or more servers.
|
|
||||||
*/
|
|
||||||
public $parameters;
|
|
||||||
/**
|
|
||||||
* @var mixed Options to configure some behaviours of the client.
|
|
||||||
*/
|
|
||||||
public $options = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Client redis socket connection
|
* @var array List of available redis commands.
|
||||||
|
* @see https://redis.io/commands
|
||||||
*/
|
*/
|
||||||
private $_socket = false;
|
public $redisCommands = [
|
||||||
|
'APPEND', // Append a value to a key
|
||||||
|
'AUTH', // Authenticate to the server
|
||||||
|
'BGREWRITEAOF', // Asynchronously rewrite the append-only file
|
||||||
|
'BGSAVE', // Asynchronously save the dataset to disk
|
||||||
|
'BITCOUNT', // Count set bits in a string
|
||||||
|
'BITFIELD', // Perform arbitrary bitfield integer operations on strings
|
||||||
|
'BITOP', // Perform bitwise operations between strings
|
||||||
|
'BITPOS', // Find first bit set or clear in a string
|
||||||
|
'BLPOP', // Remove and get the first element in a list, or block until one is available
|
||||||
|
'BRPOP', // Remove and get the last element in a list, or block until one is available
|
||||||
|
'BRPOPLPUSH', // Pop a value from a list, push it to another list and return it; or block until one is available
|
||||||
|
'CLIENT KILL', // Kill the connection of a client
|
||||||
|
'CLIENT LIST', // Get the list of client connections
|
||||||
|
'CLIENT GETNAME', // Get the current connection name
|
||||||
|
'CLIENT PAUSE', // Stop processing commands from clients for some time
|
||||||
|
'CLIENT REPLY', // Instruct the server whether to reply to commands
|
||||||
|
'CLIENT SETNAME', // Set the current connection name
|
||||||
|
'CLUSTER ADDSLOTS', // Assign new hash slots to receiving node
|
||||||
|
'CLUSTER COUNTKEYSINSLOT', // Return the number of local keys in the specified hash slot
|
||||||
|
'CLUSTER DELSLOTS', // Set hash slots as unbound in receiving node
|
||||||
|
'CLUSTER FAILOVER', // Forces a slave to perform a manual failover of its master.
|
||||||
|
'CLUSTER FORGET', // Remove a node from the nodes table
|
||||||
|
'CLUSTER GETKEYSINSLOT', // Return local key names in the specified hash slot
|
||||||
|
'CLUSTER INFO', // Provides info about Redis Cluster node state
|
||||||
|
'CLUSTER KEYSLOT', // Returns the hash slot of the specified key
|
||||||
|
'CLUSTER MEET', // Force a node cluster to handshake with another node
|
||||||
|
'CLUSTER NODES', // Get Cluster config for the node
|
||||||
|
'CLUSTER REPLICATE', // Reconfigure a node as a slave of the specified master node
|
||||||
|
'CLUSTER RESET', // Reset a Redis Cluster node
|
||||||
|
'CLUSTER SAVECONFIG', // Forces the node to save cluster state on disk
|
||||||
|
'CLUSTER SETSLOT', // Bind a hash slot to a specific node
|
||||||
|
'CLUSTER SLAVES', // List slave nodes of the specified master node
|
||||||
|
'CLUSTER SLOTS', // Get array of Cluster slot to node mappings
|
||||||
|
'COMMAND', // Get array of Redis command details
|
||||||
|
'COMMAND COUNT', // Get total number of Redis commands
|
||||||
|
'COMMAND GETKEYS', // Extract keys given a full Redis command
|
||||||
|
'COMMAND INFO', // Get array of specific Redis command details
|
||||||
|
'CONFIG GET', // Get the value of a configuration parameter
|
||||||
|
'CONFIG REWRITE', // Rewrite the configuration file with the in memory configuration
|
||||||
|
'CONFIG SET', // Set a configuration parameter to the given value
|
||||||
|
'CONFIG RESETSTAT', // Reset the stats returned by INFO
|
||||||
|
'DBSIZE', // Return the number of keys in the selected database
|
||||||
|
'DEBUG OBJECT', // Get debugging information about a key
|
||||||
|
'DEBUG SEGFAULT', // Make the server crash
|
||||||
|
'DECR', // Decrement the integer value of a key by one
|
||||||
|
'DECRBY', // Decrement the integer value of a key by the given number
|
||||||
|
'DEL', // Delete a key
|
||||||
|
'DISCARD', // Discard all commands issued after MULTI
|
||||||
|
'DUMP', // Return a serialized version of the value stored at the specified key.
|
||||||
|
'ECHO', // Echo the given string
|
||||||
|
'EVAL', // Execute a Lua script server side
|
||||||
|
'EVALSHA', // Execute a Lua script server side
|
||||||
|
'EXEC', // Execute all commands issued after MULTI
|
||||||
|
'EXISTS', // Determine if a key exists
|
||||||
|
'EXPIRE', // Set a key's time to live in seconds
|
||||||
|
'EXPIREAT', // Set the expiration for a key as a UNIX timestamp
|
||||||
|
'FLUSHALL', // Remove all keys from all databases
|
||||||
|
'FLUSHDB', // Remove all keys from the current database
|
||||||
|
'GEOADD', // Add one or more geospatial items in the geospatial index represented using a sorted set
|
||||||
|
'GEOHASH', // Returns members of a geospatial index as standard geohash strings
|
||||||
|
'GEOPOS', // Returns longitude and latitude of members of a geospatial index
|
||||||
|
'GEODIST', // Returns the distance between two members of a geospatial index
|
||||||
|
'GEORADIUS', // Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point
|
||||||
|
'GEORADIUSBYMEMBER', // Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member
|
||||||
|
'GET', // Get the value of a key
|
||||||
|
'GETBIT', // Returns the bit value at offset in the string value stored at key
|
||||||
|
'GETRANGE', // Get a substring of the string stored at a key
|
||||||
|
'GETSET', // Set the string value of a key and return its old value
|
||||||
|
'HDEL', // Delete one or more hash fields
|
||||||
|
'HEXISTS', // Determine if a hash field exists
|
||||||
|
'HGET', // Get the value of a hash field
|
||||||
|
'HGETALL', // Get all the fields and values in a hash
|
||||||
|
'HINCRBY', // Increment the integer value of a hash field by the given number
|
||||||
|
'HINCRBYFLOAT', // Increment the float value of a hash field by the given amount
|
||||||
|
'HKEYS', // Get all the fields in a hash
|
||||||
|
'HLEN', // Get the number of fields in a hash
|
||||||
|
'HMGET', // Get the values of all the given hash fields
|
||||||
|
'HMSET', // Set multiple hash fields to multiple values
|
||||||
|
'HSET', // Set the string value of a hash field
|
||||||
|
'HSETNX', // Set the value of a hash field, only if the field does not exist
|
||||||
|
'HSTRLEN', // Get the length of the value of a hash field
|
||||||
|
'HVALS', // Get all the values in a hash
|
||||||
|
'INCR', // Increment the integer value of a key by one
|
||||||
|
'INCRBY', // Increment the integer value of a key by the given amount
|
||||||
|
'INCRBYFLOAT', // Increment the float value of a key by the given amount
|
||||||
|
'INFO', // Get information and statistics about the server
|
||||||
|
'KEYS', // Find all keys matching the given pattern
|
||||||
|
'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk
|
||||||
|
'LINDEX', // Get an element from a list by its index
|
||||||
|
'LINSERT', // Insert an element before or after another element in a list
|
||||||
|
'LLEN', // Get the length of a list
|
||||||
|
'LPOP', // Remove and get the first element in a list
|
||||||
|
'LPUSH', // Prepend one or multiple values to a list
|
||||||
|
'LPUSHX', // Prepend a value to a list, only if the list exists
|
||||||
|
'LRANGE', // Get a range of elements from a list
|
||||||
|
'LREM', // Remove elements from a list
|
||||||
|
'LSET', // Set the value of an element in a list by its index
|
||||||
|
'LTRIM', // Trim a list to the specified range
|
||||||
|
'MGET', // Get the values of all the given keys
|
||||||
|
'MIGRATE', // Atomically transfer a key from a Redis instance to another one.
|
||||||
|
'MONITOR', // Listen for all requests received by the server in real time
|
||||||
|
'MOVE', // Move a key to another database
|
||||||
|
'MSET', // Set multiple keys to multiple values
|
||||||
|
'MSETNX', // Set multiple keys to multiple values, only if none of the keys exist
|
||||||
|
'MULTI', // Mark the start of a transaction block
|
||||||
|
'OBJECT', // Inspect the internals of Redis objects
|
||||||
|
'PERSIST', // Remove the expiration from a key
|
||||||
|
'PEXPIRE', // Set a key's time to live in milliseconds
|
||||||
|
'PEXPIREAT', // Set the expiration for a key as a UNIX timestamp specified in milliseconds
|
||||||
|
'PFADD', // Adds the specified elements to the specified HyperLogLog.
|
||||||
|
'PFCOUNT', // Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).
|
||||||
|
'PFMERGE', // Merge N different HyperLogLogs into a single one.
|
||||||
|
'PING', // Ping the server
|
||||||
|
'PSETEX', // Set the value and expiration in milliseconds of a key
|
||||||
|
'PSUBSCRIBE', // Listen for messages published to channels matching the given patterns
|
||||||
|
'PUBSUB', // Inspect the state of the Pub/Sub subsystem
|
||||||
|
'PTTL', // Get the time to live for a key in milliseconds
|
||||||
|
'PUBLISH', // Post a message to a channel
|
||||||
|
'PUNSUBSCRIBE', // Stop listening for messages posted to channels matching the given patterns
|
||||||
|
'QUIT', // Close the connection
|
||||||
|
'RANDOMKEY', // Return a random key from the keyspace
|
||||||
|
'READONLY', // Enables read queries for a connection to a cluster slave node
|
||||||
|
'READWRITE', // Disables read queries for a connection to a cluster slave node
|
||||||
|
'RENAME', // Rename a key
|
||||||
|
'RENAMENX', // Rename a key, only if the new key does not exist
|
||||||
|
'RESTORE', // Create a key using the provided serialized value, previously obtained using DUMP.
|
||||||
|
'ROLE', // Return the role of the instance in the context of replication
|
||||||
|
'RPOP', // Remove and get the last element in a list
|
||||||
|
'RPOPLPUSH', // Remove the last element in a list, prepend it to another list and return it
|
||||||
|
'RPUSH', // Append one or multiple values to a list
|
||||||
|
'RPUSHX', // Append a value to a list, only if the list exists
|
||||||
|
'SADD', // Add one or more members to a set
|
||||||
|
'SAVE', // Synchronously save the dataset to disk
|
||||||
|
'SCARD', // Get the number of members in a set
|
||||||
|
'SCRIPT DEBUG', // Set the debug mode for executed scripts.
|
||||||
|
'SCRIPT EXISTS', // Check existence of scripts in the script cache.
|
||||||
|
'SCRIPT FLUSH', // Remove all the scripts from the script cache.
|
||||||
|
'SCRIPT KILL', // Kill the script currently in execution.
|
||||||
|
'SCRIPT LOAD', // Load the specified Lua script into the script cache.
|
||||||
|
'SDIFF', // Subtract multiple sets
|
||||||
|
'SDIFFSTORE', // Subtract multiple sets and store the resulting set in a key
|
||||||
|
'SELECT', // Change the selected database for the current connection
|
||||||
|
'SET', // Set the string value of a key
|
||||||
|
'SETBIT', // Sets or clears the bit at offset in the string value stored at key
|
||||||
|
'SETEX', // Set the value and expiration of a key
|
||||||
|
'SETNX', // Set the value of a key, only if the key does not exist
|
||||||
|
'SETRANGE', // Overwrite part of a string at key starting at the specified offset
|
||||||
|
'SHUTDOWN', // Synchronously save the dataset to disk and then shut down the server
|
||||||
|
'SINTER', // Intersect multiple sets
|
||||||
|
'SINTERSTORE', // Intersect multiple sets and store the resulting set in a key
|
||||||
|
'SISMEMBER', // Determine if a given value is a member of a set
|
||||||
|
'SLAVEOF', // Make the server a slave of another instance, or promote it as master
|
||||||
|
'SLOWLOG', // Manages the Redis slow queries log
|
||||||
|
'SMEMBERS', // Get all the members in a set
|
||||||
|
'SMOVE', // Move a member from one set to another
|
||||||
|
'SORT', // Sort the elements in a list, set or sorted set
|
||||||
|
'SPOP', // Remove and return one or multiple random members from a set
|
||||||
|
'SRANDMEMBER', // Get one or multiple random members from a set
|
||||||
|
'SREM', // Remove one or more members from a set
|
||||||
|
'STRLEN', // Get the length of the value stored in a key
|
||||||
|
'SUBSCRIBE', // Listen for messages published to the given channels
|
||||||
|
'SUNION', // Add multiple sets
|
||||||
|
'SUNIONSTORE', // Add multiple sets and store the resulting set in a key
|
||||||
|
'SWAPDB', // Swaps two Redis databases
|
||||||
|
'SYNC', // Internal command used for replication
|
||||||
|
'TIME', // Return the current server time
|
||||||
|
'TOUCH', // Alters the last access time of a key(s). Returns the number of existing keys specified.
|
||||||
|
'TTL', // Get the time to live for a key
|
||||||
|
'TYPE', // Determine the type stored at key
|
||||||
|
'UNSUBSCRIBE', // Stop listening for messages posted to the given channels
|
||||||
|
'UNLINK', // Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.
|
||||||
|
'UNWATCH', // Forget about all watched keys
|
||||||
|
'WAIT', // Wait for the synchronous replication of all the write commands sent in the context of the current connection
|
||||||
|
'WATCH', // Watch the given keys to determine execution of the MULTI/EXEC block
|
||||||
|
'XACK', // Removes one or multiple messages from the pending entries list (PEL) of a stream consumer group
|
||||||
|
'XADD', // Appends the specified stream entry to the stream at the specified key
|
||||||
|
'XCLAIM', // Changes the ownership of a pending message, so that the new owner is the consumer specified as the command argument
|
||||||
|
'XDEL', // Removes the specified entries from a stream, and returns the number of entries deleted
|
||||||
|
'XGROUP', // Manages the consumer groups associated with a stream data structure
|
||||||
|
'XINFO', // Retrieves different information about the streams and associated consumer groups
|
||||||
|
'XLEN', // Returns the number of entries inside a stream
|
||||||
|
'XPENDING', // Fetching data from a stream via a consumer group, and not acknowledging such data, has the effect of creating pending entries
|
||||||
|
'XRANGE', // Returns the stream entries matching a given range of IDs
|
||||||
|
'XREAD', // Read data from one or multiple streams, only returning entries with an ID greater than the last received ID reported by the caller
|
||||||
|
'XREADGROUP', // Special version of the XREAD command with support for consumer groups
|
||||||
|
'XREVRANGE', // Exactly like XRANGE, but with the notable difference of returning the entries in reverse order, and also taking the start-end range in reverse order
|
||||||
|
'XTRIM', // Trims the stream to a given number of items, evicting older items (items with lower IDs) if needed
|
||||||
|
'ZADD', // Add one or more members to a sorted set, or update its score if it already exists
|
||||||
|
'ZCARD', // Get the number of members in a sorted set
|
||||||
|
'ZCOUNT', // Count the members in a sorted set with scores within the given values
|
||||||
|
'ZINCRBY', // Increment the score of a member in a sorted set
|
||||||
|
'ZINTERSTORE', // Intersect multiple sorted sets and store the resulting sorted set in a new key
|
||||||
|
'ZLEXCOUNT', // Count the number of members in a sorted set between a given lexicographical range
|
||||||
|
'ZRANGE', // Return a range of members in a sorted set, by index
|
||||||
|
'ZRANGEBYLEX', // Return a range of members in a sorted set, by lexicographical range
|
||||||
|
'ZREVRANGEBYLEX', // Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.
|
||||||
|
'ZRANGEBYSCORE', // Return a range of members in a sorted set, by score
|
||||||
|
'ZRANK', // Determine the index of a member in a sorted set
|
||||||
|
'ZREM', // Remove one or more members from a sorted set
|
||||||
|
'ZREMRANGEBYLEX', // Remove all members in a sorted set between the given lexicographical range
|
||||||
|
'ZREMRANGEBYRANK', // Remove all members in a sorted set within the given indexes
|
||||||
|
'ZREMRANGEBYSCORE', // Remove all members in a sorted set within the given scores
|
||||||
|
'ZREVRANGE', // Return a range of members in a sorted set, by index, with scores ordered from high to low
|
||||||
|
'ZREVRANGEBYSCORE', // Return a range of members in a sorted set, by score, with scores ordered from high to low
|
||||||
|
'ZREVRANK', // Determine the index of a member in a sorted set, with scores ordered from high to low
|
||||||
|
'ZSCORE', // Get the score associated with the given member in a sorted set
|
||||||
|
'ZUNIONSTORE', // Add multiple sorted sets and store the resulting sorted set in a new key
|
||||||
|
'SCAN', // Incrementally iterate the keys space
|
||||||
|
'SSCAN', // Incrementally iterate Set elements
|
||||||
|
'HSCAN', // Incrementally iterate hash fields and associated values
|
||||||
|
'ZSCAN', // Incrementally iterate sorted sets elements and associated scores
|
||||||
|
];
|
||||||
|
|
||||||
public function __call($name, $params)
|
public $redis = false;
|
||||||
{
|
|
||||||
$redisCommand = strtoupper(Inflector::camel2words($name, false));
|
|
||||||
if (in_array($redisCommand, $this->redisCommands)) {
|
|
||||||
return $this->executeCommand($redisCommand, $params);
|
|
||||||
} else {
|
|
||||||
return parent::__call($name, $params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function executeCommand($name, $params = [])
|
|
||||||
{
|
|
||||||
$this->open();
|
|
||||||
Yii::debug("Executing Redis Command: {$name} " . implode(' ', $params), __METHOD__);
|
|
||||||
return $this->_socket->executeCommand(
|
|
||||||
$this->_socket->createCommand($name, $params)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the connection when this component is being serialized.
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function __sleep()
|
public function __sleep()
|
||||||
{
|
{
|
||||||
$this->close();
|
$this->close();
|
||||||
return array_keys(get_object_vars($this));
|
return array_keys(get_object_vars($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a value indicating whether the DB connection is established.
|
|
||||||
* @return bool whether the DB connection is established
|
|
||||||
*/
|
|
||||||
public function getIsActive()
|
public function getIsActive()
|
||||||
{
|
{
|
||||||
return $this->_socket !== false;
|
return $this->redis !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function open()
|
public function open()
|
||||||
{
|
{
|
||||||
if ($this->_socket !== false) {
|
if (!$this->isActive)
|
||||||
return;
|
{
|
||||||
|
Yii::debug('Opening redis DB connection: ' . var_export($this->parameters, true), __METHOD__);
|
||||||
|
$this->redis = $this->createClient($this->parameters);
|
||||||
}
|
}
|
||||||
Yii::debug('Opening redis DB connection: ' /*. var_export($this->parameters, true)*/, __METHOD__);
|
|
||||||
$this->_socket = new Client($this->parameters, $this->options);
|
|
||||||
$this->initConnection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the currently active DB connection.
|
|
||||||
* It does nothing if the connection is already closed.
|
|
||||||
*/
|
|
||||||
public function close()
|
public function close()
|
||||||
{
|
{
|
||||||
if ($this->_socket !== false) {
|
if ($this->isActive)
|
||||||
|
{
|
||||||
Yii::debug('Closing DB connection: ' . var_export($this->parameters, true), __METHOD__);
|
Yii::debug('Closing DB connection: ' . var_export($this->parameters, true), __METHOD__);
|
||||||
$this->_socket->disconnect();
|
$this->redis->disconnect();
|
||||||
$this->_socket = false;
|
$this->redis = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __call($name, $params)
|
||||||
|
{
|
||||||
|
$redisCommand = strtoupper(Inflector::camel2words($name, false));
|
||||||
|
if (in_array($redisCommand, $this->redisCommands))
|
||||||
|
{
|
||||||
|
return $this->executeCommand($redisCommand, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::__call($name, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function executeCommand($name, $params = [])
|
||||||
|
{
|
||||||
|
$this->open();
|
||||||
|
Yii::debug("Executing Redis Command: {$name} " . var_export($params, true), __METHOD__);
|
||||||
|
return $this->redis->{$name}(...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createClient(array $config): Redis
|
||||||
|
{
|
||||||
|
$client = new Redis;
|
||||||
|
$this->establishConnection($client, $config);
|
||||||
|
if (!empty($config['password']))
|
||||||
|
{
|
||||||
|
if (isset($config['username']) && $config['username'] !== '' && is_string($config['password']))
|
||||||
|
{
|
||||||
|
$client->auth([$config['username'], $config['password']]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$client->auth($config['password']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($config['database']))
|
||||||
|
{
|
||||||
|
$client->select((int) $config['database']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($config['prefix']))
|
||||||
|
{
|
||||||
|
$client->setOption(Redis::OPT_PREFIX, $config['prefix']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($config['read_timeout']))
|
||||||
|
{
|
||||||
|
$client->setOption(Redis::OPT_READ_TIMEOUT, $config['read_timeout']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($config['scan']))
|
||||||
|
{
|
||||||
|
$client->setOption(Redis::OPT_SCAN, $config['scan']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($config['name']))
|
||||||
|
{
|
||||||
|
$client->client('SETNAME', $config['name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('serializer', $config))
|
||||||
|
{
|
||||||
|
$client->setOption(Redis::OPT_SERIALIZER, $config['serializer']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('compression', $config))
|
||||||
|
{
|
||||||
|
$client->setOption(Redis::OPT_COMPRESSION, $config['compression']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('compression_level', $config))
|
||||||
|
{
|
||||||
|
$client->setOption(Redis::OPT_COMPRESSION_LEVEL, $config['compression_level']);
|
||||||
|
}
|
||||||
|
return $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function establishConnection($client, array $config)
|
||||||
|
{
|
||||||
|
$persistent = $config['persistent'] ?? false;
|
||||||
|
|
||||||
|
$parameters = [
|
||||||
|
$config['host'],
|
||||||
|
$config['port'],
|
||||||
|
isset($config['timeout']) ? $config['timeout'] : 0.0,
|
||||||
|
$persistent ? (isset($config['persistent_id']) ? $config['persistent_id'] : null) : null,
|
||||||
|
isset($config['retry_interval']) ? $config['retry_interval'] : 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (version_compare(phpversion('redis'), '3.1.3', '>='))
|
||||||
|
{
|
||||||
|
$parameters[] = isset($config['read_timeout']) ? $config['read_timeout'] : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_compare(phpversion('redis'), '5.3.0', '>=') && !is_null($context = isset($config['context']) ? $config['context'] : null))
|
||||||
|
{
|
||||||
|
$parameters[] = $context;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client->{$persistent ? 'pconnect' : 'connect'}(...$parameters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ class RedisCache
|
||||||
{
|
{
|
||||||
if (!self::$livetime)
|
if (!self::$livetime)
|
||||||
{
|
{
|
||||||
self::$livetime = isset(Yii::$app->params['redis'], Yii::$app->params['redis']['livetime']) ? (int) Yii::$app->params['redis']['livetime'] : 300;
|
self::$livetime = isset(Yii::$app->params['redis'], Yii::$app->params['redis']['livetime']) ? (int) Yii::$app->params['redis']['livetime'] : 15*60;
|
||||||
}
|
}
|
||||||
return self::$livetime;
|
return self::$livetime;
|
||||||
}
|
}
|
||||||
|
@ -61,16 +61,11 @@ class RedisCache
|
||||||
{
|
{
|
||||||
if (!self::$prefix)
|
if (!self::$prefix)
|
||||||
{
|
{
|
||||||
self::$prefix = isset(Yii::$app->params['redis'], Yii::$app->params['redis']['prefix']) ? (string)Yii::$app->params['redis']['prefix'] : '';
|
self::$prefix = isset(Yii::$app->redis->parameters, Yii::$app->redis->parameters['prefix']) ? Yii::$app->redis->parameters['prefix'] : '';
|
||||||
}
|
}
|
||||||
return self::$prefix;
|
return self::$prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function calculateKey($key)
|
|
||||||
{
|
|
||||||
return self::prefix() . $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function hdel($key, $value)
|
public static function hdel($key, $value)
|
||||||
{
|
{
|
||||||
if (self::$setDeleteKey)
|
if (self::$setDeleteKey)
|
||||||
|
@ -79,7 +74,7 @@ class RedisCache
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Yii::$app->redis->hdel(self::calculateKey($key), $value);
|
Yii::$app->redis->hdel($key, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,13 +86,13 @@ class RedisCache
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Yii::$app->redis->del(self::calculateKey($key));
|
Yii::$app->redis->del($key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function hset($key, $field, $value)
|
public static function hset($key, $field, $value)
|
||||||
{
|
{
|
||||||
return self::getActive() ? Yii::$app->redis->hset(self::calculateKey($key), $field, json_encode($value)) : false;
|
return self::getActive() ? Yii::$app->redis->hset($key, $field, json_encode($value)) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function hsetModel($key, $field, $model)
|
public static function hsetModel($key, $field, $model)
|
||||||
|
@ -137,7 +132,7 @@ class RedisCache
|
||||||
$output = false;
|
$output = false;
|
||||||
if(self::getActive())
|
if(self::getActive())
|
||||||
{
|
{
|
||||||
$output = json_decode(Yii::$app->redis->hget(self::calculateKey($key), $field), true);
|
$output = json_decode(Yii::$app->redis->hget($key, $field), true);
|
||||||
}
|
}
|
||||||
return $output === null ? false : $output;;
|
return $output === null ? false : $output;;
|
||||||
}
|
}
|
||||||
|
@ -201,7 +196,7 @@ class RedisCache
|
||||||
foreach ($patterns as $pattern)
|
foreach ($patterns as $pattern)
|
||||||
{
|
{
|
||||||
$arKeys = [];
|
$arKeys = [];
|
||||||
while ($values = Yii::$app->redis->hscan(self::calculateKey($key), $cursor, $pattern))
|
while ($values = Yii::$app->redis->hscan($key, $cursor, $pattern))
|
||||||
{
|
{
|
||||||
foreach ($values as $vkey => $value)
|
foreach ($values as $vkey => $value)
|
||||||
{
|
{
|
||||||
|
@ -233,7 +228,7 @@ class RedisCache
|
||||||
public static function getKeyTypes()
|
public static function getKeyTypes()
|
||||||
{
|
{
|
||||||
|
|
||||||
$result = Yii::$app->redis->keys(self::calculateKey('*'));
|
$result = Yii::$app->redis->keys('*');
|
||||||
$output = ['*'];
|
$output = ['*'];
|
||||||
foreach ($result as $val)
|
foreach ($result as $val)
|
||||||
{
|
{
|
||||||
|
@ -258,7 +253,7 @@ class RedisCache
|
||||||
{
|
{
|
||||||
self::deleteAll($type);
|
self::deleteAll($type);
|
||||||
$type = $type == "*" ? "*" : "{$type}:*";
|
$type = $type == "*" ? "*" : "{$type}:*";
|
||||||
$result = Yii::$app->redis->keys(self::calculateKey($type));
|
$result = Yii::$app->redis->keys($type);
|
||||||
foreach ($result as $val)
|
foreach ($result as $val)
|
||||||
{
|
{
|
||||||
self::deleteAll(str_replace(self::prefix(), '', $val));
|
self::deleteAll(str_replace(self::prefix(), '', $val));
|
||||||
|
@ -280,7 +275,7 @@ class RedisCache
|
||||||
{
|
{
|
||||||
$options = ['EX' => $options];
|
$options = ['EX' => $options];
|
||||||
}
|
}
|
||||||
Yii::$app->redis->set(self::calculateKey($key), json_encode($value) .' '. implode(' ', $options));
|
Yii::$app->redis->set($key, json_encode($value), $options);
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
@ -313,7 +308,7 @@ class RedisCache
|
||||||
$result = false;
|
$result = false;
|
||||||
if (self::getActive())
|
if (self::getActive())
|
||||||
{
|
{
|
||||||
$result = json_decode(Yii::$app->redis->get(self::calculateKey($key)), true);
|
$result = json_decode(Yii::$app->redis->get($key), true);
|
||||||
}
|
}
|
||||||
return $result === null ? false : $result;
|
return $result === null ? false : $result;
|
||||||
}
|
}
|
||||||
|
@ -380,14 +375,14 @@ class RedisCache
|
||||||
*/
|
*/
|
||||||
public static function oldCacheDelete()
|
public static function oldCacheDelete()
|
||||||
{
|
{
|
||||||
$result = Yii::$app->redis->keys(self::calculateKey('del:*'));
|
$result = Yii::$app->redis->keys('del:*');
|
||||||
foreach ($result as $val)
|
foreach ($result as $val)
|
||||||
{
|
{
|
||||||
$cursor = 0;//null;
|
$cursor = 0;//null;
|
||||||
$key = str_replace(self::prefix(), '', $val);
|
$key = str_replace(self::prefix(), '', $val);
|
||||||
$pattern = '*';
|
$pattern = '*';
|
||||||
$arKeys = [];
|
$arKeys = [];
|
||||||
while ($values = Yii::$app->redis->hscan(self::calculateKey($key), $cursor, $pattern))
|
while ($values = Yii::$app->redis->hscan($key, $cursor, $pattern))
|
||||||
{
|
{
|
||||||
foreach ($values as $vkey => $value)
|
foreach ($values as $vkey => $value)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dominion\cache;
|
||||||
|
|
||||||
|
use Yii;
|
||||||
|
use yii\helpers\Inflector;
|
||||||
|
use Redis;
|
||||||
|
use RedisException;
|
||||||
|
use RedisSentinel;
|
||||||
|
|
||||||
|
class SentinelConnection extends Connection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Команды достапные на любой ноде master или slave
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $redisSlaveCommands = [
|
||||||
|
'GET',
|
||||||
|
'HGET',
|
||||||
|
//нужно перечислить все, но пока так
|
||||||
|
];
|
||||||
|
|
||||||
|
public $slaves = [];
|
||||||
|
|
||||||
|
public function executeCommand($name, $params = [])
|
||||||
|
{
|
||||||
|
$this->open();
|
||||||
|
Yii::debug("Executing Redis Command: {$name} " . var_export($params, true), __METHOD__);
|
||||||
|
|
||||||
|
$redis = $this->redis; //мастер
|
||||||
|
$redisCommand = strtoupper(Inflector::camel2words($name, false));
|
||||||
|
if (in_array($redisCommand, $this->redisSlaveCommands))
|
||||||
|
{
|
||||||
|
if(!empty($this->slaves))
|
||||||
|
{
|
||||||
|
//читаем из произвольного раба
|
||||||
|
$redis = $this->slaves[array_rand($this->slaves)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $redis->{$name}(...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isValidServer(mixed $server): bool
|
||||||
|
{
|
||||||
|
return is_array($server) && isset($server['ip']) && isset($server['port']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createClient(array $config): Redis
|
||||||
|
{
|
||||||
|
$service = $config['sentinel_service'] ?? 'mymaster';
|
||||||
|
|
||||||
|
$hosts = is_array($config['sentinel_host']) ? $config['sentinel_host'] : [$config['sentinel_host']];
|
||||||
|
foreach ($hosts as $host)
|
||||||
|
{
|
||||||
|
$newConfig = $config;
|
||||||
|
$newConfig['sentinel_host'] = $host;
|
||||||
|
$sentinel = $this->connectToSentinel($newConfig);
|
||||||
|
|
||||||
|
if ($sentinel->ping())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$master = $sentinel->master($service);
|
||||||
|
if (!$this->isValidServer($master))
|
||||||
|
{
|
||||||
|
throw new RedisException(sprintf("No master found for service '%s'.", $service));
|
||||||
|
}
|
||||||
|
$slaves = $sentinel->slaves($service);
|
||||||
|
foreach ($slaves as $slave)
|
||||||
|
{
|
||||||
|
if ($this->isValidServer($slave))
|
||||||
|
{
|
||||||
|
//могут быть проблемв из-за большого количества подключений
|
||||||
|
$this->slaves[] = parent::createClient(array_merge($config, [
|
||||||
|
'host' => $slave['ip'],
|
||||||
|
'port' => $slave['port'],
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::createClient(array_merge($config, [
|
||||||
|
'host' => $master['ip'],
|
||||||
|
'port' => $master['port'],
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function connectToSentinel(array $config): RedisSentinel
|
||||||
|
{
|
||||||
|
$host = $config['sentinel_host'] ?? '';
|
||||||
|
$port = $config['sentinel_port'] ?? 26379;
|
||||||
|
$timeout = $config['sentinel_timeout'] ?? 0.2;
|
||||||
|
$persistent = $config['sentinel_persistent'] ?? null;
|
||||||
|
$retryInterval = $config['sentinel_retry_interval'] ?? 0;
|
||||||
|
$readTimeout = $config['sentinel_read_timeout'] ?? 0;
|
||||||
|
$username = $config['sentinel_username'] ?? '';
|
||||||
|
$password = $config['sentinel_password'] ?? '';
|
||||||
|
$ssl = $config['sentinel_ssl'] ?? null;
|
||||||
|
|
||||||
|
if (strlen(trim($host)) === 0)
|
||||||
|
{
|
||||||
|
throw new ConfigurationException('No host has been specified for the Redis Sentinel connection.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth = null;
|
||||||
|
if (strlen(trim($username)) !== 0 && strlen(trim($password)) !== 0)
|
||||||
|
{
|
||||||
|
$auth = [$username, $password];
|
||||||
|
}
|
||||||
|
elseif (strlen(trim($password)) !== 0)
|
||||||
|
{
|
||||||
|
$auth = $password;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_compare(phpversion('redis'), '6.0', '>='))
|
||||||
|
{
|
||||||
|
$options = [
|
||||||
|
'host' => $host,
|
||||||
|
'port' => $port,
|
||||||
|
'connectTimeout' => $timeout,
|
||||||
|
'persistent' => $persistent,
|
||||||
|
'retryInterval' => $retryInterval,
|
||||||
|
'readTimeout' => $readTimeout,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($auth !== null)
|
||||||
|
{
|
||||||
|
$options['auth'] = $auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_compare(phpversion('redis'), '6.1', '>=') && $ssl !== null)
|
||||||
|
{
|
||||||
|
$options['ssl'] = $ssl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RedisSentinel($options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($auth !== null)
|
||||||
|
{
|
||||||
|
/** @noinspection PhpMethodParametersCountMismatchInspection */
|
||||||
|
return new RedisSentinel($host, $port, $timeout, $persistent, $retryInterval, $readTimeout, $auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RedisSentinel($host, $port, $timeout, $persistent, $retryInterval, $readTimeout);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @link https://www.yiiframework.com/
|
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||||
|
* @license https://www.yiiframework.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace dominion\cache;
|
||||||
|
|
||||||
|
use Yii;
|
||||||
|
use yii\base\InvalidConfigException;
|
||||||
|
use yii\di\Instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis Session implements a session component using [redis](https://redis.io/) as the storage medium.
|
||||||
|
*
|
||||||
|
* Redis Session requires redis version 2.6.12 or higher to work properly.
|
||||||
|
*
|
||||||
|
* It needs to be configured with a redis [[Connection]] that is also configured as an application component.
|
||||||
|
* By default it will use the `redis` application component.
|
||||||
|
*
|
||||||
|
* To use redis Session as the session application component, configure the application as follows,
|
||||||
|
*
|
||||||
|
* ~~~
|
||||||
|
* [
|
||||||
|
* 'components' => [
|
||||||
|
* 'session' => [
|
||||||
|
* 'class' => 'yii\redis\Session',
|
||||||
|
* 'redis' => [
|
||||||
|
* 'hostname' => 'localhost',
|
||||||
|
* 'port' => 6379,
|
||||||
|
* 'database' => 0,
|
||||||
|
* ]
|
||||||
|
* ],
|
||||||
|
* ],
|
||||||
|
* ]
|
||||||
|
* ~~~
|
||||||
|
*
|
||||||
|
* Or if you have configured the redis [[Connection]] as an application component, the following is sufficient:
|
||||||
|
*
|
||||||
|
* ~~~
|
||||||
|
* [
|
||||||
|
* 'components' => [
|
||||||
|
* 'session' => [
|
||||||
|
* 'class' => 'yii\redis\Session',
|
||||||
|
* // 'redis' => 'redis' // id of the connection application component
|
||||||
|
* ],
|
||||||
|
* ],
|
||||||
|
* ]
|
||||||
|
* ~~~
|
||||||
|
*
|
||||||
|
* @property-read bool $useCustomStorage Whether to use custom storage.
|
||||||
|
*
|
||||||
|
* @author Carsten Brandt <mail@cebe.cc>
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
class Session extends \yii\web\Session
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
|
||||||
|
* This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
|
||||||
|
* redis connection as an application component.
|
||||||
|
* After the Session object is created, if you want to change this property, you should only assign it
|
||||||
|
* with a Redis [[Connection]] object.
|
||||||
|
*/
|
||||||
|
public $redis = 'redis';
|
||||||
|
/**
|
||||||
|
* @var string a string prefixed to every cache key so that it is unique. If not set,
|
||||||
|
* it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
|
||||||
|
* if you don't want to use key prefix. It is recommended that you explicitly set this property to some
|
||||||
|
* static value if the cached data needs to be shared among multiple applications.
|
||||||
|
*/
|
||||||
|
public $keyPrefix;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the redis Session component.
|
||||||
|
* This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
|
||||||
|
* @throws InvalidConfigException if [[redis]] is invalid.
|
||||||
|
*/
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
$this->redis = Instance::ensure($this->redis, Connection::className());
|
||||||
|
if ($this->keyPrefix === null) {
|
||||||
|
$this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
|
||||||
|
}
|
||||||
|
parent::init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a value indicating whether to use custom session storage.
|
||||||
|
* This method overrides the parent implementation and always returns true.
|
||||||
|
* @return bool whether to use custom storage.
|
||||||
|
*/
|
||||||
|
public function getUseCustomStorage()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session open handler.
|
||||||
|
* @internal Do not call this method directly.
|
||||||
|
* @param string $savePath session save path
|
||||||
|
* @param string $sessionName session name
|
||||||
|
* @return bool whether session is opened successfully
|
||||||
|
*/
|
||||||
|
public function openSession($savePath, $sessionName)
|
||||||
|
{
|
||||||
|
if ($this->getUseStrictMode()) {
|
||||||
|
$id = $this->getId();
|
||||||
|
if (!$this->redis->exists($this->calculateKey($id))) {
|
||||||
|
//This session id does not exist, mark it for forced regeneration
|
||||||
|
$this->_forceRegenerateId = $id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::openSession($savePath, $sessionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session read handler.
|
||||||
|
* Do not call this method directly.
|
||||||
|
* @param string $id session ID
|
||||||
|
* @return string the session data
|
||||||
|
*/
|
||||||
|
public function readSession($id)
|
||||||
|
{
|
||||||
|
$data = $this->redis->get($this->calculateKey($id));
|
||||||
|
|
||||||
|
return $data === false || $data === null ? '' : $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session write handler.
|
||||||
|
* Do not call this method directly.
|
||||||
|
* @param string $id session ID
|
||||||
|
* @param string $data session data
|
||||||
|
* @return bool whether session write is successful
|
||||||
|
*/
|
||||||
|
public function writeSession($id, $data)
|
||||||
|
{
|
||||||
|
if ($this->getUseStrictMode() && $id === $this->_forceRegenerateId) {
|
||||||
|
//Ignore write when forceRegenerate is active for this id
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) $this->redis->set($this->calculateKey($id), $data, ['EX' => $this->getTimeout()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session destroy handler.
|
||||||
|
* Do not call this method directly.
|
||||||
|
* @param string $id session ID
|
||||||
|
* @return bool whether session is destroyed successfully
|
||||||
|
*/
|
||||||
|
public function destroySession($id)
|
||||||
|
{
|
||||||
|
$this->redis->del($this->calculateKey($id));
|
||||||
|
// @see https://github.com/yiisoft/yii2-redis/issues/82
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique key used for storing session data in cache.
|
||||||
|
* @param string $id session variable name
|
||||||
|
* @return string a safe cache key associated with the session variable name
|
||||||
|
*/
|
||||||
|
protected function calculateKey($id)
|
||||||
|
{
|
||||||
|
return $this->keyPrefix . md5(json_encode([__CLASS__, $id]));
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"yiisoft/yii2-redis": "~2.0.0",
|
"yiisoft/yii2": "~2.0.0"
|
||||||
"yiisoft/yii2": "~2.0.0",
|
|
||||||
"predis/predis": "^3.0.1"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|
Loading…
Reference in New Issue