需求:並發檢測1000台web服務器狀態(或者並發為1000台web服務器分發文件等)如何用shell實現?
方案-:(這應該是大多數第一時間都想到的方法吧)
思路:一個for循環1000次,順序執行1000次任務。
實現:
#!/bin/bash start_time=`date +%s` #定義腳本運行的開始時間 for ((i=1;i<=1000;i++)) do sleep 1 #sleep 1用來模仿執行一條命令需要花費的時間(可以用真實命令來代替) echo 'success'$i; done stop_time=`date +%s` #定義腳本運行的結束時間 echo "TIME:`expr $stop_time - $start_time`"
運行結果:
[root@iZ94yyzmpgvZ ~]# . test.sh success1 success2 success3 success4 success5 success6 success7 ........此處省略 success999 success1000 TIME:1000
代碼解析以及問題:
一個for循環1000次相當於需要處理1000個任務,循環sleep 1代表運行一個命令需要的時間,用success$i來標示每條任務。
這樣寫的問題是,1000條命令都是順序執行的,完成是阻塞時的運行,假如每條命令的運行時間是1秒的話,那么1000條命令的運行時間是1000秒,效率相當低,
而要求是並發檢測1000台web的存活,如果采用這種順序的方式,那么假如我有1000台web,這時候第900台機器掛掉了,檢測到這台機器狀態所需要的時間就是900s!
所以,問題關鍵集中在一點:如何並發
方案二:
思路:一個for循環1000次,循環體里面的每個任務放入后台運行(在命令后面加&符號代表后台運行)。
實現:
bin/bash start=`date +%s` #定義腳本運行的開始時間 for ((i=1;i<=1000;i++)) do { sleep 1 #sleep 1用來模仿執行一條命令需要花費的時間(可以用真實命令來代替) echo 'success'$i; }& #用{}把循環體括起來,后加一個&符號,代表每次循環都把命令放入后台運行 #一旦放入后台,就意味着{}里面的命令交給操作系統的一個線程處理了 #循環了1000次,就有1000個&把任務放入后台,操作系統會並發1000個線程來處理 #這些任務 done wait #wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都執行完畢了再 #往下執行。 #在這里寫wait是因為,一條命令一旦被放入后台后,這條任務就交給了操作系統 #shell腳本會繼續往下運行(也就是說:shell腳本里面一旦碰到&符號就只管把它 #前面的命令放入后台就算完成任務了,具體執行交給操作系統去做,腳本會繼續 #往下執行),所以要在這個位置加上wait命令,等待操作系統執行完所有后台命令 end=`date +%s` #定義腳本運行的結束時間 echo "TIME:`expr $end - $start`"
運行結果:
[root@iZ94yyzmpgvZ /]# . test1.sh ...... [989] Done { sleep 1; echo 'success'$i; } [990] Done { sleep 1; echo 'success'$i; } success992 [991] Done { sleep 1; echo 'success'$i; } [992] Done { sleep 1; echo 'success'$i; } success993 [993] Done { sleep 1; echo 'success'$i; } success994 success995 [994] Done { sleep 1; echo 'success'$i; } success996 [995] Done { sleep 1; echo 'success'$i; } [996] Done { sleep 1; echo 'success'$i; } success997 success998 [997] Done { sleep 1; echo 'success'$i; } success999 [998] Done { sleep 1; echo 'success'$i; } [999]- Done { sleep 1; echo 'success'$i; } success1000 [1000]+ Done { sleep 1; echo 'success'$i; } TIME:2
代碼分析以及問題:
shell實現並發,就是把循環體的命令用&符號放入后台運行,1000個任務就會並發1000個線程,運行時間2s,比起方案一的1000s,已經非常快了。
可以看出輸出結果success4......success3完全都是無序的,因為大家都是后台運行的,這個時候就是CPU隨機運行了,所以並沒有什么順序
這樣寫確實可以實現並發,然后,大家可以想象一下,1000個任務就要並發1000個線程,這樣對操作系統造成的壓力非常大,它會隨着並發任務數的增多,操作系統處理速度會慢甚至出現其他不穩定因素,就好比你在對nginx調優后,你認為你的nginx理論上最大可以支持1w並發,實際上呢,你的系統會隨着高並發壓力會不斷攀升,處理速度會越來越慢
方案三:
思路:基於方案二:使用linux管道文件特性制作隊列,控制線程數目
知識儲備:
一,管道文件
1:無名管道(ps aux | grep nginx)
2:有名管道(mkfifo /tmp/fd1)
有名管道特性:
1. cat /tmp/fd1(如果管道內容為空,則阻塞)
實驗:

2.echo “test” > /tmp/fd1(如果沒有讀管道的操作,則阻塞)
總結:
利用有名管道的上述特性就可以實現一個隊列控制了
你可以這樣想:一個女士公共廁所總共就10個蹲位,這個蹲位就是隊列長度,女側所門口放着10把鑰匙,要想上廁所必須拿一把鑰匙,上完廁所后歸還鑰匙,下一個人就可以拿鑰匙進去上廁所了,這樣同時來了1千為美女上廁所,那前十個人搶到鑰匙進去上廁所了,后面的990人需要等一個出來歸還鑰匙才可以拿到鑰匙進去上廁所,這樣10把鑰匙就實現了控制1000人上廁所的任務,(os中稱之為信號量)
二.文件描述符
1.管道具有存一個讀一個,讀完一個就少一個,沒有則阻塞,放回的可以重復取,這正是隊列特性,但是問題是當往管道文件里面放入一段內容,沒人取則阻塞,這樣你永遠也沒辦法往管道里面同時放入10段內容(想當與10把鑰匙),解決這個問題的關鍵就是文件描述符了。
2.mkfifo /tmp/fd1
創建有名管道文件exec 3<>/tmp/fd1,創建文件描述符3關聯管道文件,這時候3這個文件描述符就擁有了管道的所有特性,還具有一個管道不具有的特性:無限存不阻塞,無限取不阻塞,而不用關心管道是否為空。也不用關心是否有內容寫入引用文件描述符:&3可以執行n次echo >&3往管道里放入n把鑰匙
實現:
#!/bin/bash start_time=`date +%s` #定義腳本運行的開始時間 [ -e /tmp/fd1 ] || mkfifo /tmp/fd1 #創建有名管道 exec 3<>/tmp/fd1 #創建文件描述符,以可讀(<)可寫(>)的方式關聯管道文件,這時候文件描述符3就有了有名管道文件的所有特性 rm -rf /tmp/fd1 #關聯后的文件描述符擁有管道文件的所有特性,所以這時候管道文件可以刪除,我們留下文件描述符來用就可以了 for ((i=1;i<=10;i++)) do echo >&3 #&3代表引用文件描述符3,這條命令代表往管道里面放入了一個"令牌" done for ((i=1;i<=1000;i++)) do read -u3 #代表從管道中讀取一個令牌 { sleep 1 #sleep 1用來模仿執行一條命令需要花費的時間(可以用真實命令來代替) echo 'success'$i echo >&3 #代表我這一次命令執行到最后,把令牌放回管道 }& done wait stop_time=`date +%s` #定義腳本運行的結束時間 echo "TIME:`expr $stop_time - $start_time`" exec 3<&- #關閉文件描述符的讀 exec 3>&- #關閉文件描述符的寫
運行結果:
[root@iZ94yyzmpgvZ /]# . test2.sh success4 success6 success7 success8 success9 success5 ...... success935 success941 success942 ...... success992 [992] Done { sleep 1; echo 'success'$i; echo 1>&3; } success993 [993] Done { sleep 1; echo 'success'$i; echo 1>&3; } success994 [994] Done { sleep 1; echo 'success'$i; echo 1>&3; } success998 success999 success1000 success997 success995 success996 [995] Done { sleep 1; echo 'success'$i; echo 1>&3; } TIME:101
代碼解析以及問題:
兩個for循環,第一個for循環10次,相當於在女士公共廁所門口放了10把鑰匙,第二個for循環1000次,相當於1000個人來上廁所,read -u3相當於取走一把鑰匙。{ }里面最好一行代碼echo >&3相當於上完廁所送還鑰匙。
這樣就實現了10把鑰匙控制1000個任務的運行,運行時間為101s,肯定不如方案二快,但是比方案一已經快很多了,這就是隊列控制同一時間只有最多10個線程的並發,即提高了效率,又實現了並發控制。
注意:創建一個文件描述符exec 3<>/tmp/fd1不能有空格,代表文件描述符3有可讀(<)可寫(>)權限,注意,打開的時候可以寫在一起,關閉的時候必須分開,exec 3<&-關閉讀,exec 3>&-關閉寫
本文轉自http://www.cnblogs.com/chenjiahe/p/6268853.html