您當前的位置:首頁 > 文化

MGR Arbiter:基於成本最佳化的MySQL Group Replication部署新方案

作者:由 溫正湖 發表于 文化時間:2020-01-06

#### 一、現狀簡介

有關MGR的介紹和優勢之前已經寫過好幾篇文章,本文不再展開,大家有興趣可以檢視專欄中的其他MGR文章做進一步瞭解。在網易內部,MGR已透過RDS金融版服務在雲環境提供,在考拉海購上實現規模使用,例項數200+。在物理機環境上,與DDB一起部署的適配方案也已確定,將在雲音樂等業務場景上使用。

按照目前主流的MGR部署模式,MGR相比傳統的MySQL高可用例項會有一半的成本增加:

MGR Arbiter:基於成本最佳化的MySQL Group Replication部署新方案

上圖所示的傳統高可用例項,使用semi-sync複製,1主1備/從(從庫和備庫表示同樣意思),僅需2個節點。

MGR Arbiter:基於成本最佳化的MySQL Group Replication部署新方案

上圖是MGR的部署模式,由3個節點組成paxos叢集,事務提交時需要至少透過2個節點達成majority後才能成功返回,相比多出1個節點。當然,如果業務還需要進行讀寫分離,那就會再部署至少1個只讀節點,在有隻讀例項的情況下,MGR可以將3個節點中的一個作為只讀節點使用,因此在成本上沒有劣勢,如下圖所示。

MGR Arbiter:基於成本最佳化的MySQL Group Replication部署新方案

但是,並不是所有業務都需要只讀節點的。為了能夠讓MGR在更廣泛的場景上使用,部署成本問題必須得到解決。

#### 二、早期最佳化方案

針對成本問題,其實有很多克服的辦法,關鍵是選擇怎麼樣的方式。在Arbiter方案形成之前,曾希望透過直接減少MGR例項的節點數來解決。

等等,2個節點的MGR?如果掛掉1個節點,僅1個節點存活沒法形成majority,MGR例項會不可寫啊?

###### 可行否

我本來的看法是:

so what,那又怎樣。基於semi-sync的2個節點高可用例項,如果掛掉1個節點(備節點),那麼主節點也是會卡主的(意思是寫事務commit時無法返回,因為需要等待備/從庫回覆表示Binlog已經收到的ack資訊),所以,其實2節點的MGR例項跟2節點的semi-sync例項出現從庫故障時一樣,都需要上層的管控服務(可能是RDS管控,也可以是DBA管控系統)介入,或者透過force_member將MGR存活節點重配置成單節點的MGR例項,或者透過切非同步將卡住的事務順利提交。

應該說,2個節點MGR例項跟基於semi-sync的高可用例項在管理方式上是類似的,同時還具備了正常執行時的流控、故障恢復等能力。

###### 不足之處

2節點MGR方案不足之處在於:對使用async複製的業務來說可用性降低了。因為非同步複製例項,備節點掛掉不影響主節點的讀寫事務正常提交,而2節點MGR做不到,肯定會對業務多多少少造成一些影響。

#### 三、Arbiter節點方案來源

成本最佳化方案,在MGR落地考拉業務時,並沒有拿來重點考慮,因為考拉的目標是跨機房高可用,對服務可用性和資料可靠性要求比一般業務高,透過2節點省成本不是高優先順序問題,所以只是作為 想法存在,並沒有花時間推動實施。

而現在想將MGR在更多的業務上落地,使MGR成為未來MySQL部署一般標準,那麼成本問題就變得最為突出。另外,經過幾個月的思考和沉澱,也對最佳化方案有了更好的設計。下面簡單說下促成Arbiter方案的其他2個有價值的思路來源:

###### 阿里的RDS企業版例項

這是阿里雲在2019年下半年剛推出的MySQL高可用方案。其核心應該是阿里集團內部用的X-Cluster,其相比MGR有個顯著的優勢是支援更多的節點型別,其中之一就是日誌(Log)節點角色,按照公開文件描述,X-Cluster的日誌節點只儲存日誌,不儲存資料,有投票權沒有選舉權,引入日誌節點的目的是為了降低部署成本。

MGR Arbiter:基於成本最佳化的MySQL Group Replication部署新方案

X-Cluster的同城部署三副本能夠方便的實現零資料丟失的例項容災以及機房級容災。 相比主備方式,額外增加了一個日誌節點,換取強一致以及可用性。

之所以要建立不同的型別的副本,還是出於應用需求以及成本控制的考慮。相比傳統的兩節點主備複製,X-Cluster的常規同城部署方式是三節點。

日誌型副本是作為降低部署成本的一種選擇。日誌型副本只儲存日誌,不需要儲存資料,也不需要回放日誌更新資料。因此無論是儲存還是CPU的開銷,日誌型副本相比普通副本都有很大的優勢。在實際應用規劃中,非常適合來當作容災型的節點部署。

這似乎跟我們想要的作用挺類似。不一樣的是,X-Cluster的日誌是異化的Binlog資訊。

###### MongoDB的Arbiter節點角色

MongoDB雖然出現和流行比MySQL晚,但MongoDB在高可用架構上的進化速度相比MySQL快很多,其實應該說是MySQL在這方面太落後了。相信現在沒有人會新建基於Master-Slave高可用的MongoDB例項,複製集/ReplicaSet是MongoDB在生產環境的標準配置,在部署成本這塊,MongoDB提供了[Arbiter角色](Replica Set Arbiter),只不過,MongoDB的Arbiter只有投票功能,連oplog日誌也沒有。

MGR Arbiter:基於成本最佳化的MySQL Group Replication部署新方案

Since the arbiter does not hold a copy of the data, these deployments provides only one complete copy of the data。 Arbiters require fewer resources, at the expense of more limited redundancy and fault tolerance。

However, a deployment with a primary, secondary, and an arbiter ensures that a replica set remains available if the primary or the secondary is unavailable。 If the primary is unavailable, the replica set will elect the secondary to be primary。

對於我們來說,初衷也是想增加一個投票節點,不儲存資料。至於是否有日誌(Binlog),這不是最重要的考慮因素。所以,將角色名取為Arbiter。

#### 四、Arbiter方案可行性

有了想法,那就看看是否有可行性,如果認為可行,那就需要進行原型驗證。這裡,我們要節省的成本主要分為2塊,計算和儲存:計算上儘可能降低記憶體和cpu開銷,儲存上儘可能減少需儲存的資料量。

# 如何不儲存資料?

其實,在MySQL中,一個節點不儲存資料是很容易的,因為MySQL提供了Blackhole儲存引擎,只要將儲存引擎從InnoDB改為Blackhole就能辦到。如下所示:

```

node1>show databases;

+——————————+

| Database |

+——————————+

| information_schema |

| mysql |

| performance_schema |

| sys |

+——————————+

4 rows in set (0。01 sec)

node1>create database hzwenzhh;

Query OK, 1 row affected (0。00 sec)

node1>use hzwenzhh

Database changed

node1>create table t1 (a int primary key, b varchar(10)) engine=blackhole;

Query OK, 0 rows affected (0。00 sec)

node1>insert into t1 values (1,‘10’);

Query OK, 1 row affected (0。00 sec)

node1>select * from t1;

Empty set (0。00 sec)

node1>update t1 set a=2 where a=1;

Query OK, 0 rows affected, 1 warning (0。00 sec)

Rows matched: 0 Changed: 0 Warnings: 1

node1>show warnings;

+————-+————+——————————————————————————————————————————————————————-+

| Level | Code | Message |

+————-+————+——————————————————————————————————————————————————————-+

| Warning | 1870 | Row events are not logged for UPDATE statements that modify BLACKHOLE tables in row format。 Table(s): ‘t1。’ |

+————-+————+——————————————————————————————————————————————————————-+

1 row in set (0。00 sec)

node1>exit

Bye

hzwenzhh@innosql3:~$ ls -lh node1/data/hzwenzhh/

total 16K

-rw-r——- 1 hzwenzhh neteaseusers 67 Jan 3 15:44 db。opt

-rw-r——- 1 hzwenzhh neteaseusers 8。4K Jan 3 15:44 t1。frm

hzwenzhh@innosql3:~$

```

建立一個blackhole型別的表t1,插入一條記錄後select沒有插入的記錄,update出現一個warning。檢視對應的資料目錄發現只有t1。frm元檔案,沒有對應的資料檔案。這說明blackhole滿足我們最基本的需求。

##### 如何作為secondary節點?

引入blackhole引擎後,面臨的第一個問題是能否作為從/備節點,也就是主節點的引擎是InnoDB,從節點對應表的引擎是blackhole?顯然,DDL操作應該不影響,DML是重點,其中Insert沒有問題,Delete和Update會如何?為此做些實驗:

**一、將node2的預設儲存引擎設定為blackhole,將其作為node1的從節點。主上執行建表操作並增加一個二級索引:**

```

node1>use hzwenzhh

Database changed

node1>create table t1 (a int primary key, b varchar(10));

Query OK, 0 rows affected (0。00 sec)

node1>show create table t1;

+————-+————————————————————————————————————————————————————————————————————-+

| Table | Create Table |

+————-+————————————————————————————————————————————————————————————————————-+

| t1 | CREATE TABLE `t1` (

`a` int(11) NOT NULL,

`b` varchar(10) DEFAULT NULL,

PRIMARY KEY (`a`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |

+————-+————————————————————————————————————————————————————————————————————-+

1 row in set (0。01 sec)

node1>alter table t1 add index b_idx(b);

Query OK, 0 rows affected (0。00 sec)

Records: 0 Duplicates: 0 Warnings: 0

node1>show create table t1;

+————-+————————————————————————————————————————————————————————————————————————————————+

| Table | Create Table |

+————-+————————————————————————————————————————————————————————————————————————————————+

| t1 | CREATE TABLE `t1` (

`a` int(11) NOT NULL,

`b` varchar(10) DEFAULT NULL,

PRIMARY KEY (`a`),

KEY `b_idx` (`b`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |

+————-+————————————————————————————————————————————————————————————————————————————————+

1 row in set (0。00 sec)

```

從上透過複製回放得到的t1如下:

```

node2>show variables like “%default_storage%”;

+————————————+——————-+

| Variable_name | Value |

+————————————+——————-+

| default_storage_engine | BLACKHOLE |

+————————————+——————-+

1 row in set (0。00 sec)

node2>show create table t1;

+————-+——————————————————————————————————————————————————————————————————————+

| Table | Create Table |

+————-+——————————————————————————————————————————————————————————————————————+

| t1 | CREATE TABLE `t1` (

`a` int(11) NOT NULL,

`b` varchar(10) DEFAULT NULL,

PRIMARY KEY (`a`)

) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8mb4 |

+————-+——————————————————————————————————————————————————————————————————————+

1 row in set (0。00 sec)

node2>show create table t1;

+————-+————————————————————————————————————————————————————————————————————————————————-+

| Table | Create Table |

+————-+————————————————————————————————————————————————————————————————————————————————-+

| t1 | CREATE TABLE `t1` (

`a` int(11) NOT NULL,

`b` varchar(10) DEFAULT NULL,

PRIMARY KEY (`a`),

KEY `b_idx` (`b`) // 新增的二級索引

) ENGINE=BLACKHOLE DEFAULT CHARSET=utf8mb4 |

+————-+————————————————————————————————————————————————————————————————————————————————-+

1 row in set (0。00 sec)

```

**二、透過上面實驗,我們先認為這種部署方式下,執行DDL是沒有問題的。接下來試試Insert和Delete操作:**

```

node1>insert into t1 values (1,‘hzwenzhh’);

Query OK, 1 row affected (0。00 sec)

node1>show master status\G

*************************** 1。 row ***************************

File: mysql-bin。000002

Position: 1357

Binlog_Do_DB:

Binlog_Ignore_DB:

Executed_Gtid_Set: 0b91f44e-2dff-11ea-b898-246e963b3e60:1-6

1 row in set (0。00 sec)

node1>delete from t1 where a=1;

Query OK, 1 row affected (0。00 sec)

node1>show master status\G

*************************** 1。 row ***************************

File: mysql-bin。000002

Position: 1610

Binlog_Do_DB:

Binlog_Ignore_DB:

Executed_Gtid_Set: 0b91f44e-2dff-11ea-b898-246e963b3e60:1-7

1 row in set (0。00 sec)

```

從節點情況:

```

node2>show master status\G

*************************** 1。 row ***************************

File: mysql-bin。000003

Position: 430

Binlog_Do_DB:

Binlog_Ignore_DB:

Executed_Gtid_Set: 0b91f44e-2dff-11ea-b898-246e963b3e60:1-6

1 row in set (0。00 sec)

node2>select * from t1;

Empty set (0。00 sec)

node2>show master status\G

*************************** 1。 row ***************************

File: mysql-bin。000003

Position: 670

Binlog_Do_DB:

Binlog_Ignore_DB:

Executed_Gtid_Set: 0b91f44e-2dff-11ea-b898-246e963b3e60:1-7

1 row in set (0。00 sec)

```

**三、從庫看起來也是一切正常,繼續執行update操作:**

```

node1>insert into t1 values (1,‘hzwenzhh’);

Query OK, 1 row affected (0。00 sec)

node1>update t1 set a=2 where a=1;

Query OK, 1 row affected (0。00 sec)

Rows matched: 1 Changed: 1 Warnings: 0

node1>show master status\G

*************************** 1。 row ***************************

File: mysql-bin。000002

Position: 2131

Binlog_Do_DB:

Binlog_Ignore_DB:

Executed_Gtid_Set: 0b91f44e-2dff-11ea-b898-246e963b3e60:1-9

1 row in set (0。00 sec)

```

從庫看起來也沒問題,出乎意外。在主從複製下沒有問題,進一步又在MGR secondary節點做了驗證,確認DML也是沒有問題的。

```

node2>show master status\G

*************************** 1。 row ***************************

File: mysql-bin。000003

Position: 1165

Binlog_Do_DB:

Binlog_Ignore_DB:

Executed_Gtid_Set: 0b91f44e-2dff-11ea-b898-246e963b3e60:1-9

1 row in set (0。00 sec)

```

##### Binlog檔案儲存問題?

在上述驗證的基礎上,使用sysbench工具跑了下oltp測試(修改了common。lua,去掉建表時的engine欄位),確認雖然資料檔案沒有了,但是Binlog和relay-log還是存在的,可以透過[relay_log_space_limit](16。1。6。3 Replication Slave Options and Variables)和[binlog_expire_logs_seconds](17。1。6。4 Binary Logging Options and Variables)來控制兩者的大小(binlog_expire_logs_seconds該引數在社群版8。0提供,InnoSQL在5。7。20版本開始提供。),如果將max_binlog_size設定小些,binlog_expire_logs_seconds設定為分秒級可以達到接近不記錄Binlog的作用。

個人認為,應該視Arbiter節點部署時的具體用途來決定,保留一定時間內的Binlog檔案並不會過多佔用儲存空間,而且有額外的作用,包括可以用來作為其他節點進行分散式故障恢復的源節點(donor),另外可以作為第三方工具拉通Binlog的源節點,這樣可以降低MGR資料節點的負載,進一步發揮Arbiter節點的作用。

##### DDL轉換問題?

透過調整引數配置,可以實現MGR的Arbiter節點功能。不過,還有個點需要解決,那就是DDL語句的引擎問題。對於沒有MySQL原始碼修改能力的團隊,可以透過不在DDL中指定engine欄位,並將Arbiter節點的預設儲存引擎設定為blackhole來適配。

##### 仍有最佳化餘地

透過上面的驗證,只要確保DDL語句不帶engine欄位,就可以在不修改MySQL原始碼的情況下,做到MGR的部署成本大幅降低。

* 從儲存層面,基本上可以省的空間都省了,Binlog還是有使用價值的,可選擇保留。

* 在計算層面,記憶體消耗得到了很大控制,佔最大頭的InnoDB buffer pool所需記憶體已節省。在cpu這塊,雖然省了引擎層的事務邏輯等計算和IO開銷,但Server層的計算消耗還有最佳化的餘地,如果不要Binlog檔案,那麼完全可以將relay-log檔案和MGR的Binlog回放流程都取消掉。

#### 五、Arbiter實現方案

雖然不修改MySQL原始碼可享受Arbiter功能,但在部署和使用上多有不便,且做得並不徹底。最好的方式應該是從程式碼層面提供完整的Arbiter特性,下面對其實現方案進行分析。根據所需修改的MySQL原始碼數量和所涉及的模組,Arbiter特性實現時可以分為相對獨立的2個迭代。

##### 迭代一 完整的功能集

這個迭代解決的是Arbiter特性的可用和易用性問題,實現功能包括:

增加一個MGR變數用於指定Arbiter角色,如arbiter_member=ON。角色的作用包括設定預設儲存引擎為blackhole,將member_weight設定為0,由於該引數值會同步給其他MGR節點,因此實際上起到了無被選舉權的作用;

提供一個初始化工具。該工具的作用是在Arbiter新節點加入MGR叢集前,從其他節點透過mysqldump ——no-data/-d 選項來獲取schema資訊用來初始化Arbiter節點,在將schema資訊匯入前,需要將建表語句的engine欄位從innodb替換為blackhole;

DDL語句引擎自動轉換功能。該功能用於在Arbiter節點加入集群后,回放relay-log時,將create table和alter table等可能攜帶儲存引擎欄位(engine)的DDL語句自動修改為blackhole,防止因為回放了InnoDB引擎的DDL語句而產生資料檔案;

##### 迭代二 提供更多可選引數

這個階段是Arbiter功能的擴充套件,透過暴露可選引數讓使用者決定Arbiter節點的實際用途,實現功能包括:

增加一個MGR變數用於選擇是否需要Binlog日誌,如arbiter_no_binlog。可保留Binlog日誌,也可選擇不產生Binlog。如果為OFF,那麼無需做任何額外改動。

如果該選項為ON,那麼其實有難易兩種實現方法:

簡單的方法是取消Arbiter節點的log_slave_update為ON的限制。

另一種方法就是直接將relay-log也去掉,這也是應該採用的實現方式,方案詳細介紹如下。

###### Arbiter節點無日誌功能方案

因為MGR的Applier模組採用的是pipeline實現方式,其實實現也不是很難,如下所示:

MGR Arbiter:基於成本最佳化的MySQL Group Replication部署新方案

這給功能實現增加了很多便利。理論上說,只需要進行如下適配即可:

2。2a確定了gtid後,直接將gtid更新到gtid_executed全域性變數中,無需再確定last_commit和sequence_number。修改的是certification_handler的處理函式;

2。3和3。2這兩步過濾掉所有的使用者表DML操作,僅寫入DDL語句,修改的是applier_handler的處理函式;

##### 六、總結

MGR是極具吸引力的MySQL高可用方案,但由於其部署成本相比普通高可用例項高,限制了其使用場景。本文為MGR新增Arbiter節點,僅需低規格雲主機和小容量資料盤即可進行Arbiter節點部署,大大降低其部署成本,相比普通高可用例項,只需非常有限的新增投入就可以享受到MGR帶來的諸多好處,為MGR的廣泛使用掃除障礙,可極大拓寬其使用場景。相信MGR能夠成為未來MySQL在生產環境部署的標準高可用配置。

注:文中圖片若有版權問題,請告知刪除。

標簽: 節點  mgr  Arbiter  T1  -+