/** * Get the index of the reader connection, which may be a slave * This takes into account load ratios and lag times. It should * always return a consistent index during a given invocation * * Side effect: opens connections to databases */ function getReaderIndex($group = false, $wiki = false) { global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype; # FIXME: For now, only go through all this for mysql databases if ($wgDBtype != 'mysql') { return $this->getWriterIndex(); } if (count($this->mServers) == 1) { # Skip the load balancing if there's only one server return 0; } elseif ($group === false and $this->mReadIndex >= 0) { # Shortcut if generic reader exists already return $this->mReadIndex; } wfProfileIn(__METHOD__); $totalElapsed = 0; # convert from seconds to microseconds $timeout = $wgDBClusterTimeout * 1000000.0; # Find the relevant load array if ($group !== false) { if (isset($this->mGroupLoads[$group])) { $nonErrorLoads = $this->mGroupLoads[$group]; } else { # No loads for this group, return false and the caller can use some other group wfDebug(__METHOD__ . ": no loads for group {$group}\n"); wfProfileOut(__METHOD__); return false; } } else { $nonErrorLoads = $this->mLoads; } if (!$nonErrorLoads) { throw new MWException("Empty server array given to LoadBalancer"); } # Scale the configured load ratios according to the dynamic load (if the load monitor supports it) $this->getLoadMonitor()->scaleLoads($nonErrorLoads, $group, $wiki); $i = false; $found = false; $laggedSlaveMode = false; # First try quickly looking through the available servers for a server that # meets our criteria do { $totalThreadsConnected = 0; $overloadedServers = 0; $currentLoads = $nonErrorLoads; while (count($currentLoads)) { if ($wgReadOnly || $this->mAllowLagged || $laggedSlaveMode) { $i = $this->pickRandom($currentLoads); } else { $i = $this->getRandomNonLagged($currentLoads, $wiki); if ($i === false && count($currentLoads) != 0) { # All slaves lagged. Switch to read-only mode $wgReadOnly = wfMsgNoDBForContent('readonly_lag'); $i = $this->pickRandom($currentLoads); $laggedSlaveMode = true; } } if ($i === false) { # pickRandom() returned false # This is permanent and means the configuration or the load monitor # wants us to return false. wfDebugLog('connect', __METHOD__ . ": pickRandom() returned false\n"); wfProfileOut(__METHOD__); return false; } wfDebugLog('connect', __METHOD__ . ": Using reader #{$i}: {$this->mServers[$i]['host']}...\n"); $conn = $this->openConnection($i, $wiki); if (!$conn) { wfDebugLog('connect', __METHOD__ . ": Failed connecting to {$i}/{$wiki}\n"); unset($nonErrorLoads[$i]); unset($currentLoads[$i]); continue; } // Perform post-connection backoff $threshold = isset($this->mServers[$i]['max threads']) ? $this->mServers[$i]['max threads'] : false; $backoff = $this->getLoadMonitor()->postConnectionBackoff($conn, $threshold); // Decrement reference counter, we are finished with this connection. // It will be incremented for the caller later. if ($wiki !== false) { $this->reuseConnection($conn); } if ($backoff) { # Post-connection overload, don't use this server for now $totalThreadsConnected += $backoff; $overloadedServers++; unset($currentLoads[$i]); } else { # Return this server break 2; } } # No server found yet $i = false; # If all servers were down, quit now if (!count($nonErrorLoads)) { wfDebugLog('connect', "All servers down\n"); break; } # Some servers must have been overloaded if ($overloadedServers == 0) { throw new MWException(__METHOD__ . ": unexpectedly found no overloaded servers"); } # Back off for a while # Scale the sleep time by the number of connected threads, to produce a # roughly constant global poll rate $avgThreads = $totalThreadsConnected / $overloadedServers; $totalElapsed += $this->sleep($wgDBAvgStatusPoll * $avgThreads); } while ($totalElapsed < $timeout); if ($totalElapsed >= $timeout) { wfDebugLog('connect', "All servers busy\n"); $this->mErrorConnection = false; $this->mLastError = 'All servers busy'; } if ($i !== false) { # Slave connection successful # Wait for the session master pos for a short time if ($this->mWaitForPos && $i > 0) { if (!$this->doWait($i)) { $this->mServers[$i]['slave pos'] = $conn->getSlavePos(); } } if ($this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $i !== false) { $this->mReadIndex = $i; } } wfProfileOut(__METHOD__); return $i; }
/** * Get the index of the reader connection, which may be a slave * This takes into account load ratios and lag times. It should * always return a consistent index during a given invocation * * Side effect: opens connections to databases */ function getReaderIndex() { global $wgReadOnly, $wgDBClusterTimeout; $fname = 'LoadBalancer::getReaderIndex'; wfProfileIn($fname); $i = false; if ($this->mForce >= 0) { $i = $this->mForce; } else { if ($this->mReadIndex >= 0) { $i = $this->mReadIndex; } else { # $loads is $this->mLoads except with elements knocked out if they # don't work $loads = $this->mLoads; $done = false; $totalElapsed = 0; do { if ($wgReadOnly or $this->mAllowLagged) { $i = $this->pickRandom($loads); } else { $i = $this->getRandomNonLagged($loads); if ($i === false && count($loads) != 0) { # All slaves lagged. Switch to read-only mode $wgReadOnly = wfMsgNoDBForContent('readonly_lag'); $i = $this->pickRandom($loads); } } $serverIndex = $i; if ($i !== false) { wfDebugLog('connect', "{$fname}: Using reader #{$i}: {$this->mServers[$i]['host']}...\n"); $this->openConnection($i); if (!$this->isOpen($i)) { wfDebug("{$fname}: Failed\n"); unset($loads[$i]); $sleepTime = 0; } else { $status = $this->mConnections[$i]->getStatus("Thread%"); if (isset($this->mServers[$i]['max threads']) && $status['Threads_running'] > $this->mServers[$i]['max threads']) { # Too much load, back off and wait for a while. # The sleep time is scaled by the number of threads connected, # to produce a roughly constant global poll rate. $sleepTime = self::AVG_STATUS_POLL * $status['Threads_connected']; # If we reach the timeout and exit the loop, don't use it $i = false; } else { $done = true; $sleepTime = 0; } } } else { $sleepTime = 500000; } if ($sleepTime) { $totalElapsed += $sleepTime; $x = "{$this->mServers[$serverIndex]['host']} [{$serverIndex}]"; wfProfileIn("{$fname}-sleep {$x}"); usleep($sleepTime); wfProfileOut("{$fname}-sleep {$x}"); } } while (count($loads) && !$done && $totalElapsed / 1000000.0 < $wgDBClusterTimeout); if ($totalElapsed / 1000000.0 >= $wgDBClusterTimeout) { $this->mErrorConnection = false; $this->mLastError = 'All servers busy'; } if ($i !== false && $this->isOpen($i)) { # Wait for the session master pos for a short time if ($this->mWaitForFile) { if (!$this->doWait($i)) { $this->mServers[$i]['slave pos'] = $this->mConnections[$i]->getSlavePos(); } } if ($i !== false) { $this->mReadIndex = $i; } } else { $i = false; } } } wfProfileOut($fname); return $i; }