Shell這么簡單的腳本語言有多線程這一說嗎?答案是有的。只不過它實現起來稍微有點難理解罷了,因為它借助了命名管道實現。所謂多線程就是原本由一個進程完成的事情現在由多個線程去完成。假如一個進程需要10小時完成的事情,現在分配10個線程,給他們分工,然后同時去做這件事情,最終可能就需要1小時。
本案例具體需求是這樣的:
1)公司的業務量比較大,有100個數據庫需要全量備份,而每個數據庫的數據量高達幾十GB,(注意,每一個庫都為一個獨立的實例,即有著獨立的ip:port)。
2)預估每一個庫的備份時間在30分鐘左右
3)要求在5小時內備份完成
提示:要想在5小時內完成100個數據庫的備份,需要使用shell腳本的多線程功能,一次性開10個線程同時并發備份10個數據庫。
知識點一:使用xtrabackup備份MySQL數據庫
Mysqldump對于導出幾個G的數據庫或幾個表,還是不錯的,速度并不慢。一旦數據量達到幾十上百G,無論是對原庫的壓力還是導出的性能,mysqldump就力不從心了。Percona-Xtrabackup備份工具,是實現MySQL在線熱備工作的不二選擇,可進行全量、增量、單表備份和還原。
Xtrabackup官網下載地址:https://www.percona.com/downloads/Percona-XtraBackup-LATEST/,由于我的系統是Rocky8,所以在這里,我下載8.0.30版本。
?
wget https://downloads.percona.com/downloads/Percona-XtraBackup-LATEST/Percona-XtraBackup-8.0.30-23/binary/tarball/percona-xtrabackup-8.0.30-23-Linux-x86_64.glibc2.17.tar.gz
?
因為是二進制包,解壓后可直接使用,將包解壓到/usr/local/下
?
tar?zxf?percona-xtrabackup-8.0.30-23-Linux-x86_64.glibc2.17.tar.gz?-C?/usr/local/ ln?-s?/usr/local/percona-xtrabackup-8.0.30-22-Linux-x86_64.glibc2.17/bin/xtrabackup??/usr/bin/?
?
用xtrabackup做全量備份的命令是:
?
# xtrabackup --defaults-file=/usr/local/mysql/my.cnf --user=bakuser --password=your_pass -S /tmp/mysql.sock --backup --target-dir=/data/backup/mysql/20221210
?
說明:在執行該備份操作之前,需要先創建一個用戶bakuser(用戶名自定義),并授予reload, lock tables, replication client, process, super等權限。備份數據將會放到/data/backup/mysql/20221210目錄里面。
知識點二:文件描述符
文件描述符(縮寫fd)在形式上是一個非負整數。實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。每一個unix進程,都會擁有三個標準的文件描述符,來對應三種不同的流:
文件描述符 | 名稱 |
0 | 標準輸入 |
1 | 標準正確輸出 |
2 | 標準錯誤輸出 |
除了上面三個標準的描述符外,我們還可以在進程中去自定義其他的數字作為文件描述符。每一個文件描述符會對應一個打開文件,同時,不同的文件描述符也可以對應同一個打開文件;同一個文件可以被不同的進程打開,也可以被同一個進程多次打開。
我們可以寫一個測試腳本/tmp/test.sh,內容如下:
?
#!/bin/bash echo "該進程的pid為$$" exec 1>/tmp/test.log 2>&1 ls -l /proc/$$/fd/
?
執行該腳本 sh /tmp/test.sh,然后查看/tmp/test.log:
?
# cat /tmp/test.log 總用量 0 lrwx------ 1 root root 64 12月 10 10:06 0 -> /dev/pts/0 l-wx------ 1 root root 64 12月 10 10:06 1 -> /tmp/test.log l-wx------ 1 root root 64 12月 10 10:06 2 -> /tmp/test.log lr-x------?1?root?root?64?12月?10?10:06?255?->?/tmp/test.sh
?
說明:exec將腳本后續指令的正確和錯誤輸出重定向到了/tmp/test.log,所以查看該文件就會看到以上內容。關于exec命令,我們再來看一個直觀的例子:
?
# exec > /tmp/test # echo "123123" # echo $PWD # lalala -bash: lalala: 未找到命令 # exec > /dev/tty # cat /tmp/test 123123 /root說明:通過上面的例子,可以發現,當執行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那條命令已經結束了。
?
我們可以把命名管道和文件描述符結合起來:
?
# mkfifo test.fifo # exec 100<>test.fifo //這樣可以把fd100的讀和寫全部指定到test.fifo中 # ls -l /dev/fd/100 //可以看到fd100已經指向到了/root/test.fifo lrwx------.?1?root?root?64?12月?10?10:08?100?->?/root/test.fifo??
?
知識點四:read命令
在shell腳本中,read命令使用還是比較多的,最典型的用法是,和用戶交互,如下:
?
# read -p "Please input a number: " n Please input a number: 5 [root@aming-master ~]# echo $n 5
?
如果不使用-p選項,也可以這樣使用:
?
# read name //name為變量名,這樣也是在給name變量賦值 aming # echo $name aming
?
read的-u選項后面可以跟fd,如下:
?
# read -u10 a //這樣會把fd10里面的字符串賦值給a注意,這里的fd10就是前面我們定義的test.fifo,如果你的fd10里還沒有任何的內容寫入,那么你執行上面這條命令會卡著不動。因為fd10是一個命名管道文件,只有寫入了東西,read才會讀到,否則就一直卡著,等待寫入內容。當然,這個命名管道文件可以寫入多行,先儲存起來,然后等著read去讀。
# echo "123" >&10 # echo "456" >&10 //連續在fd10中寫入兩次內容 # read -u10 a //第一次讀取fd10里的第一行 # echo $a 123 # read -u10 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
?
執行腳本結果如下:
?
10:12:02 10:12:02 1 2 10:12:03 10:12:03 3 4 10:12:04 5 10:12:04 6 10:12:05 7 10:12:05 8 10:12:06 10:12:06 9 10可以看到,原本需要10秒完成的事情,現在需要5秒就搞定了,這說明并發量為2,即兩個線程同時執行任務。要想5個線程,那么在一開始的時候,直接向fd1000寫入5個空行即可。
?
本案例參考腳本
?
#!/bin/bash #多線程備份數據庫 #作者:阿銘 #日期:2022-12-10 #版本:v1.5 ##假設100個庫的庫名、host、port以及配置文件路徑存到了一個文件里,文件名字為/tmp/databases.list ##格式:db1 10.10.10.2 3308 /data/mysql/db1/my.cnf ##備份數據庫使用xtrabackup exec &> /tmp/mysql_bak.log if?!?which?xtrabackup?&>/dev/nll then echo "安裝xtrabackup工具" wget https://downloads.percona.com/downloads/Percona-XtraBackup-LATEST/Percona-XtraBackup-8.0.30-23/binary/tarball/percona-xtrabackup-8.0.30-23-Linux-x86_64.glibc2.17.tar.gz ????tar?zxf?percona-xtrabackup-8.0.30-23-Linux-x86_64.glibc2.17.tar.gz?-C?/usr/local/ && ln?-s?/usr/local/percona-xtrabackup-8.0.30-22-Linux-x86_64.glibc2.17/bin/xtrabackup??/usr/bin/ if [ $? -ne 0 ] then echo "安裝xtrabackup工具出錯,請檢查。" exit 1 fi fi bakdir=/data/backup/mysql/`date?+%F` 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 ????xtrabackup?--defaults-file=$4?--host=$2??--port=$3?--user=$bakuser?--password=$bakpass?--databases=$1 --backup?--target-dir=$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
?
這個腳本有點復雜,需要琢磨一會兒。
審核編輯:湯梓紅
?
評論
查看更多