阻止proxysql写组(writer group)中有多个server
现状
大家都知道,proxysql根据mysql中的read_only
属性来判断server是否允许写入,即:
- 当
read_only=0|OFF
时,说明server允许写入,把它放到writer group
中; - 当
read_only=1|ON
时,说明server是只读,确保它在reader's group
中。
对于第二条是没有问题的,但第一条很有风险,因为万一DBA忘记了启用从库的read_only
则会导致多点写入,这会造成主从数据不一致,后果非常严重。正因为如此,对于proxysql的使用,往往不敢启用read_only
check(即不配置表:mysql_replication_hostgroups
)。
改进思路
我们可不可以改造一下这个逻辑呢?
当发现一个节点的read_only=0|OFF
时,判断一下当前的写组(writer group)中是否已经存在一个ONLINE
server,如果是,则不把这个节点移动到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
表的各种操作。我们需要新增一条判断SQL1
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
62019-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考虑的比较周全。
- master宕机,proxysql会探测到并将其屏蔽。
- failover脚本探测到master宕机后,判断candidate_master的slave线程是否应用完所有的binlog。可根据GTID executed 和GTID retrived 来判断或者根据slave 的SQL thread是否running来判断。
- 若candidate_master的binlog已全部应用,failover脚本修改
read_only=0
,重置slave,并配置其它slave 为该server的slave - proxysql 探测到read_only变化,将它放到writer group中,此时可正常对外提供服务。
即使故障master恢复后,其read_only=0, proxysql也不会把它放到writer group中了,因为writer group已经有了一个server.