回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 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中的是一的。
<u><i>gopl.io/ch8/crawl1</i></u>
```go
@@ -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/
...
```
最初的錯誤信息是一個讓人莫名的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资源数量应保持接近。
<u><i>gopl.io/ch8/crawl2</i></u>
```go
@@ -82,7 +82,7 @@ func crawl(url string) []string {
}
```
第二個問題是這個程序永都不會終止,使它已爬到了所有初始接衍生出的接。(然,除非你慎重地選擇了合的初始化URL或者已經實現了練習8.6中的深度限,你應該還沒有意識到這個問題)。了使這個程序能夠終止,我需要在worklist空或者有crawl的goroutine在運行時退出主循
第二个问题是这个程序永都不会终止,使它已爬到了所有初始接衍生出的接。(然,除非你慎重地选择了合的初始化URL或者已经实现了练习8.6中的深度限,你应该还没有意识到这个问题)。了使这个程序能够终止,我需要在worklist空或者有crawl的goroutine在运行时退出主循
```go
func main() {
@@ -111,11 +111,11 @@ func main() {
}
```
這個版本中,算器nworklist的送操作數量進行了限。每一次我們發現有元素需要被送到worklist,我們都會對n進行++操作在向worklist中送初始的命令行參數之前,我們也進行過一次++操作。里的操作++是在每啟動一個crawler的goroutine之前。主循環會在n減爲0時終止這時候説明沒活可了。
这个版本中,算器nworklist的送操作数量进行了限。每一次我们发现有元素需要被送到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() {
@@ -149,16 +149,16 @@ 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尾,译注:外链的应该就不算了。)然了,出现在页面里的接你也需要行一些理,使其能在你的像站点上进行跳,而不是指向原始的接。
**譯註**
拓展閲讀 [Handling 1 Million Requests per Minute with Go](http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/)。
**译注**
拓展阅读 [Handling 1 Million Requests per Minute with Go](http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/)。