drop-in-gaming/scalingwpdb

LudicrousDB 是一个数据库类,它支持复制、故障转移、负载均衡和分区,基于Automattic的HyperDB插件。

6.0.8 2023-10-23 18:45 UTC

README

LudicrousDB 是一个针对WordPress的高级数据库接口,支持复制、故障转移、负载均衡和分区,基于Automattic的HyperDB插件。

安装

文件

将主 ludicrousdb 插件文件夹及其内容复制到以下位置之一:

  • wp-content/plugins/ludicrousdb/
  • wp-content/mu-plugins/ludicrousdb/

这都无关紧要;LudicrousDB会自动识别。文件夹名称应正好为ludicrousdb。从GitHub下载ZIP文件并解压时请小心。

插件

WordPress 支持一些“即插即用”风格的插件,用于高级覆盖一些特定功能。

LudicrousDB 包含3个基本的数据库插件

  • db.php <-> wp-content/db.php - 替换 $wpdb 对象的自举文件
  • db-error.php <-> wp-content/db-error.php - 严重数据库错误输出到用户的端点
  • db-config.php <-> ABSPATH/db-config.php - 用于配置数据库环境

您可能需要将这些文件复制到相应的位置,并在熟悉它们的功能和工作方式后进行修改。

配置

LudicrousDB 可以管理连接到大量数据库的连接。通过将表名映射到数据集,查询被分配到适当的服务器。

数据集被定义为位于同一数据库中的表组。可能存在具有相同名称但包含不同服务器的不同数据库。还可能在不同的服务器上有数据库的多个副本。术语“数据集”消除了任何歧义。将数据集视为可以镜像到多个服务器的表组。

配置LudicrousDB涉及定义数据库和数据集。定义数据库涉及指定服务器连接详情、包含的数据集以及其读写能力和优先级。定义数据集涉及指定其确切的表名或注册一个或多个将表名转换为数据集的回调函数。

示例配置1:默认服务器

这是使用仅所需参数(主机、用户、密码、名称)将服务器添加到LudicrousDB的最基本方式。这将在'global'数据集中将wp-config.php中定义的数据库添加为读写服务器。(默认情况下,每个表都在'global'中。)

$wpdb->add_database( array(
	'host'     => DB_HOST,     // If port is other than 3306, use host:port.
	'user'     => DB_USER,
	'password' => DB_PASSWORD,
	'name'     => DB_NAME,
) );

再次添加相同的服务器,这次它被配置为从属服务器。最后三个参数设置为默认值,但显示以供参考。

$wpdb->add_database( array(
	'host'     => DB_HOST,     // If port is other than 3306, use host:port.
	'user'     => DB_USER,
	'password' => DB_PASSWORD,
	'name'     => DB_NAME,
	'write'    => 0,
	'read'     => 1,
	'dataset'  => 'global',
	'timeout'  => 0.2,
) );

示例配置2:分区

此示例显示了一个设置,其中多站点博客表已从全局数据集中分离出来。

$wpdb->add_database( array(
	'host'     => 'global.db.example.com',
	'user'     => 'globaluser',
	'password' => 'globalpassword',
	'name'     => 'globaldb',
) );

$wpdb->add_database( array(
	'host'     => 'blog.db.example.com',
	'user'     => 'bloguser',
	'password' => 'blogpassword',
	'name'     => 'blogdb',
	'dataset'  => 'blog',
) );

$wpdb->add_callback( 'my_db_callback' );

// Multisite blog tables are "{$base_prefix}{$blog_id}_*"
function my_db_callback( $query, $wpdb ) {
	if ( preg_match("/^{$wpdb->base_prefix}\d+_/i", $wpdb->table) ) {
		return 'blog';
	}
}

配置函数

add_database()

$wpdb->add_database( $database );

$database 是一个包含以下参数的关联数组

host          (required) Hostname with optional :port. Default port is 3306.
user          (required) MySQL user name.
password      (required) MySQL user password.
name          (required) MySQL database name.
read          (optional) Whether server is readable. Default is 1 (readable).
                         Also used to assign preference. See "Network topology".
write         (optional) Whether server is writable. Default is 1 (writable).
                         Also used to assign preference in multi-master mode.
dataset       (optional) Name of dataset. Default is 'global'.
timeout       (optional) Seconds to wait for TCP responsiveness. Default is 0.2
lag_threshold (optional) The minimum lag on a slave in seconds before we consider it lagged.
                         Set null to disable. When not set, the value of $wpdb->default_lag_threshold is used.

add_table()

$wpdb->add_table( $dataset, $table );

$dataset$table 是字符串。

add_callback()

$wpdb->add_callback( $callback, $callback_group = 'dataset' );

$callback 是一个可调用的函数或方法。$callback_group 是此 $callback 所属的回调组。

回调将按它们注册的顺序执行,直到其中一个返回非null值。

默认的 $callback_group 是 'dataset'。此组中的回调将用两个参数调用,并期望计算数据集或返回null。

$dataset = $callback($table, &$wpdb);

任何评估为false的值都将导致查询终止。

对于更复杂的设置,可以使用回调函数来覆盖$wpdb属性或LudicrousDB::connect_db()内部的变量。如果回调函数返回一个数组,LudicrousDB将提取该数组。该数组应为关联数组,并包含一个与使用$wpdb->add_database()添加的数据库相对应的$dataset值。它还可以包含$server,这将在连接前提取以覆盖随机选择的每个数据库服务器的参数。这允许您动态更改参数,如主机、用户、密码、数据库名称、延迟阈值和TCP检查超时。

主从数据库

数据库定义可以包括'读取'和'写入'参数。它们作为布尔开关运行,但通常指定为整数。它们允许或禁止对数据库进行读取或写入。

主数据库可能被配置为允许读取和写入

'write' => 1,
'read'  => 1,

而从数据库只能允许读取

'write' => 0,
'read'  => 1,

在某些情况下,如有很多从数据库且主数据库非常繁忙进行写入时,可能有益于禁止从主数据库读取。

  'write' => 1,
  'read'  => 0,

LudicrousDB跟踪自实例化以来已写入的表,并将后续的读取查询发送到接收写入查询的同服务器。因此,这种方式设置的主数据库仍然会收到读取查询,但只有在写入之后。

网络拓扑/数据中心感知

当您的数据库位于不同的物理位置时,通常连接到附近的服务器比连接到更远的服务器更有优势。可以使用读取和写入参数将服务器放入更偏好或更不偏好的连接的逻辑组。数字越小表示偏好度越高。

此配置指示LudicrousDB随机尝试从本地从数据库中读取。如果该从数据库不可达或拒绝连接,则尝试其他从数据库,然后是主数据库,最后是按随机顺序尝试远程从数据库。

Local slave 1:   'write' => 0, 'read' => 1,
Local slave 2:   'write' => 0, 'read' => 1,
Local master:    'write' => 1, 'read' => 2,
Remote slave 1:  'write' => 0, 'read' => 3,
Remote slave 2:  'write' => 0, 'read' => 3,

在另一个数据中心,主数据库将是远程的。在决定发送读取操作的位置时,我们将考虑这一点。写入操作始终发送到主数据库,无论其位置如何。

Local slave 1:   'write' => 0, 'read' => 1,
Local slave 2:   'write' => 0, 'read' => 1,
Remote slave 1:  'write' => 0, 'read' => 2,
Remote slave 2:  'write' => 0, 'read' => 2,
Remote master:   'write' => 1, 'read' => 3,

有多种方式在不同的位置实现不同的配置。您可以使用不同的配置文件。您还可以编写代码来发现Web服务器的位置,例如通过检查$_SERVERphp_uname(),并据此计算读取/写入参数。

从数据库延迟感知

LudicrousDB通过根据定义的延迟阈值做出决策,来适应从数据库的延迟。如果没有设置延迟阈值,它将忽略从数据库的延迟。否则,它将尝试连接到一个非延迟的从数据库,然后再连接到一个延迟的从数据库。

如果一个从数据库的复制延迟大于您在$wpdb->default_lag_threshold或每个数据库设置中定义的延迟阈值,则认为该从数据库是延迟的。您还可以通过在'dataset'组回调中返回$server['lag_threshold']变量来重写延迟阈值。

LudicrousDB不会检查从数据库的延迟。您必须定义两个回调函数来执行此操作

$wpdb->add_callback( $callback, 'get_lag_cache' );

$wpdb->add_callback( $callback, 'get_lag' );

第一个在连接到从数据库之前被调用,应返回以秒为单位的复制延迟或如果未知则返回false,基于$wpdb->lag_cache_key

第二个回调在建立从数据库连接后被调用。它应返回复制延迟或如果未知则返回false,基于$wpdb->dbhs[ $wpdb->dbhname ]中的连接。

样本复制延迟检测配置。

要检测复制延迟,尝试mk-heartbeat: (http://www.maatkit.org/doc/mk-heartbeat.html)

此实现需要数据库用户具有对heartbeat表的读取访问权限。

缓存使用共享内存以提高可移植性。可以修改为与Memcached、APC等一起工作。

$wpdb->lag_cache_ttl = 30;
$wpdb->shmem_key = ftok( __FILE__, "Y" );
$wpdb->shmem_size = 128 * 1024;

$wpdb->add_callback( 'get_lag_cache', 'get_lag_cache' );
$wpdb->add_callback( 'get_lag',       'get_lag' );

function get_lag_cache( $wpdb ) {
	$segment = shm_attach( $wpdb->shmem_key, $wpdb->shmem_size, 0600 );
	$lag_data = @shm_get_var( $segment, 0 );
	shm_detach( $segment );

	if ( !is_array( $lag_data ) || !is_array( $lag_data[ $wpdb->lag_cache_key ] ) )
		return false;

	if ( $wpdb->lag_cache_ttl < time() - $lag_data[ $wpdb->lag_cache_key ][ 'timestamp' ] )
		return false;

	return $lag_data[ $wpdb->lag_cache_key ][ 'lag' ];
}

function get_lag( $wpdb ) {
	$dbh = $wpdb->dbhs[ $wpdb->dbhname ];

	if ( !mysql_select_db( 'heartbeat', $dbh ) )
		return false;

	$result = mysql_query( "SELECT UNIX_TIMESTAMP() - UNIX_TIMESTAMP(ts) AS lag FROM heartbeat LIMIT 1", $dbh );

	if ( !$result || false === $row = mysql_fetch_assoc( $result ) )
		return false;

	// Cache the result in shared memory with timestamp
	$sem_id = sem_get( $wpdb->shmem_key, 1, 0600, 1 ) ;
	sem_acquire( $sem_id );
	$segment = shm_attach( $wpdb->shmem_key, $wpdb->shmem_size, 0600 );
	$lag_data = @shm_get_var( $segment, 0 );

	if ( !is_array( $lag_data ) )
		$lag_data = array();

	$lag_data[ $wpdb->lag_cache_key ] = array( 'timestamp' => time(), 'lag' => $row[ 'lag' ] );
	shm_put_var( $segment, 0, $lag_data );
	shm_detach( $segment );
	sem_release( $sem_id );

	return $row[ 'lag' ];
}