高可用性

有没有想过你的应用是否该兼容只读模式呢?这个问题有多重要?

MySQL似乎是基于web产品的最主流数据库解决方案。大多典型的互联网应用负载包括大量的读取工作和少量写入工作。当然也有例外,比如MMO游戏(大型多人在线游戏),不过在数量上,通常读取要比写入多得多。所以在数据库架构放弃兼容写入能力的时候,无论是由于传统的MySQL复制拓扑放弃主服务器,还是Galera集群放弃其quorum,为什么要让应用declare总的宕机时间呢?在这个场景中,想象所有刚浏览过应用的用户(未贡献内容):他们并不关心数据库是否能够接受新数据。即便其功能已明显降低,人们还是更愿意访问应用程序本身,而不是看到500错误页面。在一些灾难场景中,想要执行即时恢复(PITR)或者恢复某些宝贵的数据十分花时间:如果至少能让用户读取近期备份,这样会更好一些。

我的设备:设计的应用有能力在部分停机的情况下执行只读操作,并在开发生命周期中测试应用在该模式下的运行状况。我认为这样做的回报很高,将会提高用户对产品可用性的感知。举个例子,查看某些秉持此理念的大型开源项目实施情况,比如MediaWiki,Drupal(还有一些商用产品)。

PXC(Percona XtraDB集群)

话虽如此,我想要强调一个很新同时(个人认为)在这方面十分重要的改进,是从PXC5.6.24版引入Galera复制之后开始的。我同事Stéphane也在今年早些时候于博客里提到过这一点。

专注于数据一致性

你也许知道,Galera的关键优势之一在于它对数据一致性的卓越关注,以及与数据为中心的方法。无论你在集群的哪个部分执行写入,所有节点必须保持数据一致。在你认识到节点间数据不一致所带来的后果时,就会明白它的重要性了。节点数据不一致,可导致诸如因为缺少关键键值行或副本而无法应用writeset的问题,从而造成系统中止与紧急停机。这是因为要从集群中清除受污染的部分,避免“病变”数据蔓延。如果确实出现了这种情况,大多数节点执行紧急中止命令,剩下的少数节点可能丧失集群quorum,而无法再响应用户请求。因此保护数据一致性正是保护系统的可用性。

避免断开大脑

有时候一个节点,或多个节点集群成员无法与其他节点相连同,甚至可能超过半数的节点无法再通讯。连通突然断掉,连从“失踪”节点发个恰当的“告别”信息都不曾。这些节点不知道连通断掉的原因——是被kill掉了吗?还是网络断开了?在这种情况下,节点会declare一个非主要的集群状态,然后进入关闭SQL模式。这是因为集群成员没有(多数)quorum(因此不作为主要组件)而不被信任,它可能包含不一致的数据或者老旧数据。因此是不允许客户端访问的。

有两个原因。首先是无可争议的一点,我们不希望冒着网络断开的风险允许写入操作,与此同时集群的其他部分还作为主要组件存在并持续运行着。我们可能还想拒绝对停机数据的读取,但很有可能架构的其他部分已经有很多更新的信息了。

在标准的MySQL复制过程中,并没有这样的预防措施——如果主-主拓扑的复制被打断的话,两台主服务器仍可接受写入信息,而且都可以继续读取从服务器的数据,无论这样可能造成多少延迟,或者它们是否还与主服务器相连接。但在Galera中,即便只是检测到队列应用中延迟过高(与复制延迟概念相类似),集群都会通过流控制机制中止写入操作。如果像上面那样复制被打断的话,系统甚至会停止读取。

从Galera读取脏数据

对于刚从MySQL复制转到PXC的用户来说,如果你刚好认可数据库的“从服务器”节点可以担负读取任务,即便与“主服务器”断开;再或者你的应用并不依赖写入,而是主要依靠访问现有内容,那么这种行为可能看似过于严格。在这种情况下,你可以或动态开启新的wsrep_dirty_reads变量(每次会话仅需要时),或通过在my.cnf中设置wsrep_dirty_reads = ON(从PXC5.6.26版本后,在配置文件中全局变量可用),设置集群默认运行该选项。

下面的Galera拓扑正是我们在用户网站中常见到的那种,配置WAN位置以通过VPN通讯:

mysql 商用有风险吗 mysql可以商用吗_高可用

我认为这种失败场景正是wsrep\_dirty_reads的最佳使用场合,集群中没有任何部分能够单独执行整体功能,但仍能成功执行客户端的读取请求。

因此,我们来快速看看集群成员在wsrep_dirty_reads选项关闭和开启的情况下是如何表现的(在测试中我屏蔽了4567端口的网络通讯):

percona3 mysql> show status like 'wsrep_cluster_status';
+----------------------+-------------+
| Variable_name        | Value       |
+----------------------+-------------+
| wsrep_cluster_status | non-Primary |
+----------------------+-------------+
1 row in set (0.00 sec)
percona3 mysql> show variables like '%dirty_reads';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| wsrep_dirty_reads | OFF   |
+-------------------+-------+
1 row in set (0.01 sec)
percona3 mysql>  select * from test.g1;
ERROR 1047 (08S01): WSREP has not yet prepared node for application use

而可用时:

percona2 mysql> show status like 'wsrep_cluster_status';
+----------------------+-------------+
| Variable_name        | Value       |
+----------------------+-------------+
| wsrep_cluster_status | non-Primary |
+----------------------+-------------+
1 row in set (0.00 sec)

percona2 mysql> show variables like '%dirty_reads';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| wsrep_dirty_reads | ON    |
+-------------------+-------+
1 row in set (0.00 sec)

percona2 mysql> select * from test.g1;
+----+-------+
| id | a     |
+----+-------+
|  1 | dasda |
|  2 | dasda |
+----+-------+
2 rows in set (0.00 sec)

percona2 mysql> insert into test.g1 set a="bb";
ERROR 1047 (08S01): WSREP has not yet prepared node for application use

MySQL

在传统复制中,或许能够使用从服务器来读取。因此如果主服务器崩溃,同时出于某种原因,MHA或PRM这样的故障工具包并未配置或者也出现了故障,为了让应用继续运行,应当将原本打算与主服务器连通的新连接引导到其中一台从服务器上。如果使用了负载均衡系统,也许只需将从服务器作为写入池主服务器的备份。这样可能在宕机时让用户有更好的体验——至少大家还能使用现有的信息。但上面也强调过,应用需要有这样的准备。

这种实现有些需要注意的地方,因为这种通常用在从服务器上的“只读”模式并非真的100%只读,因为存在“超级”用户这种例外。在这种情况下,在MySQL5.7中新的super\_read_only变量成为了救星(在Percona Server 5.6中可用)。有了这个功能,将与数据库的连接指向某台从服务器就没有风险了,一些特殊用户会修改数据。

如果灾难特别严重,可能需要从大型SQL dump中恢复数据,一般找到足够的空闲服务器来应对这一需求很有难度。值得注意的是,InnoDB有一种特殊的只读模式,就是用于只读媒介,与完整的InnoDB模式相比更为轻量级。

(译者/Vera 责编/钱曙光)