update tw

This commit is contained in:
chai2010
2015-12-18 10:53:03 +08:00
parent 510c741a6f
commit c66a96ee52
106 changed files with 864 additions and 864 deletions

View File

@@ -1,6 +1,6 @@
## 8.6. 示例: 併髮的Web爬蟲
## 8.6. 示例: 並發的Web爬蟲
在5.6節中,我們做了一簡單的web爬蟲用bfs(廣度優先)算法來抓取整網站。在本節中,我們會讓這箇這箇爬蟲行化,這樣每一彼此獨立的抓取命令可以行進行IO最大化利用網絡資源。crawl函數和gopl.io/ch5/findlinks3中的是一樣的。
在5.6節中,我們做了一簡單的web爬蟲用bfs(廣度優先)算法來抓取整網站。在本節中,我們會讓這個這個爬蟲行化,這樣每一彼此獨立的抓取命令可以行進行IO最大化利用網絡資源。crawl函數和gopl.io/ch5/findlinks3中的是一樣的。
```go
gopl.io/ch8/crawl1
@@ -14,7 +14,7 @@ func crawl(url string) []string {
}
```
主函數和5.6節中的breadthFirst(深度優先)類似。像之前一樣,一worklist是一記録了需要處理的元素的隊列,每一元素都是一需要抓取的URL列不過這一次我們用channel代替slice來做這隊列。每一對crawl的調用都會在他們自己的goroutine中進行且會把他們抓到的鏈接送迴worklist。
主函數和5.6節中的breadthFirst(深度優先)類似。像之前一樣,一worklist是一記録了需要處理的元素的隊列,每一元素都是一需要抓取的URL列不過這一次我們用channel代替slice來做這隊列。每一對crawl的調用都會在他們自己的goroutine中進行且會把他們抓到的鏈接送迴worklist。
```go
func main() {
@@ -38,9 +38,9 @@ func main() {
}
```
註意這的crawl所在的goroutine會將link作為一箇顯式的數傳入,來避免“循環變量快照”的問題(在5.6.1中有講解)。另外註意這將命令行數傳入worklist也是在一另外的goroutine中進行的這是了避免在main goroutine和crawler goroutine中衕時嚮另一goroutine通過channel送內容時生死鎖(因另一邊的接收操作還沒有準備好)。然,這我們也可以用buffered channel來解決問題不再贅述。
註意這的crawl所在的goroutine會將link作爲一個顯式的數傳入,來避免“循環變量快照”的問題(在5.6.1中有講解)。另外註意這將命令行數傳入worklist也是在一另外的goroutine中進行的這是了避免在main goroutine和crawler goroutine中同時向另一goroutine通過channel送內容時生死鎖(因另一邊的接收操作還沒有準備好)。然,這我們也可以用buffered channel來解決問題不再贅述。
現在爬蟲可以高併髮地運行起來,且可以產生一大坨的URL了不過還是會有倆問題。一問題是在運行一段時間後可能會齣現在log的錯誤信息的:
現在爬蟲可以高並發地運行起來,且可以產生一大坨的URL了不過還是會有倆問題。一問題是在運行一段時間後可能會齣現在log的錯誤信息的:
```
@@ -56,13 +56,13 @@ https://golang.org/blog/
too many open files
...
```
最初的錯誤信息是一讓人莫名的DNS査找失敗卽使這域名是完全可靠的。而隨後的錯誤信息揭示了原因:這程序一次性創建了太多網絡連接,超過了每一進程的打開文件數限製旣而導緻了在調用net.Dial像DNS査找失敗這樣的問題。
最初的錯誤信息是一讓人莫名的DNS査找失敗卽使這域名是完全可靠的。而隨後的錯誤信息揭示了原因:這程序一次性創建了太多網絡連接,超過了每一進程的打開文件數限製旣而導緻了在調用net.Dial像DNS査找失敗這樣的問題。
程序實在是太他媽行了。無窮無盡地併行化不是什好事情,因不管怎麼説,你的統總是會有一些限製因素比如CPU覈心數會限製你的計算負載比如你的硬盤轉軸和磁頭數限製了你的本地磁盤IO操作頻率比如你的網絡帶寬限製了你的下載速度上限或者是你的一web服務的服務容量上限等等。了解決這問題,我們可以限製併髮程序所使用的資源來使之適應自己的運行環境。對於我們的例子來最簡單的方法就是限製對links.Extract在一時間最多不會有超過n次調用的n是fd的limit-20一般情況下。這箇一箇夜店限製客人數目是一道理,隻有有客人離開時,纔會允許新的客人進入店內(譯註:作者你老流氓)。
程序實在是太他媽行了。無窮無儘地並行化不是什好事情,因不管怎麽說,你的統總是會有一些限製因素比如CPU覈心數會限製你的計算負載比如你的硬盤轉軸和磁頭數限製了你的本地磁盤IO操作頻率比如你的網絡帶寬限製了你的下載速度上限或者是你的一web服務的服務容量上限等等。了解決這問題,我們可以限製並發程序所使用的資源來使之適應自己的運行環境。對於我們的例子來最簡單的方法就是限製對links.Extract在一時間最多不會有超過n次調用的n是fd的limit-20一般情況下。這個一個夜店限製客人數目是一道理,隻有有客人離開時,纔會允許新的客人進入店內(譯註:作者你老流氓)。
我們可以用一有容量限製的buffered channel來控製併髮,這類似於操作係統裏的計數信號量概唸。從概唸上講channel的n空槽代錶n箇可以處理內容的token(通行証)從channel接收一值會釋放其中的一token且生成一新的空槽位。這樣保証了在沒有接收介入時最多有n箇髮送操作。(這可能我們拿channel填充的槽來做token更直觀一些不過還是這樣吧~)。由於channel的元素類型不重要,我們用一零值的struct{}來作其元素。
我們可以用一有容量限製的buffered channel來控製並發,這類似於操作繫統裡的計數信號量概唸。從概唸上講channel的n空槽代表n個可以處理內容的token(通行証)從channel接收一值會釋放其中的一token且生成一新的空槽位。這樣保証了在沒有接收介入時最多有n個發送操作。(這可能我們拿channel填充的槽來做token更直觀一些不過還是這樣吧~)。由於channel的元素類型不重要,我們用一零值的struct{}來作其元素。
讓我們重寫crawl函數將對links.Extract的調用操作用取、釋放token的操作包裹起來來確保一時間對其隻有20調用。信號量數量和其能操作的IO資源數量應保持接近。
讓我們重寫crawl函數將對links.Extract的調用操作用取、釋放token的操作包裹起來來確保一時間對其隻有20調用。信號量數量和其能操作的IO資源數量應保持接近。
```go
gopl.io/ch8/crawl2
@@ -82,7 +82,7 @@ func crawl(url string) []string {
}
```
第二個問題是這個程序永遠都不會終止,卽使它已經爬到了所有初始鏈接衍生齣的鏈接。(然,除非你慎重地選擇了適的初始化URL或者已經實現了練習8.6中的深度限製,你應該還沒有意識到這個問題)。爲了使這個程序能夠終止我們需要在worklist爲空或者沒有crawl的goroutine在運行時退齣主循環。
第二個問題是這個程序永遠都不會終止,卽使它已經爬到了所有初始鏈接衍生齣的鏈接。(然,除非你慎重地選擇了適的初始化URL或者已經實現了練習8.6中的深度限製,你應該還沒有意識到這個問題)。爲了使這個程序能夠終止我們需要在worklist爲空或者沒有crawl的goroutine在運行時退齣主循環。
```go
@@ -114,11 +114,11 @@ func main() {
```
版本中計算器n對worklist的送操作數量進行了限製。每一次我們現有元素需要被送到worklist時我們都會對n進行++操作,在worklist中送初始的命令行數之前,我們也進行過一次++操作。這的操作++是在每動一crawler的goroutine之前。主循環會在n減0時終止這時候明沒活可乾了。
版本中計算器n對worklist的送操作數量進行了限製。每一次我們現有元素需要被送到worklist時我們都會對n進行++操作,在worklist中送初始的命令行數之前,我們也進行過一次++操作。這的操作++是在每動一crawler的goroutine之前。主循環會在n減0時終止這時候明沒活可乾了。
現在這箇併髮爬蟲會比5.6節中的深度優先蒐索版快上20倍而且不會齣什錯,且在其完成任務時也會正確地終止。
現在這個並發爬蟲會比5.6節中的深度優先蒐索版快上20倍而且不會齣什錯,且在其完成任務時也會正確地終止。
的程序是避免過度併髮的另一種思路。這版本使用了原來的crawl函數但沒有使用計數信號量取而代之用了20長活的crawler goroutine這樣來保証最多20HTTP請求在併髮
的程序是避免過度並發的另一種思路。這版本使用了原來的crawl函數但沒有使用計數信號量取而代之用了20長活的crawler goroutine這樣來保証最多20HTTP請求在並發
```go
func main() {
@@ -152,15 +152,15 @@ func main() {
}
```
所有的爬蟲goroutine現在都是被衕一箇channel-unseenLinks餵飽的了。主goroutine負責拆分它從worklist拿到的元素然後把沒有抓過的經由unseenLinks channel送給一爬蟲的goroutine。
所有的爬蟲goroutine現在都是被同一個channel-unseenLinks餵飽的了。主goroutine負責拆分它從worklist拿到的元素然後把沒有抓過的經由unseenLinks channel送給一爬蟲的goroutine。
seen這map被限定在main goroutine中也就是説這箇map隻能在main goroutine中進行訪問。類似於其它的信息隱藏方式這樣的約束可以讓我們從一定程度上保証程序的正確性。例如內部變量不能夠在函數外部被訪問到變量(§2.3.4)在沒有被轉義的情況下是無法在函數外部訪問的;一對象的封裝字段無法被該對象的方法以外的方法訪問到。在所有的情況下,信息隱藏都可以幫助我們約束我們的程序,使其不生意料之外的情況。
seen這map被限定在main goroutine中也就是說這個map隻能在main goroutine中進行訪問。類似於其它的信息隱藏方式這樣的約束可以讓我們從一定程度上保証程序的正確性。例如內部變量不能夠在函數外部被訪問到變量(§2.3.4)在沒有被轉義的情況下是無法在函數外部訪問的;一對象的封裝字段無法被該對象的方法以外的方法訪問到。在所有的情況下,信息隱藏都可以幫助我們約束我們的程序,使其不生意料之外的情況。
crawl函數爬到的鏈接在一專有的goroutine中被送到worklist中來避免死鎖。了節省空間,這例子的終止問題我們先不進行詳細闡述了。
crawl函數爬到的鏈接在一專有的goroutine中被送到worklist中來避免死鎖。了節省空間,這例子的終止問題我們先不進行詳細闡述了。
練習8.6: 為併髮爬蟲增加深度限製。也就是如果用戶設置了depth=3隻有從首頁跳轉三次以內能夠跳到的頁纔能被抓取到。
練習8.6: 爲並發爬蟲增加深度限製。也就是如果用戶設置了depth=3隻有從首頁跳轉三次以內能夠跳到的頁纔能被抓取到。
練習8.7: 完成一箇併髮程序來創建一箇線上網站的本地鏡像,把該站點的所有可達的頁都抓取到本地硬盤。了省事,我們這可以隻取齣現在該域下的所有頁(比如golang.org結尾譯註外鏈的應該就不算了。)然了,齣現在頁麪裏的鏈接你也需要進行一些處理,使其能夠在你的鏡像站點上進行跳轉,而不是指原始的鏈接。
練習8.7: 完成一個並發程序來創建一個綫上網站的本地鏡像,把該站點的所有可達的頁都抓取到本地硬盤。了省事,我們這可以隻取齣現在該域下的所有頁(比如golang.org結尾譯註外鏈的應該就不算了。)然了,齣現在頁面裡的鏈接你也需要進行一些處理,使其能夠在你的鏡像站點上進行跳轉,而不是指原始的鏈接。
譯註: