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