This commit is contained in:
2021-07-05 15:42:44 +08:00
parent d3fd4385e6
commit 74bcdf257e
5 changed files with 559 additions and 240 deletions
+12
View File
@@ -38,6 +38,7 @@ use Snowflake\Event;
use Snowflake\Exception\InitException; use Snowflake\Exception\InitException;
use Snowflake\Exception\NotFindClassException; use Snowflake\Exception\NotFindClassException;
use Snowflake\Jwt\Jwt; use Snowflake\Jwt\Jwt;
use Snowflake\Pool\ClientsPool;
use Snowflake\Pool\Connection; use Snowflake\Pool\Connection;
use Snowflake\Pool\Pool as SPool; use Snowflake\Pool\Pool as SPool;
use Snowflake\Pool\Redis as SRedis; use Snowflake\Pool\Redis as SRedis;
@@ -434,6 +435,16 @@ abstract class BaseApplication extends Service
} }
/**
* @return ClientsPool
* @throws Exception
*/
public function getClientsPool(): ClientsPool
{
return $this->get('clientsPool');
}
/** /**
* @throws Exception * @throws Exception
*/ */
@@ -444,6 +455,7 @@ abstract class BaseApplication extends Service
'connections' => ['class' => Connection::class], 'connections' => ['class' => Connection::class],
'redis_connections' => ['class' => SRedis::class], 'redis_connections' => ['class' => SRedis::class],
'pool' => ['class' => SPool::class], 'pool' => ['class' => SPool::class],
'clientsPool' => ['class' => ClientsPool::class],
'config' => ['class' => Config::class], 'config' => ['class' => Config::class],
'logger' => ['class' => Logger::class], 'logger' => ['class' => Logger::class],
'annotation' => ['class' => SAnnotation::class], 'annotation' => ['class' => SAnnotation::class],
+2
View File
@@ -23,6 +23,7 @@ use Snowflake\Channel;
use Snowflake\Error\Logger; use Snowflake\Error\Logger;
use Snowflake\Event; use Snowflake\Event;
use Snowflake\Jwt\Jwt; use Snowflake\Jwt\Jwt;
use Snowflake\Pool\ClientsPool;
use Snowflake\Pool\Connection; use Snowflake\Pool\Connection;
use Snowflake\Pool\Pool as SPool; use Snowflake\Pool\Pool as SPool;
use Rpc\Producer as RPCProducer; use Rpc\Producer as RPCProducer;
@@ -54,6 +55,7 @@ use Rpc\Producer as RPCProducer;
* @property Channel $channel * @property Channel $channel
* @property Shutdown $shutdown * @property Shutdown $shutdown
* @property Emit $emit * @property Emit $emit
* @property ClientsPool $clientsPool
*/ */
trait TraitApplication trait TraitApplication
{ {
+314
View File
@@ -0,0 +1,314 @@
<?php
namespace Snowflake\Pool;
use Exception;
use JetBrains\PhpStorm\Pure;
use Snowflake\Abstracts\Component;
use Snowflake\Abstracts\Config;
use Snowflake\Exception\ConfigException;
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
use Swoole\Timer;
/**
* Class ClientsPool
* @package Snowflake\Pool
*/
class ClientsPool extends Component
{
/** @var Channel[] */
private static array $_connections = [];
public int $max = 60;
public int $creates = -1;
protected static array $hasCreate = [];
/**
* @param string $name
*/
public function increment(string $name)
{
if (!isset(static::$hasCreate[$name])) {
static::$hasCreate[$name] = 0;
}
static::$hasCreate[$name] += 1;
}
/**
* @param string $name
*/
public function decrement(string $name)
{
if (!isset(static::$hasCreate[$name])) {
return;
}
if (static::$hasCreate[$name] <= 0) {
return;
}
static::$hasCreate[$name] -= 1;
}
/**
* @return array
* @throws ConfigException
*/
private function getClearTime(): array
{
$firstClear = Config::get('pool.clear.start', 600);
$lastClear = Config::get('pool.clear.end', 300);
return [$firstClear, $lastClear];
}
/**
* @throws Exception
*/
public function Heartbeat_detection($ticker, string $name)
{
if (env('state') == 'exit') {
Timer::clear($this->creates);
$this->creates = -1;
} else {
$min = Config::get('databases.pool.min', 1);
if ($this->getChannel($name)->length() > $min) {
$this->flush($min);
}
}
}
/**
* @param $retain_number
* @throws Exception
*/
public function flush($retain_number)
{
$channels = $this->getChannels();
foreach ($channels as $name => $channel) {
$names[] = $name;
$this->pop($channel, $name, $retain_number);
}
static::$_connections = [];
if ($retain_number == 0) {
Timer::clear($this->creates);
$this->creates = -1;
}
}
/**
* @param $name
*/
protected function clearCreateLog($name): void
{
if (!isset(static::$hasCreate[$name])) {
return;
}
static::$hasCreate[$name] = 0;
}
/**
* @param Channel $channel
* @param $name
* @param $retain_number
* @throws Exception
*/
protected function pop(Channel $channel, $name, $retain_number): void
{
if (Coroutine::getCid() === -1) {
return;
}
while ($channel->length() > $retain_number) {
$connection = $channel->pop();
if ($connection) {
unset($connection);
}
$this->decrement($name);
}
}
/**
* @param $driver
* @param $name
* @param false $isMaster
* @param int $max
*/
public function initConnections($driver, $name, bool $isMaster = false, int $max = 60)
{
$name = $this->name($name, $isMaster);
if (isset(static::$_connections[$name]) && static::$_connections[$name] instanceof Channel) {
return;
}
if (Coroutine::getCid() === -1) {
return;
}
if ($this->creates === -1) {
$this->creates = Timer::tick(1000, [$this, 'Heartbeat_detection'], $name);
}
static::$_connections[$name] = new Channel($max);
$this->max = $max;
}
/**
* @param $name
* @return Channel
* @throws ConfigException
*/
private function getChannel($name): Channel
{
if (!isset(static::$_connections[$name])) {
static::$_connections[$name] = new Channel(Config::get('databases.pool.max', 10));
if ($this->creates === -1) {
$this->creates = Timer::tick(1000, [$this, 'Heartbeat_detection'], $name);
}
}
return static::$_connections[$name];
}
/**
* @param $name
* @return array
* @throws Exception
*/
public function getFromChannel($name): mixed
{
$channel = $this->getChannel($name);
if (!$channel->isEmpty()) {
$connection = $channel->pop();
if ($this->checkCanUse($name, $connection)) {
return $connection;
}
}
return null;
}
/**
* @param $cds
* @param false $isMaster
* @return string
*/
#[Pure] public function name($cds, bool $isMaster = false): string
{
if ($isMaster === true) {
return $cds . '_master';
} else {
return $cds . '_slave';
}
}
/**
* @param string $name
* @param mixed $client
* @return bool
* 检查连接可靠性
*/
public function checkCanUse(string $name, mixed $client): bool
{
return true;
}
/**
* @param array $config
* @param bool $isMaster
* @return mixed
* @throws Exception
*/
public function get(mixed $config, bool $isMaster): mixed
{
throw new Exception('Undefined system processing function.');
}
/**
* @param string $name
* @return bool
*/
public function hasItem(string $name): bool
{
if (isset(static::$_connections[$name])) {
return !static::$_connections[$name]->isEmpty();
}
return false;
}
/**
* @param string $name
* @return mixed
*/
public function size(string $name): mixed
{
if (Coroutine::getCid() === -1) {
return 0;
}
if (!isset(static::$_connections[$name])) {
return 0;
}
return static::$_connections[$name]->length();
}
/**
* @param string $name
* @param mixed $client
* @throws ConfigException
*/
public function push(string $name, mixed $client)
{
if (Coroutine::getCid() === -1) {
return;
}
$channel = $this->getChannel($name);
if (!$channel->isFull()) {
$channel->push($client);
}
unset($client);
}
/**
* @param string $name
* @throws Exception
*/
public function clean(string $name)
{
if (Coroutine::getCid() === -1 || !isset(static::$_connections[$name])) {
return;
}
$channel = static::$_connections[$name];
$this->pop($channel, $name, 0);
if ($this->creates > -1) {
Timer::clear($this->creates);
}
$channel->close();
static::$_connections[$name] = null;
}
/**
* @return Channel[]
*/
protected function getChannels(): array
{
return static::$_connections;
}
}
+203 -200
View File
@@ -6,7 +6,8 @@ namespace Snowflake\Pool;
use Exception; use Exception;
use HttpServer\Http\Context; use HttpServer\Http\Context;
use PDO; use PDO;
use Snowflake\Abstracts\Pool; use Snowflake\Abstracts\Component;
use Snowflake\Snowflake;
use Swoole\Coroutine; use Swoole\Coroutine;
use Swoole\Error; use Swoole\Error;
use Swoole\Runtime; use Swoole\Runtime;
@@ -16,227 +17,229 @@ use Throwable;
* Class Connection * Class Connection
* @package Snowflake\Pool * @package Snowflake\Pool
*/ */
class Connection extends Pool class Connection extends Component
{ {
private ?ClientsPool $clientsPool = null;
public int $timeout = 1900;
/**
* @param $timeout
*/
public function setTimeout($timeout)
{
$this->timeout = $timeout;
}
/** /**
* @param $value * @param $cds
*/ * @return bool
public function setLength($value) *
{ * db is in transaction
$this->max = $value; * @throws Exception
} */
public function inTransaction($cds): bool
{
return Context::getContext('begin_' . $this->getPool()->name('Mysql:' . $cds, true)) == 0;
}
/** /**
* @param $cds * @param $coroutineName
* @return bool * @throws Exception
* */
* db is in transaction public function beginTransaction($coroutineName)
*/ {
public function inTransaction($cds): bool $coroutineName = $this->getPool()->name('Mysql:' . $coroutineName, true);
{ if (!Context::hasContext('begin_' . $coroutineName)) {
return Context::getContext('begin_' . $this->name('mysql', $cds, true)) == 0; Context::setContext('begin_' . $coroutineName, 0);
} }
Context::increment('begin_' . $coroutineName);
if (Context::getContext('begin_' . $coroutineName) != 0) {
return;
}
$connection = Context::getContext($coroutineName);
if ($connection instanceof PDO && !$connection->inTransaction()) {
$connection->beginTransaction();
}
}
/** /**
* @param $coroutineName * @param $coroutineName
*/ * @throws Exception
public function beginTransaction($coroutineName) */
{ public function commit($coroutineName)
$coroutineName = $this->name('mysql', $coroutineName, true); {
if (!Context::hasContext('begin_' . $coroutineName)) { $coroutineName = $this->getPool()->name('Mysql:' . $coroutineName, true);
Context::setContext('begin_' . $coroutineName, 0); if (!Context::hasContext('begin_' . $coroutineName)) {
} return;
Context::increment('begin_' . $coroutineName); }
if (Context::getContext('begin_' . $coroutineName) != 0) { if (Context::decrement('begin_' . $coroutineName) > 0) {
return; return;
} }
$connection = Context::getContext($coroutineName); $connection = Context::getContext($coroutineName);
if ($connection instanceof PDO && !$connection->inTransaction()) { if (!($connection instanceof PDO)) {
$connection->beginTransaction(); return;
} }
} Context::setContext('begin_' . $coroutineName, 0);
if ($connection->inTransaction()) {
/** $connection->commit();
* @param $coroutineName }
*/ }
public function commit($coroutineName)
{
$coroutineName = $this->name('mysql', $coroutineName, true);
if (!Context::hasContext('begin_' . $coroutineName)) {
return;
}
if (Context::decrement('begin_' . $coroutineName) > 0) {
return;
}
$connection = Context::getContext($coroutineName);
if (!($connection instanceof PDO)) {
return;
}
Context::setContext('begin_' . $coroutineName, 0);
if ($connection->inTransaction()) {
$connection->commit();
}
}
/** /**
* @param $coroutineName * @param $coroutineName
*/ * @throws Exception
public function rollback($coroutineName) */
{ public function rollback($coroutineName)
$coroutineName = $this->name('mysql', $coroutineName, true); {
if (!Context::hasContext('begin_' . $coroutineName)) { $coroutineName = $this->getPool()->name('Mysql:' . $coroutineName, true);
return; if (!Context::hasContext('begin_' . $coroutineName)) {
} return;
if (Context::decrement('begin_' . $coroutineName) > 0) { }
return; if (Context::decrement('begin_' . $coroutineName) > 0) {
} return;
if (($connection = Context::getContext($coroutineName)) instanceof PDO) { }
if ($connection->inTransaction()) { if (($connection = Context::getContext($coroutineName)) instanceof PDO) {
$connection->rollBack(); if ($connection->inTransaction()) {
} $connection->rollBack();
} }
Context::setContext('begin_' . $coroutineName, 0); }
} Context::setContext('begin_' . $coroutineName, 0);
}
/** /**
* @param mixed $config * @param mixed $config
* @param bool $isMaster * @param bool $isMaster
* @return mixed * @return mixed
* @throws Exception * @throws Exception
*/ */
public function get(mixed $config, bool $isMaster = false): mixed public function get(mixed $config, bool $isMaster = false): mixed
{ {
$coroutineName = $this->name('mysql', $config['cds'], $isMaster); $coroutineName = $this->getPool()->name('Mysql:' . $config['cds'], $isMaster);
if (($pdo = Context::getContext($coroutineName)) instanceof PDO) { if (($pdo = Context::getContext($coroutineName)) instanceof PDO) {
return $pdo; return $pdo;
} }
/** @var PDO $connections */ /** @var PDO $connections */
$connections = $this->getFromChannel($coroutineName, $config); $connections = $this->getPool()->getFromChannel($coroutineName);
if ($number = Context::getContext('begin_' . $coroutineName, Coroutine::getCid())) { if (empty($connections)) {
$number > 0 && $connections->beginTransaction(); $connections = $this->createClient($coroutineName, $config);
} }
return Context::setContext($coroutineName, $connections); if ($number = Context::getContext('begin_' . $coroutineName, Coroutine::getCid())) {
} $number > 0 && $connections->beginTransaction();
}
return Context::setContext($coroutineName, $connections);
}
/** /**
* @param string $name * @param string $name
* @param mixed $config * @param mixed $config
* @return PDO * @return PDO
* @throws Exception * @throws Exception
*/ */
public function createClient(string $name, mixed $config): PDO public function createClient(string $name, mixed $config): PDO
{ {
if (Coroutine::getCid() === -1) { if (Coroutine::getCid() === -1) {
Runtime::enableCoroutine(false); Runtime::enableCoroutine(false);
} }
$cds = 'mysql:dbname=' . $config['database'] . ';host=' . $config['cds']; $cds = 'mysql:dbname=' . $config['database'] . ';host=' . $config['cds'];
$link = new PDO($cds, $config['username'], $config['password'], [ $link = new PDO($cds, $config['username'], $config['password'], [
PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_CASE => PDO::CASE_NATURAL, PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_TIMEOUT => $this->timeout, PDO::ATTR_TIMEOUT => $this->timeout,
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . ($config['charset'] ?? 'utf8mb4') PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . ($config['charset'] ?? 'utf8mb4')
]); ]);
$link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$link->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); $link->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
$link->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_EMPTY_STRING); $link->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_EMPTY_STRING);
return $link; return $link;
} }
/** /**
* @param $coroutineName * @param $coroutineName
* @param $isMaster * @param $isMaster
* @throws Exception * @throws Exception
*/ */
public function release($coroutineName, $isMaster) public function release($coroutineName, $isMaster)
{ {
$coroutineName = $this->name('mysql', $coroutineName, $isMaster); $coroutineName = $this->getPool()->name('Mysql:' . $coroutineName, $isMaster);
/** @var PDO $client */ /** @var PDO $client */
if (!($client = Context::getContext($coroutineName)) instanceof PDO) { if (!($client = Context::getContext($coroutineName)) instanceof PDO) {
return; return;
} }
if ($client->inTransaction()) { if ($client->inTransaction()) {
$client->commit(); $client->commit();
} }
$this->push($coroutineName, $client); $this->getPool()->push($coroutineName, $client);
Context::remove($coroutineName); Context::remove($coroutineName);
$this->lastTime = time(); }
}
/** /**
* @param $coroutineName * @param $coroutineName
* @return bool * @return bool
*/ */
private function hasClient($coroutineName): bool private function hasClient($coroutineName): bool
{ {
return Context::hasContext($coroutineName); return Context::hasContext($coroutineName);
} }
/** /**
* batch release * batch release
* @throws Exception * @throws Exception
*/ */
public function connection_clear() public function connection_clear()
{ {
$this->flush(0); $this->getPool()->flush(0);
} }
/** /**
* @param string $name * @param string $name
* @param mixed $client * @param mixed $client
* @return bool * @return bool
* @throws Exception * @throws Exception
*/ */
public function checkCanUse(string $name, mixed $client): bool public function checkCanUse(string $name, mixed $client): bool
{ {
try { try {
if (empty($client) || !($client instanceof PDO)) { if (empty($client) || !($client instanceof PDO)) {
$result = false; $result = false;
} else { } else {
$result = true; $result = true;
} }
} catch (Error | Throwable $exception) { } catch (Error | Throwable $exception) {
$result = $this->addError($exception, 'mysql'); $result = $this->addError($exception, 'mysql');
} finally { } finally {
if (!$result) { if (!$result) {
$this->decrement($name); $this->getPool()->decrement($name);
} }
return $result; return $result;
} }
} }
/** /**
* @param $coroutineName * @param $coroutineName
* @param bool $isMaster * @param bool $isMaster
* @throws Exception * @throws Exception
*/ */
public function disconnect($coroutineName, bool $isMaster = false) public function disconnect($coroutineName, bool $isMaster = false)
{ {
Context::remove($coroutineName); Context::remove($coroutineName);
$coroutineName = $this->name($coroutineName, $isMaster); $coroutineName = $this->getPool()->name('Mysql:' . $coroutineName, $isMaster);
$this->clean($coroutineName); $this->getPool()->clean($coroutineName);
} }
/**
* @return ClientsPool
* @throws Exception
*/
public function getPool(): ClientsPool
{
if (!$this->clientsPool) {
$this->clientsPool = Snowflake::app()->getClientsPool();
}
return $this->clientsPool;
}
} }
+28 -40
View File
@@ -8,9 +8,10 @@ namespace Snowflake\Pool;
use Exception; use Exception;
use HttpServer\Http\Context; use HttpServer\Http\Context;
use Redis as SRedis; use Redis as SRedis;
use Snowflake\Abstracts\Pool; use Snowflake\Abstracts\Component;
use Snowflake\Exception\ConfigException; use Snowflake\Exception\ConfigException;
use Snowflake\Exception\RedisConnectException; use Snowflake\Exception\RedisConnectException;
use Snowflake\Snowflake;
use Swoole\Coroutine; use Swoole\Coroutine;
use Swoole\Runtime; use Swoole\Runtime;
@@ -18,19 +19,10 @@ use Swoole\Runtime;
* Class RedisClient * Class RedisClient
* @package Snowflake\Snowflake\Pool * @package Snowflake\Snowflake\Pool
*/ */
class Redis extends Pool class Redis extends Component
{ {
private ?ClientsPool $clientsPool = null;
public int $_create = 0;
/**
* @param $value
*/
public function setLength($value)
{
$this->max = $value;
}
/** /**
@@ -41,11 +33,18 @@ class Redis extends Pool
*/ */
public function get(mixed $config, bool $isMaster = false): mixed public function get(mixed $config, bool $isMaster = false): mixed
{ {
$coroutineName = $this->name('redis', $config['host'], $isMaster); $coroutineName = $this->getPool()->name('Redis:' . $config['host'], $isMaster);
if (!Context::hasContext($coroutineName)) { if (Context::hasContext($coroutineName)) {
return Context::setContext($coroutineName, $this->getFromChannel($coroutineName, $config)); return Context::getContext($coroutineName);
} }
return Context::getContext($coroutineName); if (Coroutine::getCid() === -1) {
return Context::setContext($coroutineName, $this->createClient($coroutineName, $config));
}
$clients = $this->getPool()->getFromChannel($coroutineName);
if (empty($clients)) {
return Context::setContext($coroutineName, $this->createClient($coroutineName, $config));
}
return Context::setContext($coroutineName, $clients);
} }
@@ -75,7 +74,7 @@ class Redis extends Pool
$redis->setOption(SRedis::OPT_READ_TIMEOUT, $config['read_timeout']); $redis->setOption(SRedis::OPT_READ_TIMEOUT, $config['read_timeout']);
$redis->setOption(SRedis::OPT_PREFIX, $config['prefix']); $redis->setOption(SRedis::OPT_PREFIX, $config['prefix']);
$this->increment($name); $this->getPool()->increment($name);
return $redis; return $redis;
} }
@@ -85,15 +84,16 @@ class Redis extends Pool
* @param array $config * @param array $config
* @param bool $isMaster * @param bool $isMaster
* @throws ConfigException * @throws ConfigException
* @throws Exception
*/ */
public function release(array $config, bool $isMaster = false) public function release(array $config, bool $isMaster = false)
{ {
$coroutineName = $this->name('redis', $config['host'], $isMaster); $coroutineName = $this->getPool()->name('Redis:' . $config['host'], $isMaster);
if (!Context::hasContext($coroutineName)) { if (!Context::hasContext($coroutineName)) {
return; return;
} }
$this->push($coroutineName, Context::getContext($coroutineName)); $this->getPool()->push($coroutineName, Context::getContext($coroutineName));
Context::remove($coroutineName); Context::remove($coroutineName);
} }
@@ -104,37 +104,25 @@ class Redis extends Pool
*/ */
public function destroy(array $config, bool $isMaster = false) public function destroy(array $config, bool $isMaster = false)
{ {
$coroutineName = $this->name('redis', $config['host'], $isMaster); $coroutineName = $this->getPool()->name('Redis:' . $config['host'], $isMaster);
if (Context::hasContext($coroutineName)) { if (Context::hasContext($coroutineName)) {
$this->decrement($coroutineName); $this->getPool()->decrement($coroutineName);
} }
Context::remove($coroutineName); Context::remove($coroutineName);
$this->flush(0); $this->getPool()->flush(0);
} }
/** /**
* @param string $name * @return ClientsPool
* @param mixed $client
* @return bool
* @throws Exception * @throws Exception
*/ */
public function checkCanUse(string $name, mixed $client): bool public function getPool(): ClientsPool
{ {
try { if (!$this->clientsPool) {
if (!($client instanceof SRedis)) { $this->clientsPool = Snowflake::app()->getClientsPool();
$result = false;
} else {
$result = true;
}
} catch (\Throwable $exception) {
$this->addError($exception, 'redis');
$result = false;
} finally {
if (!$result) {
$this->decrement($name);
}
return $result;
} }
return $this->clientsPool;
} }