Note: 版本需求
服务级别是从 1.2.0-alpha版本引入的,mysqlnd_ms_set_qos() 从 PHP 5.4.0 版本开始可以使用。
不同类型的 MySQL 群组提供了,不同的服务和数据一致性级别。异步的 MySQL 主从同步 提供最终的数据一致性,一个读操作是否能够得到当前的数据、状态,一类与 slave 是否已经从 master 获取了最后的更新。
使用 MySQL 主从同步依赖于网络的有效性,最终会获得数据的一致性。然而, 状态数据是不能同步的。这样,只有指定的 slave 或者 master 连接才能得到所有内容。
从 1.2.0 版本开始,插件能够自动的进行 MySQL 主从同步的节点,来完成 session 一致性 或者完成很强的一致性要求。session 一致性是指一个客户端可以读取他的写入内容, 其他客户端可能不能看到他的写入内容。很强的一致性要求是指所有客户端都能够看到 其他所有客户端的写入内容。
Example #1 session 一致性:读取写入内容
{ "myapp": { "master": { "master_0": { "host": "localhost", "socket": "\/tmp\/mysql.sock" } }, "slave": { "slave_0": { "host": "127.0.0.1", "port": "3306" } } } }
Example #2 Requesting session consistency
<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
if (!$mysqli)
/* Of course, your error handling is nicer... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
/* 使用 master 完成读写分离 */
if (!$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")) {
/* Please use better error handling in your code */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* 要求 session 一致性,读取写入内容 */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
/* 插件选择一个改变数据的节点,这里是 master */
if (!$res = $mysqli->query("SELECT item FROM orders WHERE order_id = 1"))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
var_dump($res->fetch_assoc());
/* 返回到最终数据一致性状态,允许陈旧数据 */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
/* 插件选择任何一个 slaver 完成允许陈旧数据的读取 */
if (!$res = $mysqli->query("SELECT item, price FROM specials"))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
?>
服务级别可以被写在插件的配置文件中,也可以在运行时使用 mysqlnd_ms_set_qos() 设定。 在范例中,使用这个函数强制 session 一致性,直到再次通知改变。 orders 表单中的 SELECT 语句在前面写入使用的 连接中执行。读写分离逻辑被服务级别策略改变。
在从 orders 表单读取数据以后,恢复到默认的服务级别 (最终数据一致性)。 这时,语句执行选择的服务器将不再被限制,因而在 specials 表单上做的 SELECT 查询将在一个 slave 服务器中进行。
一个新的替代 SQL hint的功能,master_on_write 配置设定。 在绝大部分情况下 mysqlnd_ms_set_qos() 更容易使用, 使用它移植性更好。
Example #3 Maximum age/slave lag
{ "myapp": { "master": { "master_0": { "host": "localhost", "socket": "\/tmp\/mysql.sock" } }, "slave": { "slave_0": { "host": "127.0.0.1", "port": "3306" } }, "failover" : "master" } }
Example #4 限制 slave 延迟
<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
if (!$mysqli)
/* Of course, your error handling is nicer... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
/* 若 slave 延迟不超过 4 秒,则从 Slave 读取 */
$ret = mysqlnd_ms_set_qos($mysqli,
MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL,
MYSQLND_MS_QOS_OPTION_AGE, 4);
if (!$ret)
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
/* 选择一个 slave,他可能没有改变 */
if (!$res = $mysqli->query("SELECT item, price FROM daytrade"))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
/* 恢复默认状态,使用所有的 slave 和 master */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
?>
最终一致性服务级别可以使用一个可选的参数设定最大允许的延迟,用于选择 slave。 如果设定这个值,插件会检查所有 slave 的 SHOW SLAVE STATUS。 在范例中,只有满足 Slave_IO_Running=Yes, Slave_SQL_Running=Yes 和 Seconds_Behind_Master <= 4 的 slave 会被执行语句 SELECT item, price FROM daytrade。
在应用运行时,会透明的执行 SHOW SLAVE STATUS 命令。 任何错误会以 warning 的方式报警,但是错误信息不会被保存在连接中。 即使所有的 SHOW SLAVE STATUS 都失败了,用户的执行请求也不会被终止, 给定的 master 作为最后的选择。然而应用不需要做任何调整。
Note: 耗时和缓慢的操作
在任何程序的开始,对所有的 slave 进行 SHOW SLAVE STATUS 查询,是一个非常耗时和缓慢的操作。不要经常这样操作。MySQL 主从同步集群并没有 提供一个客户端从一个中心控制器获取备选方案的能力。 然而,没有更多有效的方式获取 slave 延迟。
请注意,关于 SHOW SLAVE STATUS 的各种限制和参数说明, 请参考 MySQl 的参考手册。
若要禁止插件,在没有找到满足延迟条件的 slave 时产生报警,需要在配置文件 当中设定 master 作为故障处理。如果没有 slave 满足条件,那么故障处理开始启动, 插件会使用 master 去执行语句。
如果没有 slave 满足条件,并且没有启动故障处理,插件将会报警。 这时,语句不会被执行,并且错误信息会被写入连接当中。
Example #5 不设置故障处理
{ "myapp": { "master": { "master_0": { "host": "localhost", "socket": "\/tmp\/mysql.sock" } }, "slave": { "slave_0": { "host": "127.0.0.1", "port": "3306" } } } }
Example #6 No slave within time limit
<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
if (!$mysqli)
/* Of course, your error handling is nicer... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
/* 若 slave 延迟不超过 4 秒,则从 slave 执行 */
$ret = mysqlnd_ms_set_qos($mysqli,
MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL,
MYSQLND_MS_QOS_OPTION_AGE, 4);
if (!$ret)
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
/* Plugin picks any slave, which may or may not have the changes */
if (!$res = $mysqli->query("SELECT item, price FROM daytrade"))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
/* Back to default: use of all slaves and masters permitted */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
?>
以上例程会输出:
PHP Warning: mysqli::query(): (mysqlnd_ms) Couldn't find the appropriate slave connection. 0 slaves to choose from. Something is wrong in %s on line %d PHP Warning: mysqli::query(): (mysqlnd_ms) No connection selected by the last filter in %s on line %d [2000] (mysqlnd_ms) No connection selected by the last filter