现状

大家都知道,proxysql根据mysql中的read_only属性来判断server是否允许写入,即:

  1. read_only=0|OFF时,说明server允许写入,把它放到writer group中;
  2. read_only=1|ON时,说明server是只读,确保它在reader's group中。

对于第二条是没有问题的,但第一条很有风险,因为万一DBA忘记了启用从库的read_only则会导致多点写入,这会造成主从数据不一致,后果非常严重。正因为如此,对于proxysql的使用,往往不敢启用read_only check(即不配置表:mysql_replication_hostgroups)。

改进思路

我们可不可以改造一下这个逻辑呢?

当发现一个节点的read_only=0|OFF时,判断一下当前的写组(writer group)中是否已经存在一个ONLINEserver,如果是,则不把这个节点移动到writer group,而是往日志中打一条警告,提示可能是配置错误!

顺着这个逻辑,我捋了一遍源码。

监控read_only的代码是在文件MySQL_Monitor.cpp(L580)中的方法:monitor_read_only_thread(void *arg)实现的。而依据read_only属性,修改主机组的代码是在MySQL_HostGroups_Manager.cpp(L1825)中的void MySQL_HostGroups_Manager::read_only_action(char *hostname, int port, int read_only)实现的。我们需要修改的就是这个方法。

这个方法中定义了5条SQL用于对mysql_server表的各种操作。我们需要新增一条判断SQL

1
const char *Q6=(char *)"select hostname from (select writer_hostgroup from mysql_replication_hostgroups join mysql_servers on reader_hostgroup=hostgroup_id and hostname='%s' and  port=%d) join mysql_servers on hostgroup_id=writer_hostgroup and status=0";

该SQL的作用是判断当前写组中(writer group)是否存在server节点。

在switch 代码段中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    switch (read_only) {
case 0:
if (num_rows==0) {
// the server has read_only=0 , but we can't find any writer, so we perform a swap
// before we perform a swap, check if there's already a server in writer group,if so, we stop swapping
// and print a warning message in the log
cols=0;
error=NULL;
affected_rows=0;
sprintf(query,Q6,hostname,port);

wrlock();
mydb->execute_statement(query,&error,&cols,&affected_rows,&resultset);
wrunlock();
if(resultset==NULL){
goto __exit_read_only_action;
}

int cc = 0;
cc=resultset->rows_count;
delete resultset;
resultset=NULL;
if(cc > 0){
// there's already one server with ONLINE status in the writer's group
proxy_warning("host:%s:%d with read_only=%d doesn't seem to be right. Please pay attention!!!\n",hostname,port,read_only);
break;
}

...

该段代码的作用是,如果该节点的read_only=0, 则判断num_rows是否为0, 如果为0,则说明该节点不在writer group,源代码中接下来的操作是把该节点切换到writer group中。我增加的代码是在把该节点加入writer group前,再次判断writer group中是否存在其它节点,且处于ONLINE状态,如果是,则这个节点的配置很可能有问题。于是停止切换到writer group, 打一条警告信息到日志中。

经过测试,该段代码能达到预期的效果。只是警告日志打的有点多:

1
2
3
4
5
6
2019-04-16 10:53:47 MySQL_HostGroups_Manager.cpp:1886:read_only_action(): [WARNING] host:192.168.216.201:3306 with read_only=0 doesn't seem to be right. Please pay attention!!!
2019-04-16 10:53:49 MySQL_HostGroups_Manager.cpp:1886:read_only_action(): [WARNING] host:192.168.216.201:3306 with read_only=0 doesn't seem to be right. Please pay attention!!!
2019-04-16 10:53:50 MySQL_HostGroups_Manager.cpp:1886:read_only_action(): [WARNING] host:192.168.216.201:3306 with read_only=0 doesn't seem to be right. Please pay attention!!!
2019-04-16 10:53:52 MySQL_HostGroups_Manager.cpp:1886:read_only_action(): [WARNING] host:192.168.216.201:3306 with read_only=0 doesn't seem to be right. Please pay attention!!!
2019-04-16 10:53:53 MySQL_HostGroups_Manager.cpp:1886:read_only_action(): [WARNING] host:192.168.216.201:3306 with read_only=0 doesn't seem to be right. Please pay attention!!!
2019-04-16 10:53:55 MySQL_HostGroups_Manager.cpp:1886:read_only_action(): [WARNING] host:192.168.216.201:3306 with read_only=0 doesn't seem to be right. Please pay attention!!!

但从库启用read_only后,警告日志就不再打出了,这也符合预期。可以配置针对这种日志的监控,以便及时发现。

其它问题:

虽然mysql双主架构使用比较少,但也不是没有,代码这样改动后,proxysql将不再适应双主的架构。另外对于一些兼容了mysql协议的NewSQL具备多点写入的能力,比如TiDB, 这样的话,proxysql会丧失部分灵活性。

但我们可以通过引入一种新的变量来实现,比如mysql-allow_multiple_servers_in_writer_group = 'False',如果该变量为True则允许多个server出现在writer group中,否则writer_group中只能有一个节点。代码怎么改呢? 感兴趣的同行可以研究一下!

优势:

基于这样的一个设计,mysql master failover将会变的非常简单。只需写一个脚本就可以了,当然还是可以使用MHA的,毕竟MHA考虑的比较周全。

  1. master宕机,proxysql会探测到并将其屏蔽。
  2. failover脚本探测到master宕机后,判断candidate_master的slave线程是否应用完所有的binlog。可根据GTID executed 和GTID retrived 来判断或者根据slave 的SQL thread是否running来判断。
  3. 若candidate_master的binlog已全部应用,failover脚本修改read_only=0,重置slave,并配置其它slave 为该server的slave
  4. proxysql 探测到read_only变化,将它放到writer group中,此时可正常对外提供服务。

即使故障master恢复后,其read_only=0, proxysql也不会把它放到writer group中了,因为writer group已经有了一个server.