MHA(Master High Availability)目前在MySQL高可用方面是一個相對成熟的解決方案,在MySQL故障切換過程中,MHA能做到在0~30秒之內自動完成數據庫的故障切換操作,並且在進行故障切換的過程中,MHA能在最大程度上保證數據的一致性,以達到真正意義上的高可用。 MHA裏有兩個角色一個是MHA Node(數據節點)另一個是MHA Manager(管理節點)。 MHA Manager可以單獨部署在一臺獨立的機器上管理多個master-slave集羣,也可以部署在一臺slave節點上。MHA Node運行在每臺MySQL服務器上,MHA Manager會定時探測集羣中的master節點,當master出現故障時,它可以自動將最新數據的slave提升爲新的master,然後將所有其他的slave重新指向新的master。整個故障轉移過程對應用程序完全透明。
MHA工作架構示意圖如下:
在MHA架構中,可以採用半同步複製的方式,來最大限度的保證數據的一致性,MySQL的主從複製中,默認是採用異步來做主從複製的,但是異步有些弊端,下面是對異步、半同步的一個簡單介紹:
- 異步與半同步:默認情況下MySQL的複製是異步的,Master上所有的更新操作寫入Binlog之後並不確保所有的更新都被複制到Slave之上。異步操作雖然效率高,但是在Master/Slave出現問題的時候,存在很高數據不同步的風險,甚至可能丟失數據。 MySQL5.5引入半同步複製功能的目的是爲了保證在master出問題的時候,至少有一臺Slave的數據是完整的。在超時的情況下也可以臨時轉入異步複製,保障業務的正常使用,直到一臺salve追趕上之後,繼續切換到半同步模式。
MHA相比較於其他HA軟件,MHA的目的在於維持MySQL replication中master庫的高可用性,其最大的特點是可以修復多個slave之間的差異日誌,最終使所有slave保持數據一致,然後從中選擇一個充當新的master,並將其他slave指向它。
大概流程如下:
1、從宕機的master保存二進制日誌事件;
2、識別含有最新更新的slave;
3、應用差異的中繼日誌(relay log)到其他slave;
4、應用從master保存的二進制日誌事件;
5、提升一個slave爲新master;
6、使其他的slave連接到新的master進行復制。
目前MHA主要支持一主多從的架構,要搭建MHA,一般MySQL集羣中最少有三臺數據庫節點,一主二從,即:一臺充當master,一臺充當備用master,另外一臺充當slave。
MHA主要特性如下:
1、MHA切換不依賴實例使用存儲引擎和BINLOG格式;
2、MHA不會增加MySQL服務器性能開銷,除MHA管理節點外無需增加額外服務器;
3、在MySQL服務器上部署MHA數據節點不會影響當前實例運行;
4、MHA實現自動故障切換,也可以手動觸發在線切換;
5、MHA可以實現秒級的故障切換;
6、MHA可以將任意slave提升master,也可以在切換時指定master候選節點;
7、MHA提供擴展接口,允許在MHA切換過程中的特定時間點執行用戶自定義腳本。
MHA支持與限制:
1、只支持BINLOG V4版本,要求MySQL 5.0或更高版本。
2、候選master節點必須開啓log-bin參數,如果所有從節點都爲開啓,則不進行故障轉移。
3、在MHA 0.52版本前不支持多master模式
4、MHA默認不支持多級主從複製,通過修改配置文件和設置multi_tier_slave參數
一、環境準備
OS | IP地址 | 主機名&角色 | 類型 |
---|---|---|---|
Centos 7.5 | 192.168.20.2 | master | 主master(寫操作) |
Centos 7.5 | 192.168.20.3 | slave1 | 從(備主)mysql(讀操作) |
Centos 7.5 | 192.168.20.4 | slave2 | 從mysql(讀操作) |
Centos 7.5 | 192.168.20.5 | manager | 管理節點 |
在上述環境中,master對外提供寫服務,slave提供讀操作,一旦master宕機,將會把其中一臺slave(我這裏將指定的是備主)提升爲新的master,slave同時也指向新的master,manager作爲管理服務器。
1、 配置hosts文件,主機之間可互相解析
以下操作在其中一個節點執行即可。
#編寫host文件
[root@master ~]# cat >> /etc/hosts << EOF
> 192.168.20.2 master
> 192.168.20.3 slave1
> 192.168.20.4 slave2
> 192.168.20.5 manager
> EOF
> #將編寫好的hosts文件分發到其他節點
[root@master ~]# for i in master slave1 slave2 manager;do scp /etc/hosts $i:/etc/;done
2、配置ssh免密登錄
注:集羣中的所有主機都需要保證互相可以免密登錄。
將以下兩條命令在所有節點上執行一次,即可完成主機間的免密登錄
# 創建密鑰對
[root@master ~]# ssh-keygen -t rsa
#將公鑰分發到其他節點
[root@master ~]# for i in master slave1 slave2 manager;do ssh-copy-id $i;done
當所有節點都執行上面的兩條命令,即可使用以下命令來驗證,免密登錄是否成功:
#如果每臺主機上執行以下指令不用輸入密碼就可以獲取所有主機的主機名,說明免密登錄配置無誤
[root@master ~]# for i in master slave1 slave2 manager;do ssh $i hostname;done
master
slave1
slave2
manager
3、配置epel並安裝MHA所需依賴
注:以下命令需要在所有節點執行。
#配置阿里的epel源(爲加快安裝速度),若非國內環境,執行yum -y install epel-release進行配置即可
[root@master ~]# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
[root@master ~]# yum makecache #建立元數據緩存
# 安裝所需依賴
[root@master ~]# yum -y install perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-ParallelForkManager perl-Config-IniFiles ncftp perl-Params-Validate perl-CPAN perl-TestMock-LWP.noarch perl-LWP-Authen-Negotiate.noarch perl-devel perl-ExtUtils-CBuilder perl-ExtUtils-MakeMaker
二、基於半同步進行主從複製
注:接下來所有操作,如果沒有特別說明,是需要在所有數據庫服務器上進行執行的。
1、查找半同步所需插件
mysql> show variables like '%plugin_dir%'; #查找插件所在目錄(每臺DB服務器可能不一樣)
+---------------+------------------------------+
| Variable_name | Value |
+---------------+------------------------------+
| plugin_dir | /usr/local/mysql/lib/plugin/ |
+---------------+------------------------------+
1 row in set (0.01 sec)
mysql> show variables like '%have_dynamic%'; #檢查是否支持動態檢測
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| have_dynamic_loading | YES |
+----------------------+-------+
1 row in set (0.01 sec)
[root@master ~]# ls /usr/local/mysql/lib/plugin/ | egrep 'master|slave'
#確定插件的目錄下有以下兩個文件(默認安裝數據庫後就有這些的)
semisync_master.so
semisync_slave.so
2、安裝插件
# semisync_master.so和semisync_slave.so爲上面查詢到的插件名字
mysql> install plugin rpl_semi_sync_master SONAME 'semisync_master.so';
mysql> install plugin rpl_semi_sync_slave SONAME 'semisync_slave.so';
3、檢查Plugin是否已正確安裝:
mysql> show plugins;
# 或者
mysql> select * from information_schema.plugins;
show plugins執行後返回如下結果,表示沒有問題:
4、查看半同步相關信息
mysql> show variables like '%rpl_semi_sync%';
+-------------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled | OFF |
| rpl_semi_sync_master_timeout | 10000 |
| rpl_semi_sync_master_trace_level | 32 |
| rpl_semi_sync_master_wait_for_slave_count | 1 |
| rpl_semi_sync_master_wait_no_slave | ON |
| rpl_semi_sync_master_wait_point | AFTER_SYNC |
| rpl_semi_sync_slave_enabled | OFF |
| rpl_semi_sync_slave_trace_level | 32 |
+-------------------------------------------+------------+
可以看到半同步插件還是未啓用的off狀態,所以需要修改my.cnf配置文件,如下:
master主機完整配置文件如下:
[root@master ~]# cat /etc/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
port=3306
server_id=1
socket=/usr/local/mysql/mysql.sock
log-error=/usr/local/mysql/data/mysqld.err
log-bin=mysql-bin
binlog_format=mixed
rpl_semi_sync_master_enabled=1
rpl_semi_sync_master_timeout=1000
rpl_semi_sync_slave_enabled=1
relay_log_purge=0
relay-log = relay-bin
relay-log-index = slave-relay-bin.index
注:
- rpl_semi_sync_master_enabled=1:1表是啓用,0表示關閉
- rpl_semi_sync_master_timeout=1000:毫秒單位 ,該參數主服務器等待確認消息10秒後,不再等待,變爲異步方式。
slave1主機完整配置文件如下:
[root@slave1 ~]# cat /etc/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
port=3306
server_id=2
socket=/usr/local/mysql/mysql.sock
log-error=/usr/local/mysql/data/mysqld.err
log-bin=mysql-bin
binlog_format=mixed
rpl_semi_sync_master_enabled=1
rpl_semi_sync_master_timeout=1000
rpl_semi_sync_slave_enabled=1
relay_log_purge=0
relay-log = relay-bin
relay-log-index = slave-relay-bin.index
slave2主機的完整配置文件如下:
[root@slave2 ~]# cat /etc/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
port=3306
server_id=3
socket=/usr/local/mysql/mysql.sock
log-error=/usr/local/mysql/data/mysqld.err
log-bin = mysql-bin
relay-log = relay-bin
relay-log-index = slave-relay-bin.index
read_only = 1
rpl_semi_sync_slave_enabled=1
#由於slave2只是用來做一個slave主機,所以無需開啓master的半同步
在配置主從複製中,由於主和備主這兩臺主機上設置了參數relay_log_purge=0(表示不自動清除中繼日誌),所以slave節點需要定期刪除中繼日誌,建議每個slave節點刪除中繼日誌的時間錯開。
crontab -e
0 5 * * * /usr/local/bin/purge_relay_logs - -user=root --password=pwd123 --port=3306 --disable_relay_log_purge >> /var/log/purge_relay.log 2>&1
更改配置文件後,需要執行以下命令進行重啓MySQL。
[root@master ~]# systemctl restart mysqld
查看半同步狀態,確認已開啓:
mysql> show variables like '%rpl_semi_sync%'; #查看半同步是否開啓
+-------------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled | ON | #這個值要爲ON
| rpl_semi_sync_master_timeout | 1000 |
| rpl_semi_sync_master_trace_level | 32 |
| rpl_semi_sync_master_wait_for_slave_count | 1 |
| rpl_semi_sync_master_wait_no_slave | ON |
| rpl_semi_sync_master_wait_point | AFTER_SYNC |
| rpl_semi_sync_slave_enabled | ON | #這個值也要爲ON。
| rpl_semi_sync_slave_trace_level | 32 |
+-------------------------------------------+------------+
8 rows in set (0.00 sec)
mysql> show status like '%rpl_semi_sync%';
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 0 |
| Rpl_semi_sync_master_net_avg_wait_time | 0 |
| Rpl_semi_sync_master_net_wait_time | 0 |
| Rpl_semi_sync_master_net_waits | 0 |
| Rpl_semi_sync_master_no_times | 0 |
| Rpl_semi_sync_master_no_tx | 0 |
| Rpl_semi_sync_master_status | ON |
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 0 |
| Rpl_semi_sync_master_tx_wait_time | 0 |
| Rpl_semi_sync_master_tx_waits | 0 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 0 |
| Rpl_semi_sync_slave_status | OFF |
+--------------------------------------------+-------+
15 rows in set (0.00 sec)
#關於上個命令查看到的信息,有幾個狀態值得關注,將在下面寫下來。
- rpl_semi_sync_master_status :顯示主服務是異步複製模式還是半同步複製模式,ON爲半同步;
- rpl_semi_sync_master_clients :顯示有多少個從服務器配置爲半同步複製模式;
- rpl_semi_sync_master_yes_tx :顯示從服務器確認成功提交的數量
- rpl_semi_sync_master_no_tx :顯示從服務器確認不成功提交的數量
- rpl_semi_sync_master_tx_avg_wait_time :事務因開啓 semi_sync ,平均需要額外等待的時間
- rpl_semi_sync_master_net_avg_wait_time :事務進入等待隊列後,到網絡平均等待時間
5、創建相關用戶
1)master主機操作如下:
# 創建用於同步的用戶
mysql> grant replication slave on *.* to mharep@'192.168.20.%' identified by '123.com';
Query OK, 0 rows affected, 1 warning (1.00 sec)
# 創建用戶mha的manager監控的用戶
mysql> grant all on *.* to manager@'192.168.20.%' identified by '123.com';
Query OK, 0 rows affected, 1 warning (0.00 sec)
# 查看master二進制相關的信息
mysql> show master status\G
*************************** 1. row ***************************
File: mysql-bin.000001
Position: 744
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set:
1 row in set (0.00 sec)
2)slave1主機操作如下:
# 創建用於同步的用戶
mysql> grant replication slave on *.* to mharep@'192.168.20.%' identified by '123.com';
Query OK, 0 rows affected, 1 warning (1.00 sec)
# 創建用戶mha的manager監控的用戶
mysql> grant all on *.* to manager@'192.168.20.%' identified by '123.com';
Query OK, 0 rows affected, 1 warning (0.00 sec)
3)slave2主機操作如下:
由於slave2無需做備主,所以不用創建用於同步數據的賬戶
#創建manager監控賬號
mysql> grant all on *.* to manager@'192.168.20.%' identified by '123.com';
Query OK, 0 rows affected, 1 warning (0.00 sec)
6、配置主從複製
以下操作需要在slave1和slave2主機上分別執行一次,以便同步master主機的數據。
#指定master主機的相關信息
mysql> change master to
-> master_host='192.168.20.2',
-> master_port=3306,
-> master_user='mharep',
-> master_password='123.com',
-> master_log_file = 'mysql-bin.000001', #這是在master主機上查看到的二進制日誌名
-> master_log_pos=744; #同上,這是查看到的二進制日誌的position
Query OK, 0 rows affected, 2 warnings (0.01 sec)
mysql> start slave; #啓動slave
Query OK, 0 rows affected (0.00 sec)
最後查看slave主機的狀態:
在master主機上查看半同步相關信息,會發現同步的client已經變成了2,如下:
7、安裝MHA-node
注:需要MHA-node需要在所有節點安裝(包括manager主機節點)
#下載包
[root@master src]# wget https://github.com/yoshinorim/mha4mysql-node/releases/download/v0.58/mha4mysql-node-0.58.tar.gz
#安裝
[root@master src]# tar zxf mha4mysql-node-0.58.tar.gz
[root@master src]# cd mha4mysql-node-0.58/
[root@master mha4mysql-node-0.58]# perl Makefile.PL
[root@master mha4mysql-node-0.58]# make && make install
注:接下來的所有操作,如果沒有特別標註,則只需要在manager主機節點上執行即可。
8、安裝MHA-manager
#下載包
[root@manager src]# wget https://github.com/yoshinorim/mha4mysql-manager/releases/download/v0.58/mha4mysql-manager-0.58.tar.gz
#安裝
[root@manager src]# tar zxf mha4mysql-manager-0.58.tar.gz
[root@manager src]# cd mha4mysql-manager-0.58/
[root@manager mha4mysql-manager-0.58]# perl Makefile.PL
[root@manager mha4mysql-manager-0.58]# make && make install
9、創建相應目錄及複製所需文件
[root@manager mha4mysql-manager-0.58]# mkdir /etc/masterha
[root@manager mha4mysql-manager-0.58]# mkdir -p /masterha/app1
[root@manager mha4mysql-manager-0.58]# mkdir /scripts
[root@manager mha4mysql-manager-0.58]# pwd
/usr/src/mha4mysql-manager-0.58 #確定當前所在目錄
[root@manager mha4mysql-manager-0.58]# cp samples/conf/* /etc/masterha/
[root@manager mha4mysql-manager-0.58]# cp samples/scripts/* /scripts/
10、修改mha-manager配置文件
注:manager共有兩個主要的配置文件,一個是通用默認的,一個是單獨的。需要將默認通用的配置文件的內容清空,如下:
#清空默認的配置文件
[root@manager masterha]# > /etc/masterha/masterha_default.cnf
然後修改單獨的配置文件:
[root@manager ~]# cat /etc/masterha/app1.cnf #修改如下:
[server default]
manager_workdir=/masterha/app1 #指定工作目錄
manager_log=/masterha/app1/manager.log #指定日誌文件
user=manager #指定manager管理數據庫節點所使用的用戶名
password=123.com #對應的是上面用戶的密碼
ssh_user=root #指定配置了ssh免密登錄的系統用戶
repl_user=mharep #指定用於同步數據的用戶名
repl_password=123.com #對應的是上面同步用戶的 密碼
ping_interval=1 #設置監控主庫,發送ping包的時間間隔,默認是3秒,嘗試三次沒有迴應時自動進行切換
[server1]
hostname=192.168.20.2
port=3306
master_binlog_dir=/usr/local/mysql/data #指定master保存二進制日誌的路徑,以便MHA可以找到master的日誌
candidate_master=1 #設置爲候選master,設置該參數後,發生主從切換以後將會將此庫提升爲主庫
[server2]
hostname=192.168.20.3
port=3306
master_binlog_dir=/usr/local/mysql/data
candidate_master=1 #設置爲候選master
[server3]
hostname=192.168.20.4
port=3306
master_binlog_dir=/usr/local/mysql/data
no_master=1 #設置的不爲備選主庫
完整無註釋的配置文件如下:
[root@manager ~]# cat /etc/masterha/app1.cnf
[server default]
manager_workdir=/masterha/app1
manager_log=/masterha/app1/manager.log
user=manager
password=123.com
ssh_user=root
repl_user=mharep
repl_password=123.com
ping_interval=1
[server1]
hostname=192.168.20.2
port=3306
master_binlog_dir=/usr/local/mysql/data
candidate_master=1
[server2]
hostname=192.168.20.3
port=3306
master_binlog_dir=/usr/local/mysql/data
candidate_master=1
[server3]
hostname=192.168.20.4
port=3306
master_binlog_dir=/usr/local/mysql/data
no_master=1
11、啓動前測試
驗證SSH有效性:
驗證集羣複製的有效性(MySQL必須都啓動),如下:
[root@manager masterha]# masterha_check_repl --global_conf=/etc/masterha/masterha_default.cnf --conf=/etc/masterha/app1.cnf
執行上述命令可能會報錯如下:
這是因爲沒有在mysql節點上找到mysqlbinlog這個命令,只需要在所有MySQL主機節點上執行以下命令創建軟連接即可,如下:
#注:以下命令是在所有數據庫服務節點執行
[root@master ~]# ln -s /usr/local/mysql/bin/* /usr/local/bin/
我這裏再次執行命令,還報錯如下:
解決辦法:
[root@master ~]# rpm -qa |grep -i dbd #查找下面的軟件包
perl-DBD-MySQL-4.023-6.el7.x86_64
perl-DBD-SQLite-1.39-3.el7.x86_64
#卸載下面的軟件包
[root@master ~]# rpm -e --nodeps perl-DBD-MySQL-4.023-6.el7.x86_64
[root@master ~]# yum -y install perl-DBD-MySQL #再次yum安裝即可解決報錯
最後檢查命令執行成功,如下:
[root@manager ~]# masterha_check_repl --global_conf=/etc/masterha/masterha_default.cnf --conf=/etc/masterha/app1.cnf
返回以下信息,表示無誤:
12、啓動manager
[root@manager ~]# nohup masterha_manager --conf=/etc/masterha/app1.cnf &> /var/log/mha_manager.log &
注:可以多次執行上述命令,指定不同的app.cnf配置文件,以便監控多個MySQL集羣,我這裏就一個MySQL集羣,所以只需要執行上述一條命令即可。
13、驗證故障轉移
可以自行查看當前MySQL集羣中目前的主是哪臺服務器,我這裏MySQL集羣中的主是master主機,如下:
現在模擬master主機故障,然後再查看集羣中的master是哪臺,如下:
# 在master主機停止MySQL服務
[root@master ~]# systemctl stop mysqld
# 再次在slave2主機上查看當前的主是哪臺?
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.20.3 # 可以看到當前的主是slave1這臺備主
Master_User: mharep
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 744
Relay_Log_File: relay-bin.000002
Relay_Log_Pos: 320
三、MHA Manager端日常維護
1)檢查是否有以下文件,有則刪除
MHA Manager服務發生主從切換後,MHA manager服務會自動停止,且在manager_workdir(/masterha/app1/)目錄下面生成app1.failover.complete文件,若要再次啓動MHA,必須先確保沒有這個文件,如果有app1.failover.complete或者app1.failover.error這個文件,則會報錯如下:
只需要刪除這個文件在將master主機指向新的master(也就是slave1)即可成功啓動manager服務,如下:
# 刪除
[root@manager app1]# rm -rf /masterha/app1/app1.failover.complete
# 在manager服務的日誌中查看指定master的信息,如下:
[root@manager app1]# cat /masterha/app1/manager.log | grep MASTER
#如果服務已運行很久,那麼肯定是看最後一條信息,日誌裏已經把指向新的master需要執行的指令記錄了下來
#但是需要自己手動寫密碼
Fri Feb 21 17:57:37 2020 - [info] All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='192.168.20.3', MASTER_PORT=3306, MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=744, MASTER_USER='mharep', MASTER_PASSWORD='xxx';
Fri Feb 21 17:57:38 2020 - [info] Executed CHANGE MASTER.
# 在master主機上執行下面命令,以便指定slave2爲主:
mysql> CHANGE MASTER TO MASTER_HOST='192.168.20.3', MASTER_PORT=3306, MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=744, MASTER_USER='mharep', MASTER_PASSWORD='123.com';
#啓動slave功能
mysql> start slave;
#再次啓動manager服務
[root@manager app1]# nohup masterha_manager --conf=/etc/masterha/app1.cnf &> /var/log/mha_manager.log &
[1] 16130
# 通過jobs -l查看到狀態爲“運行中”,即說明manager重新運行成功
[root@manager app1]# jobs -l
[1]+ 16130 運行中 nohup masterha_manager --conf=/etc/masterha/app1.cnf &>/var/log/mha_manager.log &
2)檢查MHA複製
[root@manager ~]# masterha_check_repl --conf=/etc/masterha/app1.cnf
3)停止MHA
[root@manager ~]# masterha_stop --conf=/etc/masterha/app1.cnf
4)啓動MHA
[root@manager ~]# nohup masterha_manager --conf=/etc/masterha/app1.cnf &>/tmp/mha_manager.log &
當有slave節點宕機時,manager服務是無法啓動的,建議在配置文件中暫時註釋掉宕機節點的信息,待修復後再取消註釋。
5)檢查狀態
[root@manager ~]# masterha_check_status --conf=/etc/masterha/app1.cnf #manager沒有啓動時的狀態如下
app1 is stopped(2:NOT_RUNNING).
#啓動manager服務再進行檢查如下
[root@manager ~]# nohup masterha_manager --conf=/etc/masterha/app1.cnf &>/tmp/mha_manager.log &
[1] 19651
[root@manager ~]# masterha_check_status --conf=/etc/masterha/app1.cnf
app1 (pid:19651) is running(0:PING_OK), master:192.168.20.3