Fix #17991: Improve yii\db\Connection master and slave failover, no connection attempt was made when all servers are marked as unavailable

This commit is contained in:
Carsten Brandt
2020-05-02 10:59:35 +02:00
committed by GitHub
parent e20358241c
commit 088fd518fe
2 changed files with 49 additions and 5 deletions

View File

@ -13,6 +13,7 @@ Yii Framework 2 Change Log
- Bug #17960: Fix unsigned primary key type mapping for SQLite (bizley)
- Enh #17758: `Query::withQuery()` can be used for CTE (sartor)
- Bug #17974: Fix ActiveRelationTrait compatibility with PHP 7.4 (Ximich)
- Bug #17991: Improve `yii\db\Connection` master and slave failover, no connection attempt was made when all servers are marked as unavailable (cebe)
- Enh #17993: Added `yii\i18n\Formatter::$currencyDecimalSeparator` to allow setting custom symbols for currency decimal in IntlNumberFormatter (XPOHOC269)
- Bug #18000: PK value of Oracle ActiveRecord is missing after save (mankwok)
- Enh #18006: Allow SameSite cookie pre PHP 7.3 (scottix)

View File

@ -332,6 +332,8 @@ class Connection extends Component
* the health status of the DB servers specified in [[masters]] and [[slaves]].
* This is used only when read/write splitting is enabled or [[masters]] is not empty.
* Set boolean `false` to disabled server status caching.
* @see openFromPoolSequentially() for details about the failover behavior.
* @see serverRetryInterval
*/
public $serverStatusCache = 'cache';
/**
@ -1099,12 +1101,16 @@ class Connection extends Component
/**
* Opens the connection to a server in the pool.
* This method implements the load balancing among the given list of the servers.
*
* This method implements load balancing and failover among the given list of the servers.
* Connections will be tried in random order.
* For details about the failover behavior, see [[openFromPoolSequentially]].
*
* @param array $pool the list of connection configurations in the server pool
* @param array $sharedConfig the configuration common to those given in `$pool`.
* @return Connection the opened DB connection, or `null` if no server is available
* @throws InvalidConfigException if a configuration does not specify "dsn"
* @see openFromPoolSequentially
*/
protected function openFromPool(array $pool, array $sharedConfig)
{
@ -1114,13 +1120,26 @@ class Connection extends Component
/**
* Opens the connection to a server in the pool.
* This method implements the load balancing among the given list of the servers.
* Connections will be tried in sequential order.
*
* This method implements failover among the given list of servers.
* Connections will be tried in sequential order. The first successful connection will return.
*
* If [[serverStatusCache]] is configured, this method will cache information about
* unreachable servers and does not try to connect to these for the time configured in [[serverRetryInterval]].
* This helps to keep the application stable when some servers are unavailable. Avoiding
* connection attempts to unavailable servers saves time when the connection attempts fail due to timeout.
*
* If none of the servers are available the status cache is ignored and connection attempts are made to all
* servers (Since version 2.0.35). This is to avoid downtime when all servers are unavailable for a short time.
* After a successful connection attempt the server is marked as avaiable again.
*
* @param array $pool the list of connection configurations in the server pool
* @param array $sharedConfig the configuration common to those given in `$pool`.
* @return Connection the opened DB connection, or `null` if no server is available
* @throws InvalidConfigException if a configuration does not specify "dsn"
* @since 2.0.11
* @see openFromPool
* @see serverStatusCache
*/
protected function openFromPoolSequentially(array $pool, array $sharedConfig)
{
@ -1134,8 +1153,8 @@ class Connection extends Component
$cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache;
foreach ($pool as $config) {
$config = array_merge($sharedConfig, $config);
foreach ($pool as $i => $config) {
$pool[$i] = $config = array_merge($sharedConfig, $config);
if (empty($config['dsn'])) {
throw new InvalidConfigException('The "dsn" option must be specified.');
}
@ -1158,6 +1177,30 @@ class Connection extends Component
// mark this server as dead and only retry it after the specified interval
$cache->set($key, 1, $this->serverRetryInterval);
}
// exclude server from retry below
unset($pool[$i]);
}
}
if ($cache instanceof CacheInterface) {
// if server status cache is enabled and no server is available
// ignore the cache and try to connect anyway
// $pool now only contains servers we did not already try in the loop above
foreach ($pool as $config) {
/* @var $db Connection */
$db = Yii::createObject($config);
try {
$db->open();
} catch (\Exception $e) {
Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
continue;
}
// mark this server as available again after successful connection
$cache->delete([__METHOD__, $config['dsn']]);
return $db;
}
}