網絡爬蟲及分佈式系統

 一.抓取網頁

  1.URL

  Web 上每種可用的資源, 如HTML 文檔、 圖像、 視頻片斷、 程序等都由一個通用資源標誌符(Universal Resource Identifier,URI)進行定位。
  URI 一般由三部分組成:①訪問資源的命名機制;②存放資源的主機名;③資源自身的名稱。html

  URL 是 URI 的一個子集。 它是 Uniform Resource Locator 的縮寫, 譯爲 「統一資源定位符」。通俗地說,URL 是 Internet 上描述信息資源的字符串,主要用在各類 WWW 客戶程序和服務器程序上, 特別是著名的 Mosaic。 採用 URL 能夠用一種統一的格式來描述各類信息資源,包括文件、服務器的地址和目錄等。URL 的格式由三部分組成:
  *第一部分是協議(或稱爲服務方式)。
  *第二部分是存有該資源的主機 IP 地址(有時也包括端口號)。
  *第三部分是主機資源的具體地址,如目錄和文件名等。
  第一部分和第二部分用「://」符號隔開,第二部分和第三部分用「/」符號隔開。第一部分和第二部分是不可缺乏的,第三部分有時能夠省略。web

  2.Http報文及狀態碼

  參考:http://www.cnblogs.com/jslee/p/3449915.html算法

  3.寬度優先遍歷

  圖的寬度優先遍歷須要一個隊列做爲保存當前節點的子節點的數據結構。具體的算法
  以下所示:
  (1) 頂點 V 入隊列。
  (2) 當隊列非空時繼續執行,不然算法爲空。
  (3) 出隊列,得到隊頭節點 V,訪問頂點 V 並標記 V 已經被訪問。
  (4) 查找頂點 V 的第一個鄰接頂點 col。
  (5) 若 V 的鄰接頂點 col 未被訪問過,則 col 進隊列。
  (6) 繼續查找 V 的其餘鄰接頂點 col,轉到步驟(5),若 V 的全部鄰接頂點都已經被訪問過,則轉到步驟(2)。數據庫

  寬度優先遍歷是爬蟲中使用最普遍的一種爬蟲策略,之因此使用寬度優先搜索策略,主要緣由有三點:
  *重要的網頁每每離種子比較近,例如咱們打開新聞網站的時候每每是最熱門的新聞,隨着不斷的深刻衝浪,所看到的網頁的重要性愈來愈低。
  *萬維網的實際深度最多能達到 17 層, 但到達某個網頁總存在一條很短的路徑。而寬度優先遍歷會以最快的速度到達這個網頁。
  *寬度優先有利於多爬蟲的合做抓取,多爬蟲合做一般先抓取站內連接,抓取的封閉性很強。數組

  4.頁面選擇

  在寬度優先遍歷中,在 URL 隊列中選擇須要抓取的 URL 時,不必定按照隊列「先進先出」的方式進行選擇。 而把重要的 URL 先從隊列中 「挑」 出來進行抓取。 這種策略也稱做 「頁面選擇
(Page Selection)。這可使有限的網絡資源照顧重要性高的網頁。
  判斷網頁的重要性的因素不少, 主要有連接的歡迎度(知道連接的重要性了吧)、 連接的重要度和平均連接深度、網站質量、歷史權重等主要因素。
  *連接的歡迎度主要是由反向連接(backlinks,即指向當前 URL 的連接)的數量和質量決定的,咱們定義爲 IB(P)。
  *連接的重要度, 是一個關於 URL 字符串的函數, 僅僅考察字符串自己, 好比認爲 「.com」和「home」的 URL 重要度比「.cc」和「map」高,咱們定義爲 IL(P)。
  *平均連接深度,根據上面所分析的寬度優先的原則計算出全站的平均連接深度,而後認爲距離種子站點越近的重要性越高。咱們定義爲 ID(P)。
  若是咱們定義網頁的重要性爲 I(P),那麼,頁面的重要度由下面的公式決定:
  I(P)=X*IB(P)+Y*IL(P) (1.1)
  其中,X 和 Y 兩個參數,用來調整 IB(P)和 IL(P)所佔比例的大小,ID(P)由寬度優先的遍歷規則保證,所以不做爲重要的指標函數。
  如何實現最佳優先爬蟲呢,最簡單的方式可使用優先級隊列來實現 TODO 表,而且把每一個 URL 的重要性做爲隊列元素的優先級。這樣,每次選出來擴展的 URL 就是具備最高重要性的網頁。緩存

  5.爬蟲隊列

  數以十億計的 URL 地址,使用內存的鏈表或者隊列來存儲顯然不夠,所以,須要找到一種數據結構,這種數據結構具備如下幾個特色:
  *可以存儲海量數據,當數據超出內存限制的時候,可以把它固化在硬盤上。
  *存取數據速度很是快。
  *可以支持多線程訪問(多線程技術可以大規模提高爬蟲的性能)。安全

  對存儲速度的要求,使 Hash 成爲存儲結構的不二選擇。一般,在進行 Hash 存儲的時候,key 值都選取 URL 字符串,可是爲了更節省空間, 一般會對 URL 進行壓縮。經常使用的壓縮算法是 MD5 壓縮算法。服務器

  選擇一個能夠進行線程安全、使用 Hash 存儲,而且可以應對海量數據的內存數據庫是存儲 URL 最合適的數據結構。 所以, 由 Oracle 公司開發的內存數據庫產品 Berkeley DB 就進入了咱們的視線。網絡

  關鍵字/數據(key/value)是 Berkeley DB 用來進行數據庫管理的基礎。每一個 key/value 對構成一條記錄。而整個數據庫實際上就是由許多這樣的結構單元所構成的。經過這種方式,開發人員在使用 Berkeley DB 提供的 API 訪問數據庫時, 只需提供關鍵字就可以訪問到相應的數據。固然也能夠提供 Key 和部分 Data 來查詢符合條件的相近數據。Berkeley DB 底層實現採用 B 樹數據結構

  6.布隆過濾器(Bloom Filter)

  在網絡爬蟲裏,如何判斷一個網址是否被訪問過等。最直接的方法就是將集合中所有的元素存在計算機中,遇到一個新元素時, 將它和集合中的元素直接比較便可。 通常來說, 計算機中的集合是用哈希表(Hash Table)來存儲的。它的好處是快速而準確,缺點是費存儲空間。當集合比較小時,這個問題不顯著,可是當集合巨大時,哈希表存儲效率低的問題就顯現出來了。

  因爲哈希表的存儲效率通常只有 50%, 所以一個電子郵件地址須要佔用十六個字節。一億個地址大約要 1.6GB,即十六億字節的內存。所以存儲幾十億個郵件地址可能須要上百 GB 的內存。計算機內存根本不能存儲。

  一種稱做布隆過濾器的數學工具, 它只須要哈希表 1/8 到 1/4 的大小就能解決一樣的問題。

  假定存儲一億個電子郵件地址,先創建一個 16 億二進制常量,即兩億字節的向量,而後將這 16 億個二進制位所有設置爲零。對於每個電子郵件地址 X,用 8 個不一樣的隨機數產生器(F1,F2, …, F8)產生 8 個信息指紋(f1, f2, …, f8)。再用一個隨機數產生器 G 把這 8 個信息指紋映射到 1 到 16 億中的 8 個天然數 g1, g2, …, g8。 如今咱們把這 8 個位置的二進制位所有設置爲 1。 當咱們對這 1 億個 E-mail 地址都進行這樣的處理後。 一個針對這些 E-mail地址的布隆過濾器就建成了,如圖所示。

  

  如今,來看看布隆過濾器是如何檢測一個可疑的電子郵件地址 Y 是否在黑名單中的。咱們用 8 個隨機數產生器(F1, F2, …, F8)對這個地址產生 8 個信息指紋 S1, S2, …, S8。 而後將這 8 個指紋對應到布隆過濾器的 8 個二進制位,分別是 T1, T2, …, T8。若是 Y 在黑名單中,顯然,T1, T2, …, T8 對應的 8 個二進制位必定是 1

  可是,它有一條不足之處。也就是它有極小的可能將一個不在黑名單中的電子郵件地址斷定爲在黑名單中,由於有可能某個好的郵件地址正巧對應 8 個都被設置成 1 的二進制位。好在這種可能性很小。咱們把它稱爲誤識機率。常見的補救辦法是創建一個小的白名單,存儲那些可能誤判的郵件地址。

 

  7.爬蟲架構

  一個設計良好的爬蟲架構必須知足以下需求。
  (1) 分佈式:爬蟲應該可以在多臺機器上分佈執行。
  (2) 可伸縮性:爬蟲結構應該可以經過增長額外的機器和帶寬來提升抓取速度。
  (3) 性能和有效性:爬蟲系統必須有效地使用各類系統資源,例如,處理器、存儲空間和網絡帶寬。
  (4) 質量:鑑於互聯網的發展速度,大部分網頁都不可能及時出如今用戶查詢中,因此爬蟲應該首先抓取有用的網頁。
  (5) 新鮮性:在許多應用中,爬蟲應該持續運行而不是隻遍歷一次。
  (6) 更新:由於網頁會常常更新,例如論壇網站會常常有回帖。爬蟲應該取得已經獲取的頁面的新的拷貝。例如一個搜索引擎爬蟲要可以保證全文索引中包含每一個索引頁面的較新的狀態。對於搜索引擎爬蟲這樣連續的抓取,爬蟲訪問一個頁面的頻率應該和這個網頁的更新頻率一致。
  (7) 可擴展性:爲了可以支持新的數據格式和新的抓取協議,爬蟲架構應該設計成模塊化的形式。

  最主要的關注對象是爬蟲和存儲庫。其中的爬蟲部分階段性地抓取互聯網上的內容。存儲庫存儲爬蟲下載下來的網頁,是分佈式的和可擴展的存儲系統。在往存儲庫中加載新的內容時仍然能夠讀取存儲庫。

  下圖是單線程架構:

  

  (1) URL Frontier 包含爬蟲當前待抓取的 URL(對於持續更新抓取的爬蟲,之前已經抓取過的 URL 可能會回到 Frontier 重抓)。
  (2) DNS 解析模塊根據給定的 URL 決定從哪一個 Web 服務器獲取網頁。
  (3) 獲取模塊使用 HTTP 協議獲取 URL 表明的頁面。
  (4) 解析模塊提取文本和網頁的連接集合。
  (5) 重複消除模塊決定一個解析出來的連接是否已經在 URL Frontier 或者最近下載過(檢查Visited)。

通用的爬蟲框架流程

       1)首先從互聯網頁面中精心選擇一部分網頁,以這些網頁的連接地址放在URL集中;

       2)將這些種子URL放入URL Frontier中;

       3)爬蟲從待抓取 URL隊列依次讀取,並將URL經過DNS解析,把連接地址轉換爲網站服務器對應的IP地址。 

       4)而後將IP地址和網頁相對路徑名稱交給網頁下載器,

       5)網頁下載器負責頁面內容的下載。

       6)對於下載到本地的網頁,一方面將其解析頁面,判斷內容是否重複,放入文檔數據庫中;另外一方面將下載網頁的 URL放入Visited中,這個隊列記載了爬蟲系統己經下載過的網頁URL,以免網頁的重複抓取。

       7)對於剛下載的網頁,從中抽取出所包含的全部連接信息,並在已抓取URL隊列中檢査,若是發現連接尚未被抓取過,則將這個URL放入URL Frontier!

       8,9)末尾,在以後的 抓取調度中會下載這個URL對應的網頁,如此這般,造成循環,直到待抓取URL隊列爲空.


  DNS 解析是網絡爬蟲的瓶頸。 因爲域名服務的分佈式特色, DNS 可能須要屢次請求轉發,並在互聯網上往返,須要幾秒有時甚至更長的時間解析出 IP 地址。若是咱們的目標是一秒鐘抓取數百個文件,這樣就達不到性能要求。一個標準的補救措施是引入緩存:最近完成 DNS 查詢的網址可能會在 DNS 緩存中找到,避免了訪問互聯網上的 DNS 服務器。然而,因爲抓取禮貌的限制,下降了 DNS 緩存的命中率。
  用 DNS 解析還有一個難點:在標準庫中實現的查找是同步的。這意味着一旦一個請求發送到 DNS 服務器上,在那個節點上的其餘爬蟲線程也被阻塞直到第一個請求完成。爲了不這種狀況發生,許多爬蟲本身來實現 DNS 解析

  對單線程並行抓取來講,異步 I/O 是很重要的基本功能。異步 I/O 模型大致上能夠分爲兩種,反應式(Reactive)模型和前攝式(Proactive)模型。

  傳統的 select/epoll/kqueue 模型以及 Java NIO 模型,都是典型的反應式模型,即應用代碼對 I/O 描述符進行註冊,而後等待 I/O 事件。 當某個或某些 I/O 描述符所對應的 I/O 設備上產生 I/O 事件(可讀、 可寫、異常等)時,系統將發出通知,因而應用便有機會進行 I/O 操做並避免阻塞。因爲在反應式模型中應用代碼須要根據相應的事件類型採起不一樣的動做,所以最多見的結構即是嵌套的if {…} else {…} 或 switch,並經常須要結合狀態機來完成複雜的邏輯。

  前攝式模型則偏偏相反。 在前攝式模型中, 應用代碼主動投遞異步操做而無論 I/O 設備當前是否可讀或可寫。投遞的異步 I/O 操做被系統接管,應用代碼也並不阻塞在該操做上,而是指定一個回調函數並繼續本身的應用邏輯。當該異步操做完成時,系統將發起通知並調用應用代碼指定的回調函數。在前攝式模型中,程序邏輯由各個回調函數串聯起來:異步操做 A 的回調發起異步操做 B,B 的回調再發起異步操做 C,以此往復。

  8.MapReduce執行爬蟲

MapReduce流程:

  (1) MapReduce函數庫首先把輸入文件分紅M塊,每塊大概16MB到64MB。接着在集羣的機器上執行處理程序。MapReduce算法運行過程當中有一個主控程序,稱爲master。主控程序會產生不少做業程序,稱爲worker。而且把M個map任務和R個reduce任務分配給這些worker,讓它們去完成。
  (2) 被分配了map任務的worker讀取並處理相關的輸入(這裏的輸入是指已經被切割的輸入小塊splite)。它處理輸入的數據,而且將分析出的鍵/值(key/value)對傳遞給用戶定義的reduce()函數。map()函數產生的中間結果鍵/值(key/value)對暫時緩衝到內存。
  (3) map()函數緩衝到內存的中間結果將被定時刷寫到本地硬盤,這些數據經過分區函數分紅R個區。這些中間結果在本地硬盤的位置信息將被髮送回master,而後這個master負責把這些位置信息傳送給reduce()函數的worker。
  (4) 當master通知了reduce()函數的worker關於中間鍵/值(key/value)對的位置時,worker調用遠程方法從map()函數的worker機器的本地硬盤上讀取緩衝的中間數據。當reduce()函數的worker讀取到了全部的中間數據,它就使用這些中間數據的鍵(key)進行排序,這樣可使得相同鍵(key)的值都在一塊兒。若是中間結果集太大了,那麼就須要使用外排序。
  (5) reduce()函數的worker根據每個中間結果的鍵(key)來遍歷排序後的數據,而且把鍵(key)和相關的中間結果值(value)集合傳遞給reduce()函數。reduce()函數的worker最終把輸出結果存放在master機器的一個輸出文件中。
  (6) 當全部的map任務和reduce任務都已經完成後,master激活用戶程序。在這時,MapReduce返回用戶程序的調用點。
  (7) 當以上步驟成功結束之後,MapReduce的執行數據存放在總計R個輸出文件中(每一個輸出文件都是由reduce任務產生的,這些文件名是用戶指定的)。一般,用戶不須要將這R個輸出文件合併到一個文件,他們一般把這些文件做爲輸入傳遞給另外一個MapReduce調用,或者用另外一個分佈式應用來處理這些文件,而且這些分佈式應用把這些文件當作爲輸入文件因爲分區(partition)成爲的多個塊文件。

爬蟲利用MapReduce流程:

1) 插入URL列表(inject)
  MapReduce程序1:
    目標:轉換input輸入爲CrawlDatum格式
    輸入:URL文件
    步驟:
      (1) map(line) →<url, CrawlDatum>
      (2) reduce()合併多重的URL
    輸出:臨時的CrawlDatum文件
  MapReduce程序2:
    目標:合併上一步產生的臨時文件到新的CrawlDB
    輸入:上次MapReduce輸出的CrawlDatum
    步驟:
      (1) map()過濾重複的URL
      (2) reduce:合併兩個CrawlDatum到一個新的CrawlDB
    輸出:CrawlDatum
2) 生成抓取列表(Generate)
  MapReduce程序:
    目標:選擇抓取列表
    輸入:CrawlDB文件
    步驟:
      (1) map() → 若是抓取當前時間大於如今時間,轉換成<CrawlDatum,URL>格式
      (2) reduce:取最頂部的N個連接
    輸出:< URL,CrawlDatum>文件
3) 抓取內容(Fetch)
  MapReduce程序:
    目標:抓取內容
    輸入:< URL,CrawlDatum>,按主機劃分,按hash排序
    步驟:
      (1) map(URL,CrawlDatum) → 輸出<URL,FetcherOutput>
      (2) 多線程,調用Nutch的抓取協議插件,抓取輸出<CrawlDatum, Content>
    輸出:<URL,CrawlDatum>和<URL,Content>兩個文件
4) 分析處理內容(Parse)
  MapReduce程序:
    目標:處理抓取的內容
    輸入:抓取的< URL, Content>
    步驟:
      (1) map(URL,Content) →<URL,Parse>
      (2) raduce()函數調用Nutch的解析插件,輸出處理完的格式是<ParseText,ParseData>
    輸出:< URL,ParseText>、<URL,ParseData>、<URL,CrawlDatum>
5) 更新CrawlDB庫(updateDB)
  MapReduce程序:
    目標:將fetch和parse整合到DB中
    輸入:<URL, CrawlDatum> 現有的DB加上fetch和parse的輸出,合併上面3個DB爲一個新的DB
    輸出:新的抓取DB
6) 創建索引(index)
  MapReduce程序:
    目標:生成Lucene索引
    輸入:多種文件格式
    步驟:
      (1) parse處理完的<URL,ParseData> 提取title、metadata信息等
      (2) parse處理完的<URL,ParseText> 提取text內容
      (3) 轉換連接處理完的<URL,Inlinks> 提取anchors
      (4) 抓取內容處理完的<URL,CrawlDatum> 提取抓取時間
      (5) map()函數用ObjectWritable包裹上面的內容
      (6) reduce()函數調用Nutch的索引插件,生成Lucene Document文檔
    輸出:輸出Lucene索引

  二.分佈式爬蟲

  1.分佈式存儲

  使用集羣的方法有不少種,但大體分爲兩類:一類仍然採用關係數據庫管理系統(RDBMS),而後經過對數據庫的垂直和水平切割將整個數據庫部署到一個集羣上,這種方法的優勢在於能夠採用RDBMS這種熟悉的技術,但缺點在於它是針對特定應用的。因爲應用的不一樣,切割的方法是不同的。關於數據庫的垂直和水平切割的具體細節能夠查看相關資料。
  還有一類就是Google所採用的方法,拋棄RDBMS,採用key/value形式的存儲,這樣能夠極大地加強系統的可擴展性(scalability),若是要處理的數據量持續增大,多加機器就能夠了。好比在key/value中想要查找,只需輸入key就好了,而關係數據庫中,須要表,和其中一項,還有其它不少操做,關係數據庫都須要維護(建表,表中外鍵等等),規則。

  雲存儲簡單點說就是構建一個大型的存儲平臺給別人用,這也就意味着在這上面運行的應用實際上是不可控的。若是其中某個客戶的應用隨着用戶的增加而不斷增加時,雲存儲供應商是沒有辦法經過數據庫的切割來達到擴展的,由於這個數據是客戶的,供應商不瞭解這個數據天然就無法做出切割。在這種狀況下,key/value的存儲就是惟一的選擇了,由於這種條件下的可擴展性必須是自動完成的,不能有人工干預。 

  key/value存儲與RDBMS相比,一個很大的區別就是它沒有模式的概念。在RDBMS 中,模式所表明的其實就是對數據的約束,包括數據之間的關係(relationship)和數據的完整性(integrity),好比RDBMS中對於某個數據屬性會要求它的數據類型是肯定的(整數或者字符串等),數據的範圍也是肯定的(0~255),而這些在key/value存儲中都沒有。在key/value存儲中,對於某個key,value能夠是任意的數據類型

  在全部的RDBMS中,都是採用SQL語言對數據進行訪問。一方面,SQL對於數據的查詢功能很是強大;另外一方面,因爲全部的RDBMS都支持SQL查詢,因此可移植性很強。而在key/value 存儲中,對於數據的操做使用的都是自定義的一些API,並且支持的查詢也比較簡單。 

  所謂的可擴展性,其實包括兩方面內容。一方面,是指key/value存儲能夠支持極大的數據存儲。它的分佈式的架構決定了只要有更多的機器,就可以保證存儲更多的數據。另外一方面,是指它能夠支持數量不少的併發的查詢。對於RDBMS,通常幾百個併發的查詢就可讓它很吃力了,而一個key/value存儲,能夠很輕鬆地支持上千個併發查詢。

  key/value存儲的缺陷主要有兩點:
  * 因爲key/value存儲中沒有schema,因此它是不提供數據之間的關係和數據的完備性的,全部的這些東西都落到了應用程序一端,其實也就是開發人員的頭上。這無疑加劇了開發人員的負擔。
  * 在RDBMS中,須要設定各表之間的關係,這實際上是一個數據建模的過程(data modeling process)。當數據建模完成後,這個數據庫對於應用程序就是獨立的了,這就意味着其餘程序能夠在不改變數據模型的前提下使用相同的數據集。但在key/value存儲中,因爲沒有這樣一個數據模型,不一樣的應用程序須要重複進行這個過程
  *key/value存儲最大的一個缺點在於它的接口是不熟悉的。這阻礙了開發人員能夠快速而順利地用上它。固然,如今有種作法,就是在key/value存儲上再加上一個類SQL語句的抽象接口層,從而使得開發人員能夠用他們熟悉的方式(SQL)來操做key/value存儲。但因爲RDBMS和key/value存儲的底層實現有着很大的不一樣,這種抽象接口層或多或少仍是受到了限制。   

   Consistent Hash算法 參見

  http://www.cnblogs.com/jslee/p/3444887.html

  2.GFS(google file system)

  在分佈式存儲環境中,經常會產生如下一些問題。

  1. 在分佈式存儲中,常常會出現節點失效的狀況

  2. 分佈式存儲的文件都是很是巨大的

  3. 對於搜索引擎的業務而言,大部分文件都只會在文件尾新增長數據,而少見修改已有數據的

  4. 與應用一塊兒設計的文件系統API對於增長整個系統的彈性和適用性有很大的好處

  5. 系統必須很是有效地支持多個客戶端並行添加同一個文件GFC文件常用生產者/消費者隊列模式,或者以多路合併模式進行操做。好幾百個運行在不一樣機器上的生產者,將會並行增長一個文件。
  6. 高性能的穩定帶寬的網絡要比低延時更加劇要GFS目標應用程序通常會大量操做處理比較大塊的數據。

  爲了知足以上幾點須要,Google遵守下面幾條原則設計了它的分佈式文件系統(GFS):
  (1) 系統創建在大量廉價的普通計算機上,這些計算機常常出故障。則必須對這些計算機進行持續檢測,而且在系統的基礎上進行檢查、容錯,以及從故障中進行恢復。
  (2) 系統存儲了大量的超大文件。數GB的文件常常出現而且應當對大文件進行有效的管理。同時必須支持小型文件,可是沒必要爲小型文件進行特別的優化。
  (3) 通常的工做都是由兩類讀取組成:大的流式讀取和小規模的隨機讀取。在大的流式讀取中,每一個讀操做一般一次就要讀取幾百字節以上的數據,每次讀取1MB或者以上的數據也很常見。所以,在大的流式讀取中,對於同一個客戶端來講,每每會發起連續的讀取操做順序讀取一個文件。而小規模的隨機讀取一般在文件的不一樣位置,每次讀取幾字節數據。對於性能有過特別考慮的應用一般會作批處理而且對它們讀取的內容進行排序,這樣可使得它們的讀取始終是單向順序讀取,而不須要往回讀取數據。
  (4) 一般基於GFS的操做都有不少超大的、順序寫入的文件操做。一般寫入操做的數據量和讀入的數據量至關。一旦完成寫入,文件就不多會更改。應支持文件的隨機小規模寫入,可是不須要爲此作特別的優化。

  

  在GFS下,每一個文件都被拆成固定大小的塊(chunk)。每個塊都由主服務器根據塊建立的時間產生一個全局惟一的之後不會改變的64位的塊處理(chunk handle)標誌。塊服務器在本地磁盤上用Linux文件系統保存這些塊,而且根據塊處理標誌和字節區間,經過Linux文件系統讀寫這些塊的數據。出於可靠性的考慮,每個塊都會在不一樣的塊處理器上保存備份。
  主服務器負責管理全部的文件系統的元數據,包括命名空間、訪問控制信息、文件到塊的映射關係、當前塊的位置等信息。主服務器也一樣控制系統級別的活動,好比塊的分配管理,孤點塊的垃圾回收機制、塊服務器之間的塊鏡像管理。

  鏈接到各個應用系統的GFS客戶端代碼包含了文件系統的API,而且會和主服務器及塊服務器進行通訊處理,表明應用程序進行讀寫數據的操做。客戶端和主服務器進行元數據的操做,可是全部的數據相關的通訊是直接和塊服務器進行的。
  因爲在流式讀取中,每次都要讀取很是多的文件內容,而且讀取動做是順序讀取,所以,在客戶端沒有設計緩存。沒有設計緩存系統使得客戶端以及整個系統都大大簡化了(少了緩存的同步機制)。塊服務器不須要緩存文件數據,由於塊文件就像本地文件同樣被保存,因此Linux的緩存已經把經常使用的數據緩存到了內存裏。

  在讀取時,首先,客戶端把應用要讀取的文件名和偏移量,根據固定的塊大小,轉換爲文件的塊索引。而後向主服務器發送這個包含了文件名和塊索引的請求。主服務器返回相關的塊處理標誌以及對應的位置。客戶端緩存這些信息,把文件名和塊索引做爲緩存的關鍵索引字。

  具體分佈式存儲可參見:

  http://www.cnblogs.com/jslee/p/3457475.html

  3.BigTable

  是一種key/value型分佈式數據庫系統。應用程序一般都不會直接操做GFS文件系統,而直接操做它的上一級存儲結構——BigTable。這正如通常文件系統和關係數據庫的道理同樣.

  BT在不少地方和關係數據庫相似:它採用了許多關係數據庫的實現策略。但和它們不一樣的是,BT採用了不一樣的用戶接口。BT不支持徹底的關係數據模型,而是爲客戶提供了簡單的數據模型,讓客戶來動態控制數據的分佈和格式(就是只存儲字符串,格式由客戶來解釋),這樣能大幅度地提升訪問速度。數據的下標是行和列的名字,數據自己能夠是任意字符串。BT的數據是字符串,沒有具體的類型。
  BT的本質是一個稀疏的、分佈式的、長期存儲的、多維度的和排序的Map。Map的key是行關鍵字(Row)、列關鍵字(Column)和時間戳(Timestamp)Value是一個普通的bytes數組。以下所示:
  (row:string, column:string,time:int64)->string

  BT經過行關鍵字在字典中的順序來維護數據。一張表能夠動態劃分紅多個連續「子表」(tablet)。這些「子表」由一些連續行組成,它是數據分佈和負載均衡的單位。這使得讀取較少的連續行比較有效率,一般只須要少許機器之間的通訊便可。用戶能夠利用這個屬性來選擇行關鍵字,從而達到較好的數據訪問「局部性」。舉例來講,在webtable中,經過反轉URL中主機名的方式,能夠把同一個域名下的網頁組織成連續行。具體而言,能夠把站點maps.google.com/index.html中的數據存放在關鍵字com.google.maps/index.html所對應的數據中。這種存放方式可讓基於主機和基於域名的分析更加有效。

  一組列關鍵字組成了「列族」(column famliy),這是訪問控制的基本單位。同一列族下存放的全部數據一般都是同一類型的。「列族」必須先建立,而後才能在其中的「列關鍵字」下存放數據。「列族」建立後,其中任何一個「列關鍵字」均可使用。「列關鍵字」用以下語法命名:「列族」:限定詞。「列族」名必須是看得懂的字符串,而限定詞能夠是任意字符串。好比,webtable能夠有個「列族」叫language,存放撰寫網頁的語言。咱們在language「列族」中只用一個「列關鍵字」,用來存放網頁的語言標識符。該表的另外一個有用的「列族」是anchor。「列族」的每個「列關鍵字」表明一個錨連接,訪問控制、磁盤使用統計和內存使用統計,都可在「列族」這個層面進行。例子,可使用這些功能來管理不一樣應用:有的應用添加新的基本數據,有的讀取基本數據並建立引伸的「列族」,有的則只能瀏覽數據(甚至可能由於隱私權的緣由不能瀏覽全部數據)。
  BT表中的每個表項均可以包含同一數據的多個版本,由時間戳來索引。BT的時間戳是64位整型,表示準確到毫秒的「實時」。須要避免衝突的應用程序必須本身產生具備惟一性的時間戳。不一樣版本的表項內容按時間戳倒序排列,即最新的排在前面。
  BT使用Google分佈式文件系統(GFS)來存儲日誌和數據文件。一個BT集羣一般在一個共享的機器池中工做,池中的機器還運行着其餘分佈式應用,BT和其餘程序共享機器(BT的瓶頸是I/O內存,能夠和CPU要求高的程序並存)。BT依賴集羣管理系統來安排工做,在共享的機器上管理資源,處理失效機器並監視機器狀態。

  BT還依賴一個高度可用的分佈式數據鎖服務(Chubby)。一個Chubby 由5個「活躍」的備份構成,其中一個被這些備份選成主備份,而且處理請。這個服務只有在大多數備份都是「活躍」的而且互相通訊的時候,纔是「活躍」的。當有機器失效的時候,Chubby使用必定的算法來保證備份的一致性。Chubby提供了一個名字空間,裏面包括目錄和一系列文件。每一個目錄或者文件能夠當成一個鎖來用,讀寫文件操做都是原子操做。Chubby客戶端的程序庫提供了對Chubby文件的一致性緩存。每一個Chubby客戶維護一個和Chubby通訊的會話。若是客戶不能在必定時間內更新本身的會話,會話就失效了。當一個會話失效時,其擁有的鎖和打開的文件句柄都失效。Chubby客戶能夠在文件和目錄上登記回調函數,以得到改變或者會話過時的通知。BT使用鎖服務來完成如下幾個任務:
  (1) 保證任什麼時候間最多隻有一個活躍的主備份。
  (2) 存儲BT數據的啓動位置。
  (3) 發現「子表」服務器,並處理tablet服務器失效的狀況。
  (4) 存儲BT數據的「模式」信息(每張表的列信息)。
  (5) 存儲訪問權限列表。在Chubby中,存儲了BT的訪問權限,若是Chubby不能訪問,那麼因爲獲取不到訪問權限,所以BT就也不能訪問了。

  BT主要由三個構件組成:
  (1) 一個客戶端的連接庫。
  (2) 一個主服務器。
  (3) 許多「子表」服務器。「子表」服務器能夠動態地從羣組中被添加和刪除,以適應流量的改變。
  主服務器的做用是給「子表」服務器分配「子表」、探測「子表」服務器的增長和縮減、平衡「子表」服務器負載,以及回收GFS系統中文件的碎片。此外,它還能夠建立模式表。
  一個「子表」服務器管理許多子表(通常每一個「子表」服務器能夠管理10到1000個子表)。「子表」服務器處理它所管理的「子表」的讀寫請求,還能夠將那些變得很大的「子表」分割。
  像許多單主機的分佈式存儲系統同樣,客戶端數據不是經過主服務器來傳輸的:客戶端要讀寫時直接與「子表」服務器通訊。由於BT客戶端並不依賴主服務器來請求「子表」本地信息,大多數客戶端從不與主服務器通訊。所以,實際上主機的負載每每很小。一個BT羣組能夠存儲大量的表。每個表有許多「子表」,而且每一個「子表」包含一行上全部相關的數據。最初,每一個表只包含一個子表。隨着表的增加,自動分紅了許多的「子表」,每一個子表的默認大小爲100~200MB。
  BT用三層體系的B+樹來存儲子表的地址信息。
  第一層是一個存儲在Chubby中的文件,它包含「根子表」(root tablet)的地址。「根子表」包含一些「元數據表」(MetaData tablets)的地址信息。這些「元數據表」包含用戶「子表」的地址信息。「根子表」是「元數據表」中的第一個「子表」,但它從不會被分割。
  客戶端緩存「子表」地址。若是客戶端發現緩存的地址信息是錯誤的,那麼它會遞歸地提高「子表」地址等級。若是客戶端緩存是空的,尋址算法須要三個網絡往返過程,包括一次從Chubby的讀取。若是客戶端緩存是過時的,那麼尋址算法可能要用6個往返過程。
  儘管「子表」地址緩存在內存裏,不須要GFS訪問,但仍是能夠經過客戶端預提取「子表地址」進一步下降性能損耗。「子表」一次被分配給一個子表服務器。主服務器跟蹤「活躍」的「子表」服務器集合以及當前「子表」對「子表服務器」的分配情況。當一個「子表」尚未被分配,而且有一個「子表」服務器是可用的,主機就經過傳輸一個「子表」裝載請求到「子表」服務器來分配「子表」。
  BT使用Chubby來跟蹤「子表」服務器。當「子表」服務器啓動時,它在一個特別的Chubby目錄中建立一個文件,而且得到一個互斥鎖。主機經過監聽這個目錄(服務器目錄)來發現「子表」服務器。若是「子表」服務器丟失了本身的互斥鎖,就會中止爲它的「子表」服務。
  主機負責探測什麼時候「子表」服務器再也不爲它的「子表」服務,以即可以儘快地分配那些「子表」。爲了達到這個目的,主機會週期性地詢問每一個「子表」服務器的鎖的狀態。若是一個「子表」服務器報告它丟失了鎖,主機會嘗試在服務器的文件中獲取一把互斥鎖。若是主機能得到這把鎖,則表示Chubby是可用的而且「子表」服務器已經失效。所以主機經過刪除「子表」服務器的服務文件來確保它不會再工做。一旦服務文件被刪除,主機能夠把先前分配給這臺服務器的全部「子表」移到未分配的「子表」集合中。「子表」的持久化狀態存儲在GFS文件裏。
  每次更新「子表」前都要更新「子表」的重作日誌(redo log)。而且最近更新的內容(已經提交的但尚未寫入到磁盤的內容)會存放在內存中,稱爲memtable。以前的更新(已經提交的而且固化在磁盤的內容)會被持久化到一系列的SSTable中。當一個寫操做請求過來時,「子表」服務器會先寫日誌,當提交的時候,就把這些更新寫入memtable中。以後等系統不繁忙的時候,就寫入SSTable中(這個過程和Oracle數據庫寫操做基本一致)。
  若是請求是讀操做,則能夠根據當前的memtable和SSTable中的內容進行合併,而後對請求返回結果。由於memtable和SSTable有相同的結構,所以,合併是一個很是快的操做。

  4.MapReduce

  參見:書。

 

參考:本身動手寫網絡爬蟲

相關文章
相關標籤/搜索