stuttter / ludicrousdb
LudicrousDB 是一个数据库类,支持复制、故障转移、负载均衡和分区,基于Automattic的HyperDB插件。
Requires
- php: >=5.2
- composer/installers: ~1.0 || ~2.0
Requires (Dev)
README
LudicrousDB 是一个基于Automattic的HyperDB插件的WordPress高级数据库接口,支持复制、故障转移、负载均衡和分区。
0. 安装
文件
将主 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
- 用于配置数据库环境
您可能需要将这些文件复制到相应的位置,并在熟悉它们的功能和工作方式后进行修改。
1. 配置
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-primary 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 replica 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);
任何评估为假的值都将导致查询被终止。
对于更复杂的设置,回调函数可以用来覆盖$wpdb
或LudicrousDB::connect_db()
中的变量。如果回调函数返回一个数组,LudicrousDB将提取该数组。它应该是一个关联数组,并应该包含一个与使用$wpdb->add_database()
添加的数据库相对应的$dataset
值。它还可以包含$server
,这将提取以覆盖在连接之前随机选择的每个数据库服务器的参数。这允许您动态地改变参数,例如主机、用户、密码、数据库名称、lag_threshold和TCP检查超时。
2. 主数据库与副本数据库
数据库定义可以包括'read'和'write'参数。它们作为布尔开关操作,但通常指定为整数。它们允许或禁止使用数据库进行读取或写入。
主数据库可能被配置为允许读取和写入
'write' => 1, 'read' => 1,
而副本则只能允许读取
'write' => 0, 'read' => 1,
在有许多副本可用且主数据库非常忙于写入的情况下,禁止从主数据库读取可能是有利的。
'write' => 1, 'read' => 0,
LudicrousDB跟踪自实例化以来已写入的表,并将后续的读取查询发送到接收写入查询的同一服务器。因此,这样设置的主数据库仍然会接收到读取查询,但仅在写入之后。
3. 网络拓扑/数据中心感知
当您的数据库位于不同的物理位置时,连接到附近的服务器而不是更远的服务器通常具有优势。读取和写入参数可以用来将服务器放入更多或更少的优先连接的逻辑组中。较低的数字表示更高的优先级。
此配置指示LudicrousDB尝试从本地副本之一随机读取。如果该副本不可达或拒绝连接,则将尝试其他副本,然后是主数据库,最后以随机顺序尝试远程副本。
Local replica 1: 'write' => 0, 'read' => 1, Local replica 2: 'write' => 0, 'read' => 1, Local primary: 'write' => 1, 'read' => 2, Remote replica 1: 'write' => 0, 'read' => 3, Remote replica 2: 'write' => 0, 'read' => 3,
在另一个数据中心,主数据库将是远程的。我们会在决定将读取发送到何处时考虑这一点。写入始终发送到主数据库,无论距离如何。
Local replica 1: 'write' => 0, 'read' => 1, Local replica 2: 'write' => 0, 'read' => 1, Remote replica 1: 'write' => 0, 'read' => 2, Remote replica 2: 'write' => 0, 'read' => 2, Remote primary: 'write' => 1, 'read' => 3,
有许多方法在不同的位置实现不同的配置。您可以部署不同的配置文件。您还可以编写代码来发现Web服务器的位置,例如通过检查$_SERVER
或php_uname()
,并相应地计算读取/写入参数。
4. 复制延迟
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。
第二个回调在建立到副本的连接之后被调用。它应该返回:复制延迟,或如果未知(基于连接在$wpdb->dbhs[ $wpdb->dbhname ]
)则返回false。
示例复制延迟检测配置
要检测复制延迟,请尝试使用 Percona Toolkit 中的 mk-heartbeat 或 pt-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' ]; }