案例12、shell多線程備份數據庫

shell腳本多線程,實現起來有點難理解,由於它藉助了命名管道實現。所謂多線程就是本來由一個進程完成的事情如今由多個線程去完成。假如一個進程須要10小時完成的事情,如今分配10個線程,給他們分工,而後同時去作這件事情,最終可能就須要1小時。mysql

本案例具體需求以下:sql

1)公司的業務量比較大,有100個數據庫須要全量備份,而每一個數據庫的數據量高達幾十GB,(注意,每個庫都爲一個獨立的實例,即有着獨立的IP:port)。shell

2)預估每個庫的備份時間在30分鐘左右。數據庫

3)要求在5小時內備份完成。bash


提示:要想在5小時內完成100個數據庫的備份,須要使用shell腳本的多線程功能,一次性開10個線程同時併發備份10個數據庫。多線程


知識點一:使用xtrabackup備份MySQL數據庫併發

mysqldump對於導出幾個G的數據庫或幾個表,仍是不錯的。一旦數據量達到幾十上百G,不管是對原庫的壓力仍是導出的性能,mysqldump就不太行了。Percona-Xtrabackup備份工具,實現MySQL在線熱備工做的不二選擇,可進行全量,增量,單表備份和還原。ide


xtrabackup命令只支持InnoDB和XtraDB存儲引擎的數據庫非阻塞地備份,而innobackupex經過Perl封裝了一層xtrabackup,對Myisam的備份經過加表讀鎖的方式實現。函數


在CentOS7上這樣安裝Percona-Xtrabackup:工具

# rpm -ivh https://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm  //安裝yum源
# yum install -y percona-xtrabackup-24  //yum安裝2.4版本

用xtrabackup作全量備份的命令是:

# innobackupex --default-file=/etc/my.cnf --host=10.100.100.100 --port=3333 --user=bakuser --password=your_pass /data/backup/mysql

說明:在執行該備份操做以前,須要先建立一個用戶bakuser(用戶名自定義),並授予reload,lock tables,replication client,process,super等權限。備份數據將會放到/data/backup/mysql目錄裏面,自動生成一個以當前日期、時間爲名字的目錄,例如2019-08-10_09_56_12。


知識點二:文件描述符

文件描述符(縮寫fd)在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。每個Unix進程,都會擁有三個標準的文件描述符,來對應三種不一樣的流:

文件描述符        名稱
0               標準輸入
1               標準正確輸出
2               標準錯誤輸出

除了上面三個標準的描述符外,還能夠在進程中去自定義其餘的數字做爲文件描述符。每個文件描述符會對應打開一個文件,同時,不一樣的文件描述符也能夠對應同一個打開文件;同一個文件能夠被不一樣的進程打開,也能夠被同一個進程屢次打開。


寫一個測試腳本,/tmp/test.sh,內容:

#!/bin/bash
echo "該進程的pid爲$$"
exec 1>/tmp/test.log 2>&1
ls -l /proc/$$/fd/

執行該腳本,而後查看/tmp/test.log

# cat test.log
總用量 0
lrwx------. 1 root root 64 8月  10 10:24 0 -> /dev/pts/1
l-wx------. 1 root root 64 8月  10 10:24 1 -> /tmp/test.log
l-wx------. 1 root root 64 8月  10 10:24 2 -> /tmp/test.log
lr-x------. 1 root root 64 8月  10 10:24 255 -> /tmp/test.sh

說明:exec將腳本後續指令的正確和錯誤輸出重定向到了/tmp/test.log,因此查看該文件時就會看到上述內容。關於exec命令,再看一個直觀的例子:

[root@wbs 12.sh]# exec > /tmp/test
[root@wbs 12.sh]# echo "123123"
[root@wbs 12.sh]# echo $PWD
[root@wbs 12.sh]# lalala
-bash: lalala: 未找到命令
[root@wbs 12.sh]# exec > /dev/tty
[root@wbs 12.sh]# cat /tmp/test
123123
/root/20shell/12.sh

說明:經過上面的例子,能夠發現,當執行exec後,其後面的命令的標準正確輸出所有寫入到了/tmp/test文件中,而錯誤的仍是在當前終端上顯示,要想退出這個設置,須要從新定義exec的標準輸出爲/dev/tty。


知識點三:命名管道

前面在shell腳本中屢次用過管道符號「|」,這個叫作匿名管道,而這裏提到的管道叫命名管道,功能和匿名管道基本上是同樣的。

命名管道,英文名First In First Out,簡稱FIFO。命名管道特色:

1)在文件系統中,FIFO擁有名稱,而且是以設備特殊文件的形式存在的;

2)任何進程均可以經過FIFO共享數據;

3)除非FIFO兩端同時有讀和寫的進程,不然FIFO的數據流通將會阻塞;

4)匿名管道是由shell自動建立的,存在於內核中,而FIFO則是由程序建立的(好比mkfifo命令),存在於文件系統中;

5)匿名管道是單向的字節流,而FIFO是雙向的字節流。


用mkfifo命令建立一個命名管道:

# screen
# mkfifo 123.fifo
# echo "121212" > 123.fifo  //此時被阻塞,由於咱們只是在管道里寫入了內容,並無其餘進程讀這個內容。
ctrl+a d  //退出該screen
# cat 123.fifo  //此時能夠看到121212內容,而後再進入screen去看剛纔的echo那條命令已經結束了。

能夠把命名管道和文件描述符結合起來:

[root@wbs ~]# mkfifo test.fifo
[root@wbs ~]# exec 10<>test.fifo  //這樣能夠把fd10的讀和寫所有指定到test.fifo中
[root@wbs ~]# ls -l /dev/fd/10  //能夠看到fd10已經指向到了/root/test.fifo
lrwx------. 1 root root 64 8月  10 11:41 /dev/fd/10 -> /root/test.fifo


知識點四:read命令

在shell腳本中,read命令使用仍是比較多的,最典型的用法是,和用戶交互,以下:

# read -p "Please input a number:" n
Please input a number:3
# echo $n
3

若是不使用-p選項,也能夠這樣使用:

# read name
1234
# echo $name
1234

read的-u選項後面能夠跟fd,以下:

# read -u 10 a  //這樣會把fd 10裏面的字符串賦值給a

注意,這裏的fd 10就是前面定義的test.fifo,若是你的fd 10裏尚未任何的內容寫入,那麼你執行上面這條命令會卡着不動。由於fd 10是一個命名管道文件,只有寫入了東西,read纔會讀到,不然就一直卡着,等待寫入內容。固然,這個命名管道文件能夠寫入多行,先儲存起來,而後等着read去讀。

# echo "123" >& 10
# echo "456" >& 10  //連續在fd10中寫入兩次內容
# read -u 10 a  //第一次讀取fd10裏的第一行
# echo $a
123
# read -u 10 a  //第二次讀取fd10裏的第二行
# echo $a
456


知識點五:wait命令

wait命令:等待的意思,即等待那些在沒有完成的任務(主要是後臺的任務),直到全部任務完成後,纔會繼續執行wait之後的指令,經常使用於shell腳本中。如下是關於wait指令的示例:

# sleep 5 &
# wait   //此時會卡死不動,直到上面的後臺指令執行完,纔會有反應。


知識點六:結合命名管道和read實現多線程

命名管道有兩個很明顯的特色:

1)先進先出,好比上例中咱們給fd10寫入了兩行內容,則第一次read第一行,第二次read第二行。

2)有內容read則執行,沒有則阻塞,例如上例中,read完兩次後,若是你再執行一次read,則它會一直卡着,直到咱們再次寫入新的內容它纔會read到。


利用這兩個特色,就能夠實現shell的多線程了,先看個例子:

#!/bin/bash
#建立命名管道123.fifo文件
mkfifo 123.fifo
#將命名管道123.fifo和文件描述符1000綁定,即fd1000的輸入輸出都是在123.fifo中
exec 1000<>123.fifo

#連續向fd1000中寫入兩次空行
echo >&1000
echo >&1000

#循環10次
for i in `seq 1 10`
do
    #每循環一次,讀一次fd1000中的內容,即空行,只有讀到空行了,纔會執行{ }內的指令
    #每次循環都須要打印當前的時間,休眠1秒,而後再次向fd1000中寫入空行,這樣後續的read就有內容了
    #read指令不只能夠賦值,也能夠跟一個函數,用{ }括起來,函數中是多條指令
    read -u1000
    {
        date +%T
        echo $i
        sleep 1
        echo >&1000
    } &  //丟到後臺去,這樣10次很快就循環完,只不過這些任務是在後臺跑着。因爲咱們一開始就向fd1000裏寫入了兩個空行,因此read會一次性讀到兩行。
done
#等待全部後臺任務執行完成
wait
#刪除fd1000
exec 1000>&-
#刪除命名管道
rm -f 123.fifo

執行結果:

08:54:11
1
08:54:11
2
08:54:12
3
08:54:12
4
08:54:13
5
08:54:13
6
08:54:14
08:54:14
7
8
08:54:15
9
08:54:15
10

能夠看到,本來須要10秒完成的事情,如今5秒就完成了,這說明併發量爲2,即兩個線程同時執行任務,要想5個線程,那麼在一開始的時候,直接向fd1000寫入5個空行便可。


本案例參考腳本

#!/bin/bash
#多線程備份數據庫
#做者:
#日期:
#版本:v1.0

##假設100個庫的庫名、host、port以及配置文件路徑存到了一個文件裏,文件名字爲/tmp/databases.list
##格式:db1 10.10.10.2 3308 /data/mysql/db1/my.cnf
##備份數據庫使用xtrabackup(因爲涉及到myisam,命令爲inoobackupex)

exec &> /tmp/mysql_bak.log

if ! which innobackupex &>/dev/nll
then
    echo "安裝xtrabackup工具"
    rpm -ivh http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm  && \ 
    yum install -y percona-xtrabackup-24
    if [ $? -ne 0 ]
    then
        echo "安裝xtrabackup工具出錯,請檢查。"
        exit 1
    fi
fi

bakdir=/data/backup/mysql
bakuser=vyNctM
bakpass=99omeaBHh

function bak_data {
    db_name=$1
    db_host=$2
    db_port=$3
    cnf=$4
    [ -d $bakdir/$db_name ] || mkdir -p $bakdir/$db_name
    innobackupex --defaults-file=$4  --host=$2  --port=$3 --user=$bakuser --password=$bakpass  $bakdir/$1
        if [ $? -ne 0 ]
        then
            echo "備份數據庫$1出現問題。"
        fi
}

fifofile=/tmp/$$
mkfifo $fifofile
exec 1000<>$fifofile


thread=10
for ((i=0;i<$thread;i++))
do
    echo >&1000
done

cat /tmp/databases.list | while read line
do
    read -u1000
    {
        bak_data `echo $line`
        echo >&1000
    } &
done

wait
exec 1000>&-
rm -f $fifofile
相關文章
相關標籤/搜索