Commit de475202 authored by Jeremy's avatar Jeremy Committed by Jeremy

Issue #332094 by Jeremy, nirl, markpavlitski: Add support for server weight

parent 666a4601
......@@ -102,6 +102,15 @@ to $conf; memcache_servers and memcache_bins. The arrays follow this pattern:
binS => clusterS
)
You can optionally assign a weight to each server, favoring one server more than
another. For example, to make it 10 times more likely to store an item on
server1 versus server2:
'memcache_servers' => array(
server1:port => array('cluster' => cluster1, 'weight' => 10),
server2:port => array('cluster' => cluster2, 'weight' => 1'),
)
The bin/cluster/server model can be described as follows:
- Servers are memcached instances identified by host:port.
......
......@@ -579,7 +579,17 @@ function dmemcache_stats($stats_bin = 'cache', $stats_type = 'default', $aggrega
// passed to the Memcache memcache_get_extended_stats() function.
elseif ($mc instanceof Memcache) {
if ($stats_type == 'default' || $stats_type == '') {
$stats[$bin] = $mc->getExtendedStats();
$rc = $mc->getExtendedStats();
if (is_array($rc)) {
foreach ($rc as $server => $data) {
if (empty($data)) {
unset($rc[$server]);
}
}
if (!empty($rc)) {
$stats[$bin] = $rc;
}
}
}
// If $slab isn't zero, then we are dumping the contents of a
// specific cache slab.
......@@ -777,14 +787,13 @@ function dmemcache_instance($bin = 'cache') {
* @param string $server
* A server string of the format "localhost:11211" or
* "unix:///path/to/socket".
* @param bool $connection
* TRUE or FALSE, whether the $memcache instance already has at least one
* open connection.
* @param integer $weight
* Weighted probability of talking to this server.
*
* @return bool
* TRUE or FALSE if connection was successful.
*/
function dmemcache_connect($memcache, $server, $connection) {
function dmemcache_connect($memcache, $server, $weight) {
static $memcache_persistent = NULL;
$extension = dmemcache_extension();
......@@ -798,8 +807,8 @@ function dmemcache_connect($memcache, $server, $connection) {
$memcache_persistent = variable_get('memcache_persistent', TRUE);
}
$port_error = FALSE;
if ($extension == 'Memcache') {
// Support unix sockets of the format 'unix:///path/to/socket'.
if ($host == 'unix') {
// Use full protocol and path as expected by Memcache extension.
......@@ -807,31 +816,7 @@ function dmemcache_connect($memcache, $server, $connection) {
$port = 0;
}
else if (!isset($port)) {
register_shutdown_function('watchdog', 'memcache', 'You have specified an invalid address of "!server" in settings.php which does not include a port. Please review README.txt for proper configuration. You must specify both a server address and port such as "!ip" or "!host", or a unix socket such as "!socket".', array('!server' => $server, '!ip' => t('127.0.0.1:11211'), '!host' => t('localhost:11211'), '!socket' => t('unix:///path/to/socket')), WATCHDOG_WARNING);
}
// When using the PECL memcache extension, we must use ->(p)connect
// for the first connection.
if (!$connection) {
$track_errors = ini_set('track_errors', '1');
$php_errormsg = '';
// The Memcache extension requires us to use (p)connect for the first
// server we connect to.
if ($memcache_persistent) {
$rc = @$memcache->pconnect($host, $port);
}
else {
$rc = @$memcache->connect($host, $port);
}
if (!empty($php_errormsg)) {
register_shutdown_function('watchdog', 'memcache', 'Exception caught in dmemcache_connect while connecting to !host:!port: !msg', array('!host' => $host, '!port' => $port, '!msg' => $php_errormsg), WATCHDOG_WARNING);
$php_errormsg = '';
}
ini_set('track_errors', $track_errors);
}
else {
$rc = $memcache->addServer($host, $port, $memcache_persistent);
$port_error = TRUE;
}
}
elseif ($extension == 'Memcached') {
......@@ -842,27 +827,34 @@ function dmemcache_connect($memcache, $server, $connection) {
$port = 0;
}
else if (!isset($port)) {
register_shutdown_function('watchdog', 'memcache', 'You have specified an invalid address of "!server" in settings.php which does not include a port. Please review README.txt for proper configuration. You must specify both a server address and port such as "!ip" or "!host", or a unix socket such as "!socket".', array('!server' => $server, '!ip' => t('127.0.0.1:11211'), '!host' => t('localhost:11211'), '!socket' => t('unix:///path/to/socket')), WATCHDOG_WARNING);
$port_error = TRUE;
}
}
if ($port_error) {
register_shutdown_function('watchdog', 'memcache', 'You have specified an invalid address of "!server" in settings.php which does not include a port. Please review README.txt for proper configuration. You must specify both a server address and port such as "!ip" or "!host", or a unix socket such as "!socket".', array('!server' => $server, '!ip' => t('127.0.0.1:11211'), '!host' => t('localhost:11211'), '!socket' => t('unix:///path/to/socket')), WATCHDOG_WARNING);
}
if ($extension == 'Memcache') {
$rc = $memcache->addServer($host, $port, $memcache_persistent, $weight);
}
elseif ($extension == 'Memcached') {
$match = FALSE;
if ($memcache_persistent) {
$servers = $memcache->getServerList();
$match = FALSE;
foreach ($servers as $s) {
if ($s['host'] == $host && $s['port'] == $port) {
$match = TRUE;
break;
}
}
if (!$match) {
$rc = $memcache->addServer($host, $port);
}
else {
$rc = TRUE;
}
}
else {
if (!$match) {
$rc = $memcache->addServer($host, $port);
}
else {
$rc = TRUE;
}
}
else {
$rc = FALSE;
......@@ -946,22 +938,23 @@ function dmemcache_object($bin = NULL, $flush = FALSE) {
// Track whether or not we've opened any memcache connections.
$connection = FALSE;
// Link all the servers to this cluster.
foreach ($memcache_servers as $server => $c) {
if ($c == $cluster && !isset($failed_connections[$server])) {
$rc = dmemcache_connect($memcache, $server, $connection);
if ($rc !== FALSE) {
// We've made at least one successful connection.
$connection = TRUE;
}
else {
// Memcache connection failure. We can't log to watchdog directly
// because we're in an early Drupal bootstrap phase where watchdog
// is non-functional. Instead, register a shutdown handler so it
// gets recorded at the end of the page load.
register_shutdown_function('watchdog', 'memcache', 'Failed to connect to memcache server: !server', array('!server' => $server), WATCHDOG_ERROR);
$failed_connections[$server] = FALSE;
foreach ($memcache_servers as $server => $b) {
if ($c = dmemcache_object_cluster($b)) {
if ($c['cluster'] == $cluster && !isset($failed_connections[$server])) {
$rc = dmemcache_connect($memcache, $server, $c['weight'], $connection);
if ($rc) {
// We've made at least one connection.
$connection = TRUE;
}
else {
// Memcache connection failure. We can't log to watchdog directly
// because we're in an early Drupal bootstrap phase where watchdog
// is non-functional. Instead, register a shutdown handler so it
// gets recorded at the end of the page load.
register_shutdown_function('watchdog', 'memcache', 'Failed to connect to memcache server: !server', array('!server' => $server), WATCHDOG_ERROR);
$failed_connections[$server] = FALSE;
}
}
}
}
......@@ -984,6 +977,29 @@ function dmemcache_object($bin = NULL, $flush = FALSE) {
return empty($memcache_cache[$bin]) ? FALSE : $memcache_cache[$bin];
}
/**
* Ensure that we're working with a proper cluster array.
*/
function dmemcache_object_cluster($cluster) {
if (!is_array($cluster)) {
// Set defaults.
$cluster = array(
'cluster' => $cluster,
'weight' => 1,
);
}
if (!isset($cluster['cluster']) || !is_string($cluster['cluster'])) {
// Cluster is required, complain if it's missing or invalid.
register_shutdown_function('watchdog', 'memcache', 'Ignoring invalid or missing cluster definition, review your memcache_servers configuration.', array(), WATCHDOG_ERROR);
return FALSE;
}
if (!isset($cluster['weight']) || !is_int($cluster['weight']) || $cluster['weight'] < 1) {
// Weight is optional.
$cluster['weight'] = 1;
}
return $cluster;
}
/**
* Prefixes a key and ensures it is url safe.
*
......
......@@ -32,6 +32,17 @@ function lock_initialize() {
function lock_acquire($name, $timeout = 30) {
global $locks;
// Special case variable_init, as on memcache errors we can get stuck in an
// infinite loop.
static $variable_init = 0;
if ($name == 'variable_init') {
if ($variable_init > 25) {
register_shutdown_function('watchdog', 'memcache', 'Broke out of loop trying to grab lock for variable_init.');
return TRUE;
}
$variable_init++;
}
// Ensure that the timeout is at least 1 sec. This is a limitation
// imposed by memcached.
$timeout = (int) max($timeout, 1);
......
......@@ -12,7 +12,11 @@ require_once dirname(__FILE__) . '/dmemcache.inc';
// @todo get rid of this conditional include as soon as this is done:
// http://drupal.org/node/1225404
$lock_file = dirname(__FILE__) . '/memcache-lock-code.inc';
if (!dmemcache_object('semaphore')) {
$mc = dmemcache_object('semaphore');
// dmemcache_object always returns TRUE, we don't need these stats but it forces
// us to try and connect to memcache. If this fails, we can't store locks in
// memcache.
if (!$mc->getStats()) {
$lock_file = DRUPAL_ROOT . '/includes/lock.inc';
}
require_once $lock_file;
......@@ -31,13 +31,15 @@ function memcache_enable() {
// Make a test connection to all configured memcache servers.
$memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
foreach ($memcache_servers as $server => $bin) {
$memcache = dmemcache_instance($bin);
if (dmemcache_connect($memcache, $server, FALSE) === FALSE) {
$error = TRUE;
}
else {
if (!variable_get('memcache_persistent', TRUE)) {
dmemcache_close($memcache);
if ($cluster = dmemcache_object_cluster($bin)) {
$memcache = dmemcache_instance($cluster['cluster']);
if (dmemcache_connect($memcache, $server, $cluster['weight']) === FALSE) {
$error = TRUE;
}
else {
if (!variable_get('memcache_persistent', TRUE)) {
dmemcache_close($memcache);
}
}
}
}
......@@ -114,13 +116,15 @@ function memcache_requirements($phase) {
// Make a test connection to all configured memcache servers.
$memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
foreach ($memcache_servers as $server => $bin) {
$memcache = dmemcache_instance($bin);
if (dmemcache_connect($memcache, $server, FALSE) === FALSE) {
$errors[] = $t('Failed to connect to memcached server instance at %server.', array('%server' => $server));
}
else {
if (!variable_get('memcache_persistent', TRUE)) {
dmemcache_close($memcache);
if ($cluster = dmemcache_object_cluster($bin)) {
$memcache = dmemcache_instance($cluster['cluster']);
if (dmemcache_connect($memcache, $server, $cluster['weight']) === FALSE) {
$errors[] = $t('Failed to connect to memcached server instance at %server.', array('%server' => $server));
}
else {
if (!variable_get('memcache_persistent', TRUE)) {
dmemcache_close($memcache);
}
}
}
}
......
......@@ -59,9 +59,11 @@ function memcache_admin_menu() {
);
$memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
$clusters = array();
foreach ($memcache_servers as $server => $cluster) {
$clusters[$cluster]['servers'][] = $server;
$clusters[$cluster]['bin'] = _memcache_admin_get_bin_for_cluster($cluster);
foreach ($memcache_servers as $server => $bin) {
if ($cluster = dmemcache_object_cluster($bin)) {
$clusters[$cluster]['servers'][] = $server;
$clusters[$cluster]['bin'] = _memcache_admin_get_bin_for_cluster($cluster['cluster']);
}
}
$count = 0;
......@@ -272,47 +274,45 @@ function memcache_admin_stats($bin = 'default') {
$output = '';
$server = array();
$stats = dmemcache_stats($bin, 'default', TRUE);
if (empty($stats)) {
if (empty($stats[$bin])) {
// Failed to load statistics. Provide a useful error about where to get
// more information and help.
drupal_set_message(t('There may be a problem with your Memcache configuration. Please review %readme and !more for more information.', array('%readme' => 'README.txt', 'admin/reports/status', '!more' => module_exists('memcache') ? t('visit the Drupal admin !status page', array('!status' => l(t('status report'), 'admin/reports/status'))) : t('!enable the memcache module', array('!enable' => l(t('enable'), 'admin/modules', array('fragment' => 'edit-modules-performance-and-scalability')))))), 'error');
drupal_set_message(t('Failed to retreive statistics. There may be a problem with your Memcache configuration. Please review %readme and !more for more information.', array('%readme' => 'README.txt', 'admin/reports/status', '!more' => module_exists('memcache') ? t('visit the Drupal admin !status page', array('!status' => l(t('status report'), 'admin/reports/status'))) : t('!enable the memcache module', array('!enable' => l(t('enable'), 'admin/modules', array('fragment' => 'edit-modules-performance-and-scalability')))))), 'error');
}
else {
if (count($stats[$bin])) {
$stats = $stats[$bin];
$aggregate = array_pop($stats);
$mc = dmemcache_object($bin);
if ($mc instanceof Memcached) {
$version = t('Memcached v!version', array('!version' => phpversion('Memcached')));
}
elseif ($mc instanceof Memcache) {
$version = t('Memcache v!version', array('!version' => phpversion('Memcache')));
$stats = $stats[$bin];
$aggregate = array_pop($stats);
$mc = dmemcache_object($bin);
if ($mc instanceof Memcached) {
$version = t('Memcached v!version', array('!version' => phpversion('Memcached')));
}
elseif ($mc instanceof Memcache) {
$version = t('Memcache v!version', array('!version' => phpversion('Memcache')));
}
else {
$version = t('Unknown');
drupal_set_message(t('Failed to detect the memcache PECL extension.'), 'error');
}
$memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
foreach ($stats as $server => $statistics) {
if (empty($statistics['uptime'])) {
drupal_set_message(t('Failed to connect to server at %address.', array('%address' => $server)), 'error');
}
else {
$version = t('Unknown');
drupal_set_message(t('Failed to detect the memcache PECL extension.'), 'error');
}
$memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
foreach ($stats as $server => $statistics) {
if (empty($statistics['uptime'])) {
drupal_set_message(t('Failed to connect to server at %address.', array('%address' => $server)), 'error');
}
else {
$servers[] = $server;
$data['server_overview'][$server] = t('v!version running !uptime', array('!version' => check_plain($statistics['version']), '!uptime' => format_interval($statistics['uptime'])));
$data['server_pecl'][$server] = t('n/a');
$data['server_serialize'][$server] = t('n/a');
$data['server_time'][$server] = format_date($statistics['time']);
$data['server_connections'][$server] = _memcache_admin_stats_connections($statistics);
$data['cache_sets'][$server] = _memcache_admin_stats_sets($statistics);
$data['cache_gets'][$server] = _memcache_admin_stats_gets($statistics);
$data['cache_counters'][$server] = _memcache_admin_stats_counters($statistics);
$data['cache_transfer'][$server] = _memcache_admin_stats_transfer($statistics);
$data['cache_average'][$server] = _memcache_admin_stats_average($statistics);
$data['memory_available'][$server] = _memcache_admin_stats_memory($statistics);
$data['memory_evictions'][$server] = number_format($statistics['evictions']);
}
$servers[] = $server;
$data['server_overview'][$server] = t('v!version running !uptime', array('!version' => check_plain($statistics['version']), '!uptime' => format_interval($statistics['uptime'])));
$data['server_pecl'][$server] = t('n/a');
$data['server_serialize'][$server] = t('n/a');
$data['server_time'][$server] = format_date($statistics['time']);
$data['server_connections'][$server] = _memcache_admin_stats_connections($statistics);
$data['cache_sets'][$server] = _memcache_admin_stats_sets($statistics);
$data['cache_gets'][$server] = _memcache_admin_stats_gets($statistics);
$data['cache_counters'][$server] = _memcache_admin_stats_counters($statistics);
$data['cache_transfer'][$server] = _memcache_admin_stats_transfer($statistics);
$data['cache_average'][$server] = _memcache_admin_stats_average($statistics);
$data['memory_available'][$server] = _memcache_admin_stats_memory($statistics);
$data['memory_evictions'][$server] = number_format($statistics['evictions']);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment