richardtmiles/scalingwpdb

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

1.0.0 2023-10-04 18:03 UTC

This package is auto-updated.

Last update: 2024-09-04 20:16:43 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 的最基本方法。这将 wp-config.php 中定义的数据库添加为 'global' 数据集的读/写服务器。(默认情况下,每个表都在 '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 属于此 $callback_group

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

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

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

任何评估为假的东西都会导致查询中止。

对于更复杂的设置,回调可以用来覆盖$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' );

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

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

示例复制延迟检测配置。

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

此实现需要数据库用户具有读取心跳表权限。

缓存使用共享内存以提高可移植性。可以修改以与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' ];
}