这里使用的redis哨兵模式 , 并通过实例化不同的连接池达到读写分离的作用
首先如图所示 , 创建这四个文件
RedisMaster
<?
namespace App\Pool;
use Hyperf\Contract\ConnectionInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Pool\Exception\ConnectionException;
use Hyperf\Redis\Exception\InvalidRedisConnectionException;
use Hyperf\Redis\RedisConnection;
class RedisMaster extends RedisConnection implements ConnectionInterface
{
public function reconnect(): bool
{
$auth = $this->config['auth'];
$db = $this->config['db'];
$cluster = $this->config['cluster']['enable'] ?? false;
$sentinel = $this->config['sentinel']['enable'] ?? false;
$redis = match (true) {
$cluster => $this->createRedisCluster(),
$sentinel => $this->createRedisSentinel(),
default => $this->createRedis($this->config),
};
$options = $this->config['options'] ?? [];
foreach ($options as $name => $value) {
// The name is int, value is string.
$redis->setOption($name, $value);
}
if ($redis instanceof \Redis && isset($auth) && $auth !== '') {
$redis->auth($auth);
}
$database = $this->database ?? $db;
if ($database > 0) {
$redis->select($database);
}
$this->connection = $redis;
$this->lastUseTime = microtime(true);
return true;
}
protected function createRedisSentinel(): \Redis
{
try {
$nodes = $this->config['sentinel']['nodes'] ?? [];
$timeout = $this->config['timeout'] ?? 0;
$persistent = $this->config['sentinel']['persistent'] ?? null;
$retryInterval = $this->config['retry_interval'] ?? 0;
$readTimeout = $this->config['sentinel']['read_timeout'] ?? 0;
$masterName = $this->config['sentinel']['master_name'] ?? '';
$auth = $this->config['sentinel']['auth'] ?? null;
shuffle($nodes);
$host = null;
$port = null;
foreach ($nodes as $node) {
try {
[$sentinelHost, $sentinelPort] = explode(':', $node);
$sentinel = new \RedisSentinel(
$sentinelHost,
intval($sentinelPort),
$timeout,
$persistent,
$retryInterval,
$readTimeout
// $auth
);
$masterInfo = $sentinel->getMasterAddrByName($masterName);
if (is_array($masterInfo) && count($masterInfo) >= 2) {
[$host, $port] = $masterInfo;
break;
}
// echo $sentinelHost.$sentinelPort;
} catch (\Throwable $exception) {
$logger = $this->container->get(StdoutLoggerInterface::class);
$logger->warning('Redis sentinel connection failed, caused by ' . $exception->getMessage());
continue;
}
}
if ($host === null && $port === null) {
throw new InvalidRedisConnectionException('Connect sentinel redis server failed.');
}
$redis = $this->createRedis([
'host' => $host,
'port' => $port,
'timeout' => $timeout,
'retry_interval' => $retryInterval,
'read_timeout' => $readTimeout,
'reserved' => null
]);
} catch (\Throwable $e) {
throw new ConnectionException('Connection reconnect failed ' . $e->getMessage());
}
return $redis;
}
protected function createRedis(array $config): \Redis
{
$parameters = [
$config['host'] ?? '',
$config['port'] ?? 6379,
$config['timeout'] ?? 0.0,
$config['reserved'] ?? null,
$config['retry_interval'] ?? 0,
$config['read_timeout'] ?? 0.0,
];
if (!empty($config['context'])) {
$parameters[] = $config['context'];
}
$redis = new \Redis();
$res = $redis->connect($config['host'], $config['port'], $config['timeout'], $config['reserved'], $config['retry_interval'], $config['read_timeout']);
if (!$res) {
throw new ConnectionException('Connection reconnect failed.');
}
return $redis;
}
}
RedisMasterPool
<?php
namespace App\Pool;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\ConnectionInterface;
use Hyperf\Pool\Pool;
use Hyperf\Redis\Frequency;
use Hyperf\Utils\Arr;
use Psr\Container\ContainerInterface;
class RedisMasterPool extends Pool
{
protected array $config;
public function __construct(ContainerInterface $container, protected string $name = 'default')
{
$config = $container->get(ConfigInterface::class);
$key = sprintf('redis.%s', $this->name);
if (!$config->has($key)) {
throw new \InvalidArgumentException(sprintf('config[%s] is not exist!', $key));
}
$this->config = $config->get($key);
$options = Arr::get($this->config, 'pool', []);
$this->frequency = make(Frequency::class);
parent::__construct($container, $options);
}
public function getName(): string
{
return $this->name;
}
protected function createConnection(): ConnectionInterface
{
return new RedisMaster($this->container, $this, $this->config);
}
}
RedisSlave
<?
namespace App\Pool;
use Hyperf\Contract\ConnectionInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Pool\Exception\ConnectionException;
use Hyperf\Redis\Exception\InvalidRedisConnectionException;
use Hyperf\Redis\RedisConnection;
class RedisSlave extends RedisConnection implements ConnectionInterface
{
public function reconnect(): bool
{
$auth = $this->config['auth'];
$db = $this->config['db'];
$cluster = $this->config['cluster']['enable'] ?? false;
$sentinel = $this->config['sentinel']['enable'] ?? false;
$redis = match (true) {
$cluster => $this->createRedisCluster(),
$sentinel => $this->createRedisSentinel(),
default => $this->createRedis($this->config),
};
$options = $this->config['options'] ?? [];
foreach ($options as $name => $value) {
// The name is int, value is string.
$redis->setOption($name, $value);
}
if ($redis instanceof \Redis && isset($auth) && $auth !== '') {
$redis->auth($auth);
}
$database = $this->database ?? $db;
if ($database > 0) {
$redis->select($database);
}
$this->connection = $redis;
$this->lastUseTime = microtime(true);
return true;
}
protected function createRedisSentinel(): \Redis
{
try {
$nodes = $this->config['sentinel']['nodes'] ?? [];
$timeout = $this->config['timeout'] ?? 0;
$persistent = $this->config['sentinel']['persistent'] ?? null;
$retryInterval = $this->config['retry_interval'] ?? 0;
$readTimeout = $this->config['sentinel']['read_timeout'] ?? 0;
$masterName = $this->config['sentinel']['master_name'] ?? '';
$auth = $this->config['sentinel']['auth'] ?? null;
shuffle($nodes);
$host = null;
$port = null;
$slaveInfos = $this->getSlaveRedis();
if (count($slaveInfos) > 0) {
shuffle($slaveInfos);
foreach ($slaveInfos as $slaveInfo) {
try {
if (is_array($slaveInfo) && count($slaveInfo) >= 3) {
[$addr, $host, $port] = $slaveInfo;
break;
}
} catch (\Throwable $exception) {
$logger = $this->container->get(StdoutLoggerInterface::class);
$logger->warning('Redis slaves connection failed, caused by ' . $exception->getMessage());
continue;
}
}
} else {
foreach ($nodes as $node) {
try {
[$sentinelHost, $sentinelPort] = explode(':', $node);
$sentinel = new \RedisSentinel(
$sentinelHost,
intval($sentinelPort),
$timeout,
$persistent,
$retryInterval,
$readTimeout
// $auth
);
$masterInfo = $sentinel->getMasterAddrByName($masterName);
if (is_array($masterInfo) && count($masterInfo) >= 2) {
[$host, $port] = $masterInfo;
break;
}
} catch (\Throwable $exception) {
$logger = $this->container->get(StdoutLoggerInterface::class);
$logger->warning('Redis sentinel connection failed, caused by ' . $exception->getMessage());
continue;
}
}
}
if ($host === null && $port === null) {
throw new InvalidRedisConnectionException('Connect sentinel redis server failed.');
}
$redis = $this->createRedis([
'host' => $host,
'port' => $port,
'timeout' => $timeout,
'retry_interval' => $retryInterval,
'read_timeout' => $readTimeout,
'reserved' => null
]);
} catch (\Throwable $e) {
throw new ConnectionException('Connection reconnect failed ' . $e->getMessage());
}
return $redis;
}
protected function getSlaveRedis()
{
$redisPool = $this->container->get(\App\Pool\RedisMasterPool::class);
$masterRedisConn = $redisPool->get();
$infoReplication = $masterRedisConn->info('replication');
$masterRedisConn->release();
$slavesCount = $infoReplication['connected_slaves'];
$slaves = [];
for ($i = 0; $i < $slavesCount; $i ++) {
preg_match('/ip=(.*?),port=(\d+)/', $infoReplication['slave' . $i], $slaves[]);
}
return $slaves;
}
protected function createRedis(array $config): \Redis
{
$parameters = [
$config['host'] ?? '',
$config['port'] ?? 6379,
$config['timeout'] ?? 0.0,
$config['reserved'] ?? null,
$config['retry_interval'] ?? 0,
$config['read_timeout'] ?? 0.0,
];
if (!empty($config['context'])) {
$parameters[] = $config['context'];
}
$redis = new \Redis();
$res = $redis->connect($config['host'], $config['port'], $config['timeout'], $config['reserved'], $config['retry_interval'], $config['read_timeout']);
if (!$res) {
throw new ConnectionException('Connection reconnect failed.');
}
return $redis;
}
}
RedisSlavePool
<?php
namespace App\Pool;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\ConnectionInterface;
use Hyperf\Pool\Pool;
use Hyperf\Redis\Frequency;
use Hyperf\Utils\Arr;
use Psr\Container\ContainerInterface;
class RedisSlavePool extends Pool
{
protected array $config;
public function __construct(ContainerInterface $container, protected string $name = 'default')
{
$config = $container->get(ConfigInterface::class);
$key = sprintf('redis.%s', $this->name);
if (!$config->has($key)) {
throw new \InvalidArgumentException(sprintf('config[%s] is not exist!', $key));
}
$this->config = $config->get($key);
$options = Arr::get($this->config, 'pool', []);
$this->frequency = make(Frequency::class);
parent::__construct($container, $options);
}
public function getName(): string
{
return $this->name;
}
protected function createConnection(): ConnectionInterface
{
return new RedisSlave($this->container, $this, $this->config);
}
}
Config/autoload/redis
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'auth' => env('REDIS_AUTH', null),
'port' => (int) env('REDIS_PORT', 6379),
'db' => (int) env('REDIS_DB', 0),
'timeout' => 0.0,
'reserved' => null,
'retry_interval' => 0,
'read_timeout' => 0.0,
'cluster' => [
'enable' => (bool) env('REDIS_CLUSTER_ENABLE', false),
'name' => null,
'seeds' => [],
],
'sentinel' => [
'enable' => (bool) env('REDIS_SENTINEL_ENABLE', true),
'master_name' => env('REDIS_MASTER_NAME', 'mymaster'),
'nodes' => explode(';', env('REDIS_SENTINEL_NODE', '')),
'persistent' => '',
'read_timeout' => 100,
'auth' => 'ygyc',
],
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => (float) env('REDIS_MAX_IDLE_TIME', 0),
],
],
];
env文件自行修改
使用
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Utils\ApplicationContext;
class IndexController extends AbstractController
{
public function index()
{
$container = ApplicationContext::getContainer();
$redisMaster = $container->get(\App\Pool\RedisMasterPool::class)->get();
$redisMaster->set('times', '2022-10-26');
$redisMaster->release();
$redisSlave = $container->get(\App\Pool\RedisSlavePool::class)->get();
$time = $redisSlave->get('time');
$redisSlave->release();
return ['redis' => $time];
}
}
这里使用完redis后需要release释放连接到连接池中