• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            不會飛的鳥

            2010年12月10日 ... 不鳥他們!!! 我要用自己開發的分布式文件系統、分布式調度系統、分布式檢索系統, 做自己的搜索引擎!!!大魚有大志!!! ---楊書童

            #

            Linux命令screen用法總結

            screen    進入screen模式
            C-a c    在當前screen下建立新的窗口
            exit    退出當前窗口,如果它是此screen的唯一窗口時,此screen也將完全退出。
            C-a d     暫時斷開screen會話
            screen -ls    查看有哪些screen。
            screen -r id 打開編號為id的screen窗口。
            C-a w    顯示所有窗口列表
            C-a n    切換到下一個窗口
            C-a p    切換到前一個窗口(與C-a n相對)

            最無敵命令 screen --help

            posted @ 2010-04-27 15:01 不會飛的鳥 閱讀(2424) | 評論 (0)編輯 收藏

            Hadoop分布式文件系統:架構和設計要點

            原文:http://hadoop.apache.org/core/docs/current/hdfs_design.html
            一、前提和設計目標
            1、硬件錯誤是常態,而非異常情況,HDFS可能是有成百上千的server組成,任何一個組件都有可能一直失效,因此錯誤檢測和快速、自動的恢復是HDFS的核心架構目標。
            2、跑在HDFS上的應用與一般的應用不同,它們主要是以流式讀為主,做批量處理;比之關注數據訪問的低延遲問題,更關鍵的在于數據訪問的高吞吐量。
            3HDFS以支持大數據集合為目標,一個存儲在上面的典型文件大小一般都在千兆至T字節,一個單一HDFS實例應該能支撐數以千萬計的文件。
            4HDFS應用對文件要求的是write-one-read-many訪問模型。一個文件經過創建、寫,關閉之后就不需要改變。這一假設簡化了數據一致性問題,使高吞吐量的數據訪問成為可能。典型的如MapReduce框架,或者一個web crawler應用都很適合這個模型。
            5、移動計算的代價比之移動數據的代價低。一個應用請求的計算,離它操作的數據越近就越高效,這在數據達到海量級別的時候更是如此。將計算移動到數據附近,比之將數據移動到應用所在顯然更好,HDFS提供給應用這樣的接口。
            6、在異構的軟硬件平臺間的可移植性。

            二、NamenodeDatanode
                HDFS采用master/slave架構。一個HDFS集群是有一個Namenode和一定數目的Datanode組成。Namenode是一個中心服務器,負責管理文件系統的namespace和客戶端對文件的訪問。Datanode在集群中一般是一個節點一個,負責管理節點上它們附帶的存儲。在內部,一個文件其實分成一個或多個block,這些block存儲在Datanode集合里。Namenode執行文件系統的namespace操作,例如打開、關閉、重命名文件和目錄,同時決定block到具體Datanode節點的映射。DatanodeNamenode的指揮下進行block的創建、刪除和復制。NamenodeDatanode都是設計成可以跑在普通的廉價的運行linux的機器上。HDFS采用java語言開發,因此可以部署在很大范圍的機器上。一個典型的部署場景是一臺機器跑一個單獨的Namenode節點,集群中的其他機器各跑一個Datanode實例。這個架構并不排除一臺機器上跑多個Datanode,不過這比較少見。

            單一節點的Namenode大大簡化了系統的架構。Namenode負責保管和管理所有的HDFS元數據,因而用戶數據就不需要通過Namenode(也就是說文件數據的讀寫是直接在Datanode上)。

            三、文件系統的namespace
               HDFS支持傳統的層次型文件組織,與大多數其他文件系統類似,用戶可以創建目錄,并在其間創建、刪除、移動和重命名文件。HDFS不支持user quotas和訪問權限,也不支持鏈接(link),不過當前的架構并不排除實現這些特性。Namenode維護文件系統的namespace,任何對文件系統namespace和文件屬性的修改都將被Namenode記錄下來。應用可以設置HDFS保存的文件的副本數目,文件副本的數目稱為文件的 replication因子,這個信息也是由Namenode保存。

            四、數據復制
                HDFS被設計成在一個大集群中可以跨機器地可靠地存儲海量的文件。它將每個文件存儲成block序列,除了最后一個block,所有的block都是同樣的大小。文件的所有block為了容錯都會被復制。每個文件的block大小和replication因子都是可配置的。Replication因子可以在文件創建的時候配置,以后也可以改變。HDFS中的文件是write-one,并且嚴格要求在任何時候只有一個writerNamenode全權管理block的復制,它周期性地從集群中的每個Datanode接收心跳包和一個Blockreport。心跳包的接收表示該Datanode節點正常工作,而Blockreport包括了該Datanode上所有的block組成的列表。


            1、副本的存放,副本的存放是HDFS可靠性和性能的關鍵。HDFS采用一種稱為rack-aware的策略來改進數據的可靠性、有效性和網絡帶寬的利用。這個策略實現的短期目標是驗證在生產環境下的表現,觀察它的行為,構建測試和研究的基礎,以便實現更先進的策略。龐大的HDFS實例一般運行在多個機架的計算機形成的集群上,不同機架間的兩臺機器的通訊需要通過交換機,顯然通常情況下,同一個機架內的兩個節點間的帶寬會比不同機架間的兩臺機器的帶寬大。
                通過一個稱為Rack Awareness的過程,Namenode決定了每個Datanode所屬的rack id。一個簡單但沒有優化的策略就是將副本存放在單獨的機架上。這樣可以防止整個機架(非副本存放)失效的情況,并且允許讀數據的時候可以從多個機架讀取。這個簡單策略設置可以將副本分布在集群中,有利于組件失敗情況下的負載均衡。但是,這個簡單策略加大了寫的代價,因為一個寫操作需要傳輸block到多個機架。
                在大多數情況下,replication因子是3HDFS的存放策略是將一個副本存放在本地機架上的節點,一個副本放在同一機架上的另一個節點,最后一個副本放在不同機架上的一個節點。機架的錯誤遠遠比節點的錯誤少,這個策略不會影響到數據的可靠性和有效性。三分之一的副本在一個節點上,三分之二在一個機架上,其他保存在剩下的機架中,這一策略改進了寫的性能。

            2、副本的選擇,為了降低整體的帶寬消耗和讀延時,HDFS會盡量讓reader讀最近的副本。如果在reader的同一個機架上有一個副本,那么就讀該副本。如果一個HDFS集群跨越多個數據中心,那么reader也將首先嘗試讀本地數據中心的副本。

            3SafeMode
                Namenode啟動后會進入一個稱為SafeMode的特殊狀態,處在這個狀態的Namenode是不會進行數據塊的復制的。Namenode從所有的 Datanode接收心跳包和BlockreportBlockreport包括了某個Datanode所有的數據塊列表。每個block都有指定的最小數目的副本。當Namenode檢測確認某個Datanode的數據塊副本的最小數目,那么該Datanode就會被認為是安全的;如果一定百分比(這個參數可配置)的數據塊檢測確認是安全的,那么Namenode將退出SafeMode狀態,接下來它會確定還有哪些數據塊的副本沒有達到指定數目,并將這些block復制到其他Datanode

            五、文件系統元數據的持久化
                Namenode存儲HDFS的元數據。對于任何對文件元數據產生修改的操作,Namenode都使用一個稱為Editlog的事務日志記錄下來。例如,在HDFS中創建一個文件,Namenode就會在Editlog中插入一條記錄來表示;同樣,修改文件的replication因子也將往 Editlog插入一條記錄。Namenode在本地OS的文件系統中存儲這個Editlog。整個文件系統的namespace,包括block到文件的映射、文件的屬性,都存儲在稱為FsImage的文件中,這個文件也是放在Namenode所在系統的文件系統上。
                Namenode在內存中保存著整個文件系統namespace和文件Blockmap的映像。這個關鍵的元數據設計得很緊湊,因而一個帶有4G內存的 Namenode足夠支撐海量的文件和目錄。當Namenode啟動時,它從硬盤中讀取EditlogFsImage,將所有Editlog中的事務作用(apply)在內存中的FsImage ,并將這個新版本的FsImage從內存中flush到硬盤上,然后再truncate這個舊的Editlog,因為這個舊的Editlog的事務都已經作用在FsImage上了。這個過程稱為checkpoint。在當前實現中,checkpoint只發生在Namenode啟動時,在不久的將來我們將實現支持周期性的checkpoint
                Datanode并不知道關于文件的任何東西,除了將文件中的數據保存在本地的文件系統上。它把每個HDFS數據塊存儲在本地文件系統上隔離的文件中。 Datanode并不在同一個目錄創建所有的文件,相反,它用啟發式地方法來確定每個目錄的最佳文件數目,并且在適當的時候創建子目錄。在同一個目錄創建所有的文件不是最優的選擇,因為本地文件系統可能無法高效地在單一目錄中支持大量的文件。當一個Datanode啟動時,它掃描本地文件系統,對這些本地文件產生相應的一個所有HDFS數據塊的列表,然后發送報告到Namenode,這個報告就是Blockreport

            六、通訊協議
                所有的HDFS通訊協議都是構建在TCP/IP協議上。客戶端通過一個可配置的端口連接到Namenode,通過ClientProtocolNamenode交互。而Datanode是使用DatanodeProtocolNamenode交互。從ClientProtocolDatanodeprotocol抽象出一個遠程調用(RPC),在設計上,Namenode不會主動發起RPC,而是是響應來自客戶端和 Datanode RPC請求。

            七、健壯性
                HDFS的主要目標就是實現在失敗情況下的數據存儲可靠性。常見的三種失敗:Namenode failures, Datanode failures和網絡分割(network partitions)
            1、硬盤數據錯誤、心跳檢測和重新復制
                每個Datanode節點都向Namenode周期性地發送心跳包。網絡切割可能導致一部分DatanodeNamenode失去聯系。 Namenode通過心跳包的缺失檢測到這一情況,并將這些Datanode標記為dead,不會將新的IO請求發給它們。寄存在dead Datanode上的任何數據將不再有效。Datanode的死亡可能引起一些block的副本數目低于指定值,Namenode不斷地跟蹤需要復制的 block,在任何需要的情況下啟動復制。在下列情況可能需要重新復制:某個Datanode節點失效,某個副本遭到損壞,Datanode上的硬盤錯誤,或者文件的replication因子增大。

            2、集群均衡
               HDFS支持數據的均衡計劃,如果某個Datanode節點上的空閑空間低于特定的臨界點,那么就會啟動一個計劃自動地將數據從一個Datanode搬移到空閑的Datanode。當對某個文件的請求突然增加,那么也可能啟動一個計劃創建該文件新的副本,并分布到集群中以滿足應用的要求。這些均衡計劃目前還沒有實現。

            3、數據完整性
              從某個Datanode獲取的數據塊有可能是損壞的,這個損壞可能是由于Datanode的存儲設備錯誤、網絡錯誤或者軟件bug造成的。HDFS客戶端軟件實現了HDFS文件內容的校驗和。當某個客戶端創建一個新的HDFS文件,會計算這個文件每個block的校驗和,并作為一個單獨的隱藏文件保存這些校驗和在同一個HDFS namespace下。當客戶端檢索文件內容,它會確認從Datanode獲取的數據跟相應的校驗和文件中的校驗和是否匹配,如果不匹配,客戶端可以選擇從其他Datanode獲取該block的副本。

            4、元數據磁盤錯誤
                FsImageEditlogHDFS的核心數據結構。這些文件如果損壞了,整個HDFS實例都將失效。因而,Namenode可以配置成支持維護多個FsImageEditlog的拷貝。任何對FsImage或者Editlog的修改,都將同步到它們的副本上。這個同步操作可能會降低 Namenode每秒能支持處理的namespace事務。這個代價是可以接受的,因為HDFS是數據密集的,而非元數據密集。當Namenode重啟的時候,它總是選取最近的一致的FsImageEditlog使用。
               NamenodeHDFS是單點存在,如果Namenode所在的機器錯誤,手工的干預是必須的。目前,在另一臺機器上重啟因故障而停止服務的Namenode這個功能還沒實現。

            5、快照
               快照支持某個時間的數據拷貝,當HDFS數據損壞的時候,可以恢復到過去一個已知正確的時間點。HDFS目前還不支持快照功能。

            八、數據組織
            1、數據塊
                兼容HDFS的應用都是處理大數據集合的。這些應用都是寫數據一次,讀卻是一次到多次,并且讀的速度要滿足流式讀。HDFS支持文件的write- once-read-many語義。一個典型的block大小是64MB,因而,文件總是按照64M切分成chunk,每個chunk存儲于不同的 Datanode
            2、步驟
                某個客戶端創建文件的請求其實并沒有立即發給Namenode,事實上,HDFS客戶端會將文件數據緩存到本地的一個臨時文件。應用的寫被透明地重定向到這個臨時文件。當這個臨時文件累積的數據超過一個block的大小(默認64M),客戶端才會聯系NamenodeNamenode將文件名插入文件系統的層次結構中,并且分配一個數據塊給它,然后返回Datanode的標識符和目標數據塊給客戶端。客戶端將本地臨時文件flush到指定的 Datanode上。當文件關閉時,在臨時文件中剩余的沒有flush的數據也會傳輸到指定的Datanode,然后客戶端告訴Namenode文件已經關閉。此時Namenode才將文件創建操作提交到持久存儲。如果Namenode在文件關閉前掛了,該文件將丟失。
               上述方法是對通過對HDFS上運行的目標應用認真考慮的結果。如果不采用客戶端緩存,由于網絡速度和網絡堵塞會對吞估量造成比較大的影響。

            3、流水線復制
                當某個客戶端向HDFS文件寫數據的時候,一開始是寫入本地臨時文件,假設該文件的replication因子設置為3,那么客戶端會從Namenode 獲取一張Datanode列表來存放副本。然后客戶端開始向第一個Datanode傳輸數據,第一個Datanode一小部分一小部分(4kb)地接收數據,將每個部分寫入本地倉庫,并且同時傳輸該部分到第二個Datanode節點。第二個Datanode也是這樣,邊收邊傳,一小部分一小部分地收,存儲在本地倉庫,同時傳給第三個Datanode,第三個Datanode就僅僅是接收并存儲了。這就是流水線式的復制。

            九、可訪問性
                HDFS給應用提供了多種訪問方式,可以通過DFSShell通過命令行與HDFS數據進行交互,可以通過java API調用,也可以通過C語言的封裝API訪問,并且提供了瀏覽器訪問的方式。正在開發通過WebDav協議訪問的方式。具體使用參考文檔。
            十、空間的回收
            1、文件的刪除和恢復
                用戶或者應用刪除某個文件,這個文件并沒有立刻從HDFS中刪除。相反,HDFS將這個文件重命名,并轉移到/trash目錄。當文件還在/trash目錄時,該文件可以被迅速地恢復。文件在/trash中保存的時間是可配置的,當超過這個時間,Namenode就會將該文件從namespace中刪除。文件的刪除,也將釋放關聯該文件的數據塊。注意到,在文件被用戶刪除和HDFS空閑空間的增加之間會有一個等待時間延遲。
                當被刪除的文件還保留在/trash目錄中的時候,如果用戶想恢復這個文件,可以檢索瀏覽/trash目錄并檢索該文件。/trash目錄僅僅保存被刪除文件的最近一次拷貝。/trash目錄與其他文件目錄沒有什么不同,除了一點:HDFS在該目錄上應用了一個特殊的策略來自動刪除文件,目前的默認策略是刪除保留超過6小時的文件,這個策略以后會定義成可配置的接口。

            2Replication因子的減小
                當某個文件的replication因子減小,Namenode會選擇要刪除的過剩的副本。下次心跳檢測就將該信息傳遞給DatanodeDatanode就會移除相應的block并釋放空間,同樣,在調用setReplication方法和集群中的空閑空間增加之間會有一個時間延遲。

            參考資料:
            HDFS Java API: http://hadoop.apache.org/core/docs/current/api/
            HDFS source code: http://hadoop.apache.org/core/version_control.html

            posted @ 2010-03-24 23:55 不會飛的鳥 閱讀(284) | 評論 (0)編輯 收藏

            【轉載】分布式基礎學習【二】 —— 分布式計算系統(Map/Reduce)

            二. 分布式計算(Map/Reduce)

            分布式式計算,同樣是一個寬泛的概念,在這里,它狹義的指代,按Google Map/Reduce框架所設計的分布式框架。在Hadoop中,分布式文件系統,很大程度上,是為各種分布式計算需求所服務的。我們說分布式文件系統就是加了分布式的文件系統,類似的定義推廣到分布式計算上,我們可以將其視為增加了分布式支持的計算函數。從計算的角度上看,Map/Reduce框架接受各種格式的鍵值對文件作為輸入,讀取計算后,最終生成自定義格式的輸出文件。而從分布式的角度上看,分布式計算的輸入文件往往規模巨大,且分布在多個機器上,單機計算完全不可支撐且效率低下,因此Map/Reduce框架需要提供一套機制,將此計算擴展到無限規模的機器集群上進行。依照這樣的定義,我們對整個Map/Reduce的理解,也可以分別沿著這兩個流程去看。。。
            在Map/Reduce框架中,每一次計算請求,被稱為作業。在分布式計算Map/Reduce框架中,為了完成這個作業,它進行兩步走的戰略,首先是將其拆分成若干個Map任務,分配到不同的機器上去執行,每一個Map任務拿輸入文件的一部分作為自己的輸入,經過一些計算,生成某種格式的中間文件,這種格式,與最終所需的文件格式完全一致,但是僅僅包含一部分數據。因此,等到所有Map任務完成后,它會進入下一個步驟,用以合并這些中間文件獲得最后的輸出文件。此時,系統會生成若干個Reduce任務,同樣也是分配到不同的機器去執行,它的目標,就是將若干個Map任務生成的中間文件為匯總到最后的輸出文件中去。當然,這個匯總不總會像1 + 1 = 2那么直接了當,這也就是Reduce任務的價值所在。經過如上步驟,最終,作業完成,所需的目標文件生成。整個算法的關鍵,就在于增加了一個中間文件生成的流程,大大提高了靈活性,使其分布式擴展性得到了保證。。。

            I. 術語對照

            和分布式文件系統一樣,Google、Hadoop和....我,各執一種方式表述統一概念,為了保證其統一性,特有下表。。。

            文中翻譯 Hadoop術語 Google術語 相關解釋
            作業 Job Job 用戶的每一個計算請求,就稱為一個作業。
            作業服務器 JobTracker Master 用戶提交作業的服務器,同時,它還負責各個作業任務的分配,管理所有的任務服務器。
            任務服務器 TaskTracker Worker 任勞任怨的工蜂,負責執行具體的任務。
            任務 Task Task 每一個作業,都需要拆分開了,交由多個服務器來完成,拆分出來的執行單位,就稱為任務。
            備份任務 Speculative Task Buckup Task 每一個任務,都有可能執行失敗或者緩慢,為了降低為此付出的代價,系統會未雨綢繆的實現在另外的任務服務器上執行同樣一個任務,這就是備份任務。

            II. 基本架構

            與分布式文件系統類似,Map/Reduce的集群,也由三類服務器構成。其中作業服務器,在Hadoop中稱為Job Tracker,在Google論文中稱為Master。前者告訴我們,作業服務器是負責管理運行在此框架下所有作業的,后者告訴我們,它也是為各個作業分配任務的核心。與HDFS的主控服務器類似,它也是作為單點存在的,簡化了負責的同步流程。具體的負責執行用戶定義操作的,是任務服務器,每一個作業被拆分成很多的任務,包括Map任務Reduce任務等,任務是具體執行的基本單元,它們都需要分配到合適任務服務器上去執行,任務服務器一邊執行一邊向作業服務器匯報各個任務的狀態,以此來幫助作業服務器了解作業執行的整體情況,分配新的任務等等。。。
            除了作業的管理者執行者,還需要有一個任務的提交者,這就是客戶端。與分布式文件系統一樣,客戶端也不是一個單獨的進程,而是一組API,用戶需要自定義好自己需要的內容,經由客戶端相關的代碼,將作業及其相關內容和配置,提交到作業服務器去,并時刻監控執行的狀況。。。
            同作為Hadoop的實現,與HDFS的通信機制相同,Hadoop Map/Reduce也是用了協議接口來進行服務器間的交流。實現者作為RPC服務器,調用者經由RPC的代理進行調用,如此,完成大部分的通信,具體服務器的架構,和其中運行的各個協議狀況,參見下圖。從圖中可以看到,與HDFS相比,相關的協議少了幾個,客戶端與任務服務器,任務服務器之間,都不再有直接通信關系。這并不意味著客戶端就不需要了解具體任務的執行狀況,也不意味著,任務服務器之間不需要了解別家任務執行的情形,只不過,由于整個集群各機器的聯系比HDFS復雜的多,直接通信過于的難以維系,所以,都統一由作業服務器整理轉發。另外,從這幅圖可以看到,任務服務器不是一個人在戰斗,它會像孫悟空一樣招出一群寶寶幫助其具體執行任務。這樣做的好處,個人覺得,應該有安全性方面的考慮,畢竟,任務的代碼是用戶提交的,數據也是用戶指定的,這質量自然良莠不齊,萬一碰上個搞破壞的,把整個任務服務器進程搞死了,就因小失大了。因此,放在單獨的地盤進行,愛咋咋地,也算是權責明確了。。。
            與分布式文件系統相比,Map/Reduce框架的還有一個特點,就是可定制性強。文件系統中很多的算法,都是很固定和直觀的,不會由于所存儲的內容不同而有太多的變化。而作為通用的計算框架,需要面對的問題則要復雜很多,在各種不同的問題、不同的輸入、不同的需求之間,很難有一種包治百病的藥能夠一招鮮吃遍天。作為Map/Reduce框架而言,一方面要盡可能的抽取出公共的一些需求,實現出來。更重要的,是需要提供良好的可擴展機制,滿足用戶自定義各種算法的需求。Hadoop是由Java來實現的,因此通過反射來實現自定義的擴展,顯得比較小菜一碟了。在JobConf類中,定義了大量的接口,這基本上是Hadoop Map/Reduce框架所有可定制內容的一次集中展示。在JobConf中,有大量set接口接受一個Class<? extends xxx>的參數,通常它都有一個默認實現的類,用戶如果不滿意,則可自定義實現。。。

            III. 計算流程

            如果一切都按部就班的進行,那么整個作業的計算流程,應該是作業的提交 -> Map任務的分配和執行 -> Reduce任務的分配和執行 -> 作業的完成。而在每個任務的執行中,又包含輸入的準備 -> 算法的執行 -> 輸出的生成,三個子步驟。沿著這個流程,我們可以很快的整理清晰整個Map/Reduce框架下作業的執行。。。

            1、作業的提交

            一個作業,在提交之前,需要把所有應該配置的東西都配置好,因為一旦提交到了作業服務器上,就陷入了完全自動化的流程,用戶除了觀望,最多也就能起一個監督作用,懲治一些不好好工作的任務。。。
            基本上,用戶在提交代碼階段,需要做的工作主要是這樣的:
            首先,書寫好所有自定的代碼,最起碼,需要有Map和Reduce的執行代碼。在Hadoop中,Map需要派生自Mapper<K1, V1, K2, V2>接口,Reduce需要派生自Reducer<K2, V2, K3, V3>接口。這里都是用的泛型,用以支持不同的鍵值類型。這兩個接口都僅有一個方法,一個是map,一個是reduce,這兩個方法都直接受四個參數,前兩個是輸入的相關的數據結構,第三個是作為輸出相關的數據結構,最后一個,是一個Reporter類的實例,實現的時候可以利用它來統計一些計數。除了這兩個接口,還有大量可以派生的接口,比如分割的Partitioner<K2, V2>接口。。。
            然后,需要書寫好主函數的代碼,其中最主要的內容就是實例化一個JobConf類的對象,然后調用其豐富的setXXX接口,設定好所需的內容,包括輸入輸出的文件路徑,Map和Reduce的類,甚至包括讀取寫入文件所需的格式支持類,等等。。。
            最后,調用JobClientrunJob方法,提交此JobConf對象。runJob方法會先行調用到JobSubmissionProtocol接口所定義的submitJob方法,將此作業,提交給作業服務器。接著,runJob開始循環,不停的調用JobSubmissionProtocol的getTaskCompletionEvents方法,獲得TaskCompletionEvent類的對象實例,了解此作業各任務的執行狀況。。。

            2、Map任務的分配

            當一個作業提交到了作業服務器上,作業服務器會生成若干個Map任務,每一個Map任務,負責將一部分的輸入轉換成格式與最終格式相同的中間文件。通常一個作業的輸入都是基于分布式文件系統的文件(當然在單機環境下,文件系統單機的也可以...),因為,它可以很天然的和分布式的計算產生聯系。而對于一個Map任務而言,它的輸入往往是輸入文件的一個數據塊,或者是數據塊的一部分,但通常,不跨數據塊。因為,一旦跨了數據塊,就可能涉及到多個服務器,帶來了不必要的復雜性。。。
            當一個作業,從客戶端提交到了作業服務器上,作業服務器會生成一個JobInProgress對象,作為與之對應的標識,用于管理。作業被拆分成若干個Map任務后,會預先掛在作業服務器上的任務服務器拓撲樹。這是依照分布式文件數據塊的位置來劃分的,比如一個Map任務需要用某個數據塊,這個數據塊有三份備份,那么,在這三臺服務器上都會掛上此任務,可以視為是一個預分配。。。
            關于任務管理和分配的大部分的真實功能和邏輯的實現,JobInProgress則依托JobInProgressListenerTaskScheduler的子類。TaskScheduler,顧名思義是用于任務分配的策略類(為了簡化描述,用它代指所有TaskScheduler的子類...)。它會掌握好所有作業的任務信息,其assignTasks函數,接受一個TaskTrackerStatus作為參數,依照此任務服務器的狀態和現有的任務狀況,為其分配新的任務。而為了掌握所有作業相關任務的狀況,TaskScheduler會將若干個JobInProgressListener注冊到JobTracker中去,當有新的作業到達、移除或更新的時候,JobTracker會告知給所有的JobInProgressListener,以便它們做出相應的處理。。。
            任務分配是一個重要的環節,所謂任務分配,就是將合適作業的合適任務分配到合適的服務器上。不難看出,里面蘊含了兩個步驟,先是選擇作業,然后是在此作業中選擇任務。和所有分配工作一樣,任務分配也是一個復雜的活。不良好的任務分配,可能會導致網絡流量增加、某些任務服務器負載過重效率下降,等等。不僅如此,任務分配還是一個無一致模式的問題,不同的業務背景,可能需要不同的算法才能滿足需求。因此,在Hadoop中,有很多TaskScheduler的子類,像Facebook,Yahoo,都為其貢獻出了自家用的算法。在Hadoop中,默認的任務分配器,是JobQueueTaskScheduler類。它選擇作業的基本次序是:Map Clean Up Task(Map任務服務器的清理任務,用于清理相關的過期的文件和環境...) -> Map Setup Task(Map任務服務器的安裝任務,負責配置好相關的環境...) -> Map Tasks -> Reduce Clean Up Task -> Reduce Setup Task -> Reduce Tasks。在這個前提下,具體到Map任務的分配上來。當一個任務服務器工作的游刃有余,期待獲得新的任務的時候,JobQueueTaskScheduler會按照各個作業的優先級,從最高優先級的作業開始分配。每分配一個,還會為其留出余量,已被不時之需。舉一個例子:系統目前有優先級3、2、1的三個作業,每個作業都有一個可分配的Map任務,一個任務服務器來申請新的任務,它還有能力承載3個任務的執行,JobQueueTaskScheduler會先從優先級3的作業上取一個任務分配給它,然后再留出一個1任務的余量。此時,系統只能在將優先級2作業的任務分配給此服務器,而不能分配優先級1的任務。這樣的策略,基本思路就是一切為高優先級的作業服務,優先分配不說,分配了好保留有余力以備不時之需,如此優待,足以讓高優先級的作業喜極而泣,讓低優先級的作業感慨既生瑜何生亮,甚至是活活餓死。。。
            確定了從哪個作業提取任務后,具體的分配算法,經過一系列的調用,最后實際是由JobInProgressfindNewMapTask函數完成的。它的算法很簡單,就是盡全力為此服務器非配且盡可能好的分配任務,也就是說,只要還有可分配的任務,就一定會分給它,而不考慮后來者。作業服務器會從離它最近的服務器開始,看上面是否還掛著未分配的任務(預分配上的),從近到遠,如果所有的任務都分配了,那么看有沒有開啟多次執行,如果開啟,考慮把未完成的任務再分配一次(后面有地方詳述...)。。。
            對于作業服務器來說,把一個任務分配出去了,并不意味著它就徹底解放,可以對此任務可以不管不顧了。因為任務可以在任務服務器上執行失敗,可能執行緩慢,這都需要作業服務器幫助它們再來一次。因此在Task中,記錄有一個TaskAttemptID,對于任務服務器而言,它們每次跑的,其實都只是一個Attempt而已,Reduce任務只需要采信一個的輸出,其他都算白忙乎了。。。

            3、Map任務的執行

            與HDFS類似,任務服務器是通過心跳消息,向作業服務器匯報此時此刻其上各個任務執行的狀況,并向作業服務器申請新的任務的。具體實現,是TaskTracker調用InterTrackerProtocol協議的heartbeat方法來做的。這個方法接受一個TaskTrackerStatus對象作為參數,它描述了此時此任務服務器的狀態。當其有余力接受新的任務的時候,它還會傳入acceptNewTasks為true的參數,表示希望作業服務器委以重任。JobTracker接收到相關的參數后,經過處理,會返回一個HeartbeatResponse對象。這個對象中,定義了一組TaskTrackerAction,用于指導任務服務器進行下一步的工作。系統中已定義的了一堆其TaskTrackerAction的子類,有的對攜帶的參數進行了擴充,有的只是標明了下ID,具體不詳寫了,一看便知。。。
            當TaskTracker收到的TaskTrackerAction中,包含了LaunchTaskAction,它會開始執行所分配的新的任務。在TaskTracker中,有一個TaskTracker.TaskLauncher線程(確切的說是兩個,一個等Map任務,一個等Reduce任務),它們在癡癡的守候著新任務的來到。一旦等到了,會最終調用到Task的createRunner方法,構造出一個TaskRunner對象,新建一個線程來執行。對于一個Map任務,它對應的Runner是TaskRunner的子類MapTaskRunner,不過,核心部分都在TaskRunner的實現內。TaskRunner會先將所需的文件全部下載并拆包好,并記錄到一個全局緩存中,這是一個全局的目錄,可以供所有此作業的所有任務使用。它會用一些軟鏈接,將一些文件名鏈接到這個緩存中來。然后,根據不同的參數,配置出一個JVM執行的環境,這個環境與JvmEnv類的對象對應。
            接著,TaskRunner會調用JvmManagerlaunchJvm方法,提交給JvmManager處理。JvmManager用于管理該TaskTracker上所有運行的Task子進程。在目前的實現中,嘗試的是池化的方式。有若干個固定的槽,如果槽沒有滿,那么就啟動新的子進程,否則,就尋找idle的進程,如果是同Job的直接放進去,否則殺死這個進程,用一個新的進程代替。每一個進程都是由JvmRunner來管理的,它也是位于單獨線程中的。但是從實現上看,這個機制好像沒有部署開,子進程是死循環等待,而不會阻塞在父進程的相關線程上,父線程的變量一直都沒有個調整,一旦分配,始終都處在繁忙的狀況了。
            真實的執行載體,是Child,它包含一個main函數,進程執行,會將相關參數傳進來,它會拆解這些參數,并且構造出相關的Task實例,調用其run函數進行執行。每一個子進程,可以執行指定個數量的Task,這就是上面所說的池化的配置。但是,這套機制在我看來,并沒有運行起來,每個進程其實都沒有機會不死而執行新的任務,只是傻傻的等待進程池滿,而被一刀斃命。也許是我老眼昏花,沒看出其中實現的端倪。。。

            4、Reduce任務的分配與執行

            比之Map任務,Reduce的分配及其簡單,基本上是所有Map任務完成了,有空閑的任務服務器,來了就給分配一個Job任務。因為Map任務的結果星羅棋布,且變化多端,真要搞一個全局優化的算法,絕對是得不償失。而Reduce任務的執行進程的構造和分配流程,與Map基本完全的一致,沒有啥可說的了。。。
            但其實,Reduce任務與Map任務的最大不同,是Map任務的文件都在本地隔著,而Reduce任務需要到處采集。這個流程是作業服務器經由此Reduce任務所處的任務服務器,告訴Reduce任務正在執行的進程,它需要的Map任務執行過的服務器地址,此Reduce任務服務器會于原Map任務服務器聯系(當然本地就免了...),通過FTP服務,下載過來。這個隱含的直接數據聯系,就是執行Reduce任務與執行Map任務最大的不同了。。。

            5、作業的完成

            當所有Reduce任務都完成了,所需數據都寫到了分布式文件系統上,整個作業才正式完成了。此中,涉及到很多的類,很多的文件,很多的服務器,所以說起來很費勁,話說,一圖解千語,說了那么多,我還是畫兩幅圖,徹底表達一下吧。。。
            首先,是一個時序圖。它模擬了一個由3個Map任務和1個Reduce任務構成的作業執行流程。我們可以看到,在執行的過程中,只要有人太慢,或者失敗,就會增加一次嘗試,以此換取最快的執行總時間。一旦所有Map任務完成,Reduce開始運作(其實,不一定要這樣的...),對于每一個Map任務來說,只有執行到Reduce任務把它上面的數據下載完成,才算成功,否則,都是失敗,需要重新進行嘗試。。。
            而第二副圖,不是我畫的,就不轉載了,參見這里,它描述了整個Map/Reduce的服務器狀況圖,包括整體流程、所處服務器進程、輸入輸出等,看清楚這幅圖,對Map/Reduce的基本流程應該能完全跑通了。有這幾點,可能圖中描述的不夠清晰需要提及一下,一個是在HDFS中,其實還有日志文件,圖中沒有標明;另一個是步驟5,其實是由TaskTracker主動去拉取而不是JobTracker推送過來的;還有步驟8和步驟11,創建出來的MapTask和ReduceTask,在Hadoop中都是運行在獨立的進程上的。。。

            IV. Map任務詳請

            從上面,可以了解到整個Map和Reduce任務的整體流程,而后面要啰嗦的,是具體執行中的細節。Map任務的輸入,是分布式文件系統上的,包含鍵值對信息的文件。為了給每一個Map任務指定輸入,我們需要掌握文件格式把它分切成塊,并從每一塊中分離出鍵值信息。在HDFS中,輸入的文件格式,是由InputFormat<K, V>類來表示的,在JobConf中,它的默認值是TextInputFormat類(見getInputFormat),此類是特化的FileInputFormat<LongWritable, Text>子類,而FileInputFormat<K, V>正是InputFormat<K, V>的子類。通過這樣的關系我們可以很容易的理解,默認的文件格式是文本文件,且鍵是LongWritable類型(整形數),值是Text類型(字符串)。僅僅知道文件類型是不夠的,我們還需要將文件中的每一條數據,分離成鍵值對,這個工作,是RecordReader<K, V>來做的。在TextInputFormat的getRecordReader方法中我們可以看到,與TextInputFormat默認配套使用的,是LineRecordReader類,是特化的RecordReader<LongWritable, Text>的子類,它將每一行作為一個記錄,起始的位置作為鍵,整行的字符串作為值。有了格式,分出了鍵值,還需要切開分給每一個Map任務。每一個Map任務的輸入用InputSplit接口表示,對于一個文件輸入而言,其實現是FileSplit,它包含著文件名、起始位置、長度和存儲它的一組服務器地址。。。
            當Map任務拿到所屬的InputSplit后,就開始一條條讀取記錄,并調用用于定義的Mapper,進行計算(參見MapRunner<K1, V1, K2, V2>和MapTask的run方法),然后,輸出。MapTask會傳遞給Mapper一個OutputCollector<K, V>對象,作為輸出的數據結構。它定義了一個collect的函數,接受一個鍵值對。在MapTask中,定義了兩個OutputCollector的子類,一個是MapTask.DirectMapOutputCollector<K, V>,人如其名,它的實現確實很Direct,直截了當。它會利用一個RecordWriter<K, V>對象,collect一調用,就直接調用RecordWriter<K, V>的write方法,寫入本地的文件中去。如果覺著RecordWriter<K, V>出現的很突兀,那么看看上一段提到的RecordReader<K, V>,基本上,數據結構都是對應著的,一個是輸入一個是輸出。輸出很對稱也是由RecordWriter<K, V>和OutputFormat<K, V>來協同完成的,其默認實現是LineRecordWriter<K, V>和TextOutputFormat<K, V>,多么的眼熟啊。。。
            除了這個非常直接的實現之外,MapTask中還有一個復雜的多的實現,是MapTask.MapOutputBuffer<K extends Object, V extends Object>。有道是簡單壓倒一切,那為什么有很簡單的實現,要琢磨一個復雜的呢。原因在于,看上去很美的往往帶著刺,簡單的輸出實現,每調用一次collect就寫一次文件,頻繁的硬盤操作很有可能導致此方案的低效。為了解決這個問題,這就有了這個復雜版本,它先開好一段內存做緩存,然后制定一個比例做閾值開一個線程監控此緩存。collect來的內容,先寫到緩存中,當監控線程發現緩存的內容比例超過閾值,掛起所有寫入操作,建一個新的文件,把緩存的內容批量刷到此文件中去,清空緩存,重新開放,接受繼續collect。。。
            為什么說是刷到文件中去呢。因為這不是一個簡單的照本宣科簡單復制的過程,在寫入之前,會先將緩存中的內存,經過排序、合并器(Combiner)統計之后,才會寫入。如果你覺得Combiner這個名詞聽著太陌生,那么考慮一下Reducer,Combiner也就是一個Reducer類,通過JobConf的setCombinerClass進行設置,在常用的配置中,Combiner往往就是用用戶為Reduce任務定義的那個Reducer子類。只不過,Combiner只是服務的范圍更小一些而已,它在Map任務執行的服務器本地,依照Map處理過的那一小部分數據,先做一次Reduce操作,這樣,可以壓縮需要傳輸內容的大小,提高速度。每一次刷緩存,都會開一個新的文件,等此任務所有的輸入都處理完成后,就有了若干個有序的、經過合并的輸出文件。系統會將這些文件搞在一起,再做一個多路的歸并外排,同時使用合并器進行合并,最終,得到了唯一的、有序的、經過合并的中間文件(注:文件數量等同于分類數量,在不考慮分類的時候,簡單的視為一個...)。它,就是Reduce任務夢寐以求的輸入文件。。。
            除了做合并,復雜版本的OutputCollector,還具有分類的功能。分類,是通過Partitioner<K2, V2>來定義的,默認實現是HashPartitioner<K2, V2>,作業提交者可以通過JobConf的setPartitionerClass來自定義。分類的含義是什么呢,簡單的說,就是將Map任務的輸出,劃分到若干個文件中(通常與Reduce任務數目相等),使得每一個Reduce任務,可以處理某一類文件。這樣的好處是大大的,舉一個例子說明一下。比如有一個作業是進行單詞統計的,其Map任務的中間結果應該是以單詞為鍵,以單詞數量為值的文件。如果這時候只有一個Reduce任務,那還好說,從全部的Map任務那里收集文件過來,分別統計得到最后的輸出文件就好。但是,如果單Reduce任務無法承載此負載或效率太低,就需要多個Reduce任務并行執行。此時,再沿用之前的模式就有了問題。每個Reduce任務從一部分Map任務那里獲得輸入文件,但最終的輸出結果并不正確,因為同一個單詞可能在不同的Reduce任務那里都有統計,需要想方法把它們統計在一起才能獲得最后結果,這樣就沒有將Map/Reduce的作用完全發揮出來。這時候,就需要用到分類。如果此時有兩個Reduce任務,那么將輸出分成兩類,一類存放字母表排序較高的單詞,一類存放字母表排序低的單詞,每一個Reduce任務從所有的Map任務那里獲取一類的中間文件,得到自己的輸出結果。最終的結果,只需要把各個Reduce任務輸出的,拼接在一起就可以了。本質上,這就是將Reduce任務的輸入,由垂直分割,變成了水平分割。Partitioner的作用,正是接受一個鍵值,返回一個分類的序號。它會在從緩存刷到文件之前做這個工作,其實只是多了一個文件名的選擇而已,別的邏輯都不需要變化。。。
            除了緩存、合并、分類等附加工作之外,復雜版本的OutputCollector還支持錯誤數據的跳過功能,在后面分布式將排錯的時候,還會提及,標記一下,按下不表。。。

            V. Reduce任務詳情

            理論上看,Reduce任務的整個執行流程要比Map任務更為的羅嗦一些,因為,它需要收集輸入文件,然后才能進行處理。Reduce任務,主要有這么三個步驟:CopySortReduce(參見ReduceTask的run方法)。所謂Copy,就是從執行各個Map任務的服務器那里,收羅到本地來。拷貝的任務,是由ReduceTask.ReduceCopier類來負責,它有一個內嵌類,叫MapOutputCopier,它會在一個單獨的線程內,負責某個Map任務服務器上文件的拷貝工作。遠程拷貝過來的內容(當然也可以是本地了...),作為MapOutput對象存在,它可以在內存中也可以序列化在磁盤上,這個根據內存使用狀況來自動調節。整個拷貝過程是一個動態的過程,也就是說它不是一次給好所有輸入信息就不再變化了。它會不停的調用TaskUmbilicalProtocol協議的getMapCompletionEvents方法,向其父TaskTracker詢問此作業個Map任務的完成狀況(TaskTracker要向JobTracker詢問后再轉告給它...)。當獲取到相關Map任務執行服務器的信息后,都會有一個線程開啟,做具體的拷貝工作。同時,還有一個內存Merger線程和一個文件Merger線程在同步工作,它們將新鮮下載過來的文件(可能在內存中,簡單的統稱為文件...),做著歸并排序,以此,節約時間,降低輸入文件的數量,為后續的排序工作減負。。。
            Sort,排序工作,就相當于上述排序工作的一個延續。它會在所有的文件都拷貝完畢后進行,因為雖然同步有做著歸并的工作,但可能留著尾巴,沒做徹底。經過這一個流程,該徹底的都徹底了,一個嶄新的、合并了所有所需Map任務輸出文件的新文件,誕生了。而那些千行萬苦從其他各個服務器網羅過來的Map任務輸出文件,很快的結束了它們的歷史使命,被掃地出門一掃而光,全部刪除了。。。
            所謂好戲在后頭,Reduce任務的最后一個階段,正是Reduce本身。它也會準備一個OutputCollector收集輸出,與MapTask不同,這個OutputCollector更為簡單,僅僅是打開一個RecordWriter,collect一次,write一次。最大的不同在于,這次傳入RecordWriter的文件系統,基本都是分布式文件系統,或者說是HDFS。而在輸入方面,ReduceTask會從JobConf那里調用一堆getMapOutputKeyClass、getMapOutputValueClass、getOutputKeyComparator等等之類的自定義類,構造出Reducer所需的鍵類型,和值的迭代類型Iterator(一個鍵到了這里一般是對應一組值)。具體實現頗為拐彎抹角,建議看一下Merger.MergeQueueRawKeyValueIteratorReduceTask.ReduceValuesIterator等等之類的實現。有了輸入,有了輸出,不斷循環調用自定義的Reducer,最終,Reduce階段完成。。。

            VI. 分布式支持

            1、服務器正確性保證

            Hadoop Map/Reduce服務器狀況和HDFS很類似,由此可知,救死扶傷的方法也是大同小異。廢話不多說了,直接切正題。同作為客戶端,Map/Reduce的客戶端只是將作業提交,就開始搬個板凳看戲,沒有占茅坑的行動。因此,一旦它掛了,也就掛了,不傷大雅。而任務服務器,也需要隨時與作業服務器保持心跳聯系,一旦有了問題,作業服務器可以將其上運行的任務,移交給它人完成。作業服務器,作為一個單點,非常類似的是利用還原點(等同于HDFS的鏡像)和歷史記錄(等同于HDFS的日志),來進行恢復。其上,需要持久化用于恢復的內容,包含作業狀況、任務狀況、各個任務嘗試的工作狀況等。有了這些內容,再加上任務服務器的動態注冊,就算挪了個窩,還是很容易恢復的。JobHistory是歷史記錄相關的一個靜態類,本來,它也就是一個干寫日志活的,只是在Hadoop的實現中,對日志的寫入做了面向對象的封裝,同時又大量用到觀察者模式做了些嵌入,使得看起來不是那么直觀。本質上,它就是打開若干個日志文件,利用各類接口來往里面寫內容。只不過,這些日志,會放在分布式文件系統中,就不需要像HDFS那樣,來一個SecondXXX隨時候命,由此可見,有巨人在腳下踩著,真好。JobTracker.RecoveryManager類是作業服務器中用于進行恢復相關的事情,當作業服務器啟動的時候,會調用其recover方法,恢復日志文件中的內容。其中步驟,注釋中寫的很清楚,請自行查看。。。

            2、任務執行的正確和速度

            整個作業流程的執行,秉承著木桶原理。執行的最慢的Map任務和Reduce任務,決定了系統整體執行時間(當然,如果執行時間在整個流程中占比例很小的話,也許就微不足道了...)。因此,盡量加快最慢的任務執行速度,成為提高整體速度關鍵。所使用的策略,簡約而不簡單,就是一個任務多次執行。當所有未執行的任務都分配出去了,并且先富起來的那部分任務已經完成了,并還有任務服務器孜孜不倦的索取任務的時候,作業服務器會開始炒剩飯,把那些正在吭哧吭哧在某個服務器上慢慢執行的任務,再把此任務分配到一個新的任務服務器上,同時執行。兩個服務器各盡其力,成王敗寇,先結束者的結果將被采納。這樣的策略,隱含著一個假設,就是我們相信,輸入文件的分割算法是公平的,某個任務執行慢,并不是由于這個任務本身負擔太重,而是由于服務器不爭氣負擔太重能力有限或者是即將撒手西去,給它換個新環境,人挪死樹挪活事半功倍。。。
            當然,肯定有哽咽的任務,不論是在哪個服務器上,都無法順利完成。這就說明,此問題不在于服務器上,而是任務本身天資有缺憾。缺憾在何處?每個作業,功能代碼都是一樣的,別的任務成功了,就是這個任務不成功,很顯然,問題出在輸入那里。輸入中有非法的輸入條目,導致程序無法辨識,只能揮淚惜別。說到這里,解決策略也浮出水面了,三十六計走位上,惹不起,還是躲得起的。在MapTask中的MapTask.SkippingRecordReader<K, V>和ReduceTask里的ReduceTask.SkippingReduceValuesIterator<KEY,VALUE>,都是用于干這個事情的。它們的原理很簡單,就是在讀一條記錄前,把當前的位置信息,封裝成SortedRanges.Range對象,經由Task的reportNextRecordRange方法提交到TaskTracker上去。TaskTracker會把這些內容,擱在TaskStatus對象中,隨著心跳消息,匯報到JobTracker上面。這樣,作業服務器就可以隨時隨刻了解清楚,每個任務正讀取在那個位置,一旦出錯,再次執行的時候,就在分配的任務信息里面添加一組SortedRanges信息。MapTask或ReduceTask讀取的時候,會看一下這些區域,如果當前區域正好處于上述雷區,跳過不讀。如此反復,正可謂,道路曲折,前途光明啊。。。

            VII. 總結

            對于Map/Reduce而言,真正的困難,在于提高其適應能力,打造一款能夠包治百病的執行框架。Hadoop已經做得很好了,但只有真正搞清楚了整個流程,你才能幫助它做的更好。。。

            posted @ 2010-02-03 10:58 不會飛的鳥 閱讀(257) | 評論 (0)編輯 收藏

            【裝載】分布式基礎學習【一】-分布式文件系統

            所謂分布式,在這里,很狹義的指代以Google的三駕馬車,GFSMap/ReduceBigTable為框架核心的分布式存儲和計算系統。通常如我一樣初學的人,會以Google這幾份經典的論文作為開端的。它們勾勒出了分布式存儲和計算的一個基本藍圖,已可窺見其幾分風韻,但終究還是由于缺少一些實現的代碼和示例,色彩有些斑駁,缺少了點感性。幸好我們還有Open Source,還有Hadoop。Hadoop是一個基于Java實現的,開源的,分布式存儲和計算的項目。作為這個領域最富盛名的開源項目之一,它的使用者也是大牌如云,包括了Yahoo,Amazon,Facebook等等(好吧,還可能有校內,不過這真的沒啥分量...)。Hadoop本身,實現的是分布式的文件系統HDFS,和分布式的計算(Map/Reduce)框架,此外,它還不是一個人在戰斗,Hadoop包含一系列擴展項目,包括了分布式文件數據庫HBase(對應Google的BigTable),分布式協同服務ZooKeeper(對應Google的Chubby),等等。。。
            如此,一個看上去不錯的黃金搭檔浮出水面,Google的論文 + Hadoop的實現,順著論文的框架看具體的實現,用實現來進一步理解論文的邏輯,看上去至少很美。網上有很多前輩們,做過Hadoop相關的源碼剖析工作,我關注最多的是這里,目前博主已經完成了HDFS的剖析工作,Map/Reduce的剖析正火熱進行中,更新頻率之高,剖析之詳盡,都是難得一見的,所以,走過路過一定不要錯過了。此外,還有很多Hadoop的關注者和使用者貼過相關的文章,比如:這里這里。也可以去Hadoop的中文站點(不知是民間還是官方...),搜羅一些學習資料。。。
            我個人從上述資料中受益匪淺,而我自己要做的整理,與原始的源碼剖析有些不同,不是依照實現的模塊,而是基于論文的脈絡和實現這樣系統的基本脈絡來進行的,也算,從另一個角度給出一些東西吧。鑒于個人對于分布式系統的理解非常的淺薄,缺少足夠的實踐經驗,深入的問題就不班門弄斧了,僅做梳理和解析,大牛至此,可繞路而行了。。。

            一. 分布式文件系統

            分布式文件系統,在整個分布式系統體系中處于最低層最基礎的地位,存儲嘛,沒了數據,再好的計算平臺,再完善的數據庫系統,都成了無水之舟了。那么,什么是分布式文件系統,顧名思義,就是分布式+文件系統。它包含這兩個方面的內涵,從文件系統的客戶使用的角度來看,它就是一個標準的文件系統,提供了一系列API,由此進行文件或目錄的創建、移動、刪除,以及對文件的讀寫等操作。從內部實現來看,分布式的系統則不再和普通文件系統一樣負責管理本地磁盤,它的文件內容和目錄結構都不是存儲在本地磁盤上,而是通過網絡傳輸到遠端系統上。并且,同一個文件存儲不只是在一臺機器上,而是在一簇機器上分布式存儲,協同提供服務,正所謂分布式。。。
            因此,考量一個分布式文件系統的實現,其實不妨可以從這兩方面來分別剖析,而后合二為一。首先,看它如何去實現文件系統所需的基本增刪改查的功能。然后,看它如何考慮分布式系統的特點,提供更好的容錯性,負載平衡,等等之類的。這二者合二為一,就明白了一個分布式文件系統,整體的實現模式。。。

            I. 術語對照

            說任何東西,都需要統一一下語言先,不然明明說的一個意思,卻容易被理解到另一個地方去。Hadoop的分布式文件系統HDFS,基本是按照Google論文中的GFS的架構來實現的。但是,HDFS為了彰顯其不走尋常路的本性,其中的大量術語,都與GFS截然不同。明明都是一個枝上長的土豆,它偏偏就要叫山藥蛋,弄得水火不容的,苦了我們看客。秉承老好人,誰也不得罪的方針,文中,既不采用GFS的叫法,也不采用Hadoop的稱謂,而是另辟蹊徑,自立門戶,搞一套自己的中文翻譯,為了避免不必要的痛楚,特此先來一帖術語對照表,要不懂查一查,包治百病。。。

            文中所用翻譯 HDFS中的術語 GFS中的術語 術語解釋
            主控服務器 NameNode Master 整個文件系統的大腦,它提供整個文件系統的目錄信息,并且管理各個數據服務器。
            數據服務器 DataNode Chunk Server 分布式文件系統中的每一個文件,都被切分成若干個數據塊,每一個數據塊都被存儲在不同的服務器上,此服務器稱之為數據服務器。
            數據塊 Block Chunk 每個文件都會被切分成若干個塊,每一塊都有連續的一段文件內容,是存儲的基恩單位,在這里統一稱做數據塊。
            數據包 Packet 客戶端寫文件的時候,不是一個字節一個字節寫入文件系統的,而是累計到一定數量后,往文件系統中寫入一次,每發送一次的數據,都稱為一個數據包。
            傳輸塊 Chunk 在每一個數據包中,都會將數據切成更小的塊,每一個塊配上一個奇偶校驗碼,這樣的塊,就是傳輸塊。
            備份主控服務器 SecondaryNameNode 備用的主控服務器,在身后默默的拉取著主控服務器 的日志,等待主控服務器犧牲后被扶正。

            *注:本文采用的Hadoop是0.19.0版本。

            II. 基本架構

            1. 服務器介紹

            與單機的文件系統不同,分布式文件系統不是將這些數據放在一塊磁盤上,由上層操作系統來管理。而是存放在一個服務器集群上,由集群中的服務器,各盡其責,通力合作,提供整個文件系統的服務。其中重要的服務器包括:主控服務器(Master/NameNode),數據服務器(ChunkServer/DataNode),和客戶服務器。HDFS和GFS都是按照這個架構模式搭建的。個人覺得,其中設計的最核心內容是:文件的目錄結構獨立存儲在一個主控服務器上,而具體文件數據,拆分成若干塊,冗余的存放在不同的數據服務器上。
            存儲目錄結構的主控服務器,在GFS中稱為Master,在HDFS中稱為NameNode。這兩個名字,叫得都有各自的理由,是瞎子摸象各表一面。Master是之于數據服務器來叫的,它做為數據服務器的領導同志存在,管理各個數據服務器,收集它們的信息,了解所有數據服務器的生存現狀,然后給它們分配任務,指揮它們齊心協力為系統服務;而NameNode是針對客戶端來叫的,對于客戶端而言,主控服務器上放著所有的文件目錄信息,要找一個文件,必須問問它,由此而的此名。。。
            主控服務器在整個集群中,同時提供服務的只存在一個,如果它不幸犧牲的話,會有后備軍立刻前赴后繼的跟上,但,同一時刻,需要保持一山不容二虎的態勢。這種設計策略,避免了多臺服務器間即時同步數據的代價,而同時,它也使得主控服務器很可能成為整個架構的瓶頸所在。因此,盡量為主控服務器減負,不然它做太多的事情,就自然而然的晉升成了一個分布式文件系統的設計要求。。。
            每一個文件的具體數據,被切分成若干個數據塊,冗余的存放在數據服務器。通常的配置,每一個數據塊的大小為64M,在三個數據服務器上冗余存放(這個64M,不是隨便得來的,而是經過反復實踐得到的。因為如果太大,容易造成熱點的堆疊,大量的操作集中在一臺數據服務器上,而如果太小的話,附加的控制信息傳輸成本,又太高了。因此沒有比較特定的業務需求,可以考慮維持此配置...)。數據服務器是典型的四肢發達頭腦簡單的苦力,其主要的工作模式就是定期向主控服務器匯報其狀況,然后等待并處理命令,更快更安全的存放好數據。。。
            此外,整個分布式文件系統還有一個重要角色是客戶端。它不和主控服務和數據服務一樣,在一個獨立的進程中提供服務,它只是以一個類庫(包)的模式存在,為用戶提供了文件讀寫、目錄操作等APIs。當用戶需要使用分布式文件系統進行文件讀寫的時候,把客戶端相關包給配置上,就可以通過它來享受分布式文件系統提供的服務了。。。

            2. 數據分布

            一個文件系統中,最重要的數據,其實就是整個文件系統的目錄結構和具體每個文件的數據。具體的文件數據被切分成數據塊,存放在數據服務器上。每一個文件數據塊,在數據服務器上都表征為出雙入隊的一對文件(這是普通的Linux文件),一個是數據文件,一個是附加信息的元文件,在這里,不妨把這對文件簡稱為數據塊文件。數據塊文件存放在數據目錄下,它有一個名為current的根目錄,然后里面有若干個數據塊文件和從dir0-dir63的最多64個的子目錄,子目錄內部結構等同于current目錄,依次類推(更詳細的描述,參見這里)。個人覺得,這樣的架構,有利于控制同一目錄下文件的數量,加快檢索速度。。。
            這是磁盤上的物理結構,與之對應的,是內存中的數據結構,用以表征這樣的磁盤結構,方便讀寫操作的進行。Block類用于表示數據塊,而FSDataset類是數據服務器管理文件塊的數據結構,其中,FSDataset.FSDir對應著數據塊文件和目錄,FSDataset.FSVolume對應著一個數據目錄,FSDataset.FSVolumeSet是FSVolume的集合,每一個FSDataset有一個FSVolumeSet。多個數據目錄,可以放在不同的磁盤上,這樣有利于加快磁盤操作的速度。相關的類圖,可以參看這里 。。。
            此外,與FSVolume對應的,還有一個數據結構,就是DataStorage,它是Storage的子類,提供了升級、回滾等支持。但與FSVolume不一樣,它不需要了解數據塊文件的具體內容,它只知道有這么一堆文件放這里,會有不同版本的升級需求,它會處理怎么把它們升級回滾之類的業務(關于Storage,可以參見這里)。而FSVolume提供的接口,都基本上是和Block相關的。。。
            相比數據服務器,主控服務器的數據量不大,但邏輯更為復雜。主控服務器主要有三類數據:文件系統的目錄結構數據各個文件的分塊信息數據塊的位置信息(就數據塊放置在哪些數據服務器上...)。在GFS和HDFS的架構中,只有文件的目錄結構和分塊信息才會被持久化到本地磁盤上,而數據塊的位置信息則是通過動態匯總過來的,僅僅存活在內存數據結構中,機器掛了,就灰飛煙滅了。每一個數據服務器啟動后,都會向主控服務器發送注冊消息,將其上數據塊的狀況都告知于主控服務器。俗話說,簡單就是美,根據DRY原則,保存的冗余信息越少,出現不一致的可能性越低,付出一點點時間的代價,換取了一大把邏輯上的簡單性,絕對應該是一個包賺不賠的買賣。。。
            在HDFS中,FSNamespacesystem類就負責保管文件系統的目錄結構以及每個文件的分塊狀況的,其中,前者是由FSDirectory類來負責,后者是各個INodeFile本身維護。在INodeFile里面,有一個BlockInfo的數組,保存著與該文件相關的所有數據塊信息,BlockInfo中包含了從數據塊到數據服務器的映射,INodeFile只需要知道一個偏移量,就可以提供相關的數據塊,和數據塊存放的數據服務器信息。。。

            3、服務器間協議

            在Hadoop的實現中,部署了一套RPC機制,以此來實現各服務間的通信協議。在Hadoop中,每一對服務器間的通信協議,都定義成為一個接口。服務端的類實現該接口,并且建立RPC服務,監聽相關的接口,在獨立的線程處理RPC請求。客戶端則可以實例化一個該接口的代理對象,調用該接口的相應方法,執行一次同步的通信,傳入相應參數,接收相應的返回值。基于此RPC的通信模式,是一個消息拉取的流程,RPC服務器等待RPC客戶端的調用,而不會先發制人主動把相關信息推送到RPC客戶端去。。。
            其實RPC的模式和原理,實在是沒啥好說的,之所以說,是因為可以通過把握好這個,徹底理順Hadoop各服務器間的通信模式。Hadoop會定義一些列的RPC接口,只需要看誰實現,誰調用,就可以知道誰和誰通信,都做些啥事情,圖中服務器的基本架構、各服務所使用的協議、調用方向、以及協議中的基本內容。。。

            III. 基本的文件操作

            基本的文件操作,可以分成兩類,一個是對文件目錄結構的操作,比如文件和目錄的創建、刪除、移動、更名等等;另一個是對文件數據流的操作,包括讀取和寫入文件數據。當然,文件讀和寫,是有本質區別的,尤其是在數據冗余的情況下,因此,當成兩類操作也不足為過。此外,要具體到讀寫的類別,也是可以再繼續分類下去的。在GFS的論文中,對于分布式文件系統的讀寫場景有一個重要的假定(其實是從實際業務角度得來的...):就是文件的讀取是由大數據量的連續讀取和小數據量的隨機讀取組成,文件的寫入則基本上都是批量的追加寫,和偶爾的插入寫(GFS中還有大量的假設,它們構成了分布式文件系統架構設計的基石。每一個系統架構都是搭建在一定假設上的,這些假設有些來自于實際業務的狀況,有些是因為天生的條件約束,不基于假設理解設計,肯定會有失偏頗...)。在GFS中,對文件的寫入分成追加寫和插入寫都有所支持,但是,在HDFS中僅僅支持追加寫,這大大降低了復雜性。關于HDFS與GFS的一些不同,可以參看這里。。。

            1. 文件和目錄的操作

            文件目錄的信息,全部囤積在主控服務器上,因此,所有對文件目錄的操作,只會直接涉及到客戶端和主控服務器。整個目錄相關的操作流程基本都是這樣的:客戶端DFSClient調用ClientProtocol定義的相關函數,該操作通過RPC傳送到其實現者主控服務器NameNode那里,NameNode做相關的處理后(很少...),調用FSNamesystem的相關函數。在FSNamesystem中,往往是做一些驗證和租約操作,具體的目錄結構操作交由FSDirectory的相應函數來操作。最后,依次返回,經由RPC傳送回客戶端。具體各操作涉及到的函數和具體步驟,參見下表:

            相關操作 ClientProtocol / NameNode FSNamesystem FSDirectory 關鍵步驟
            創建文件 create startFile addFile 1. 檢查是否有寫權限;
            2. 檢查是否已經存在此文件,如果是覆寫,則先進行刪除操作;
            3. 在指定路徑下添加INodeFileUnderConstruction的文件實例;
            4. 寫日志;
            5. 簽訂租約。
            創建目錄 mkdirs mkdirs mkdirs 1. 檢查指定目錄是否是目錄;
            2. 檢查是否有相關權限;
            3. 在指定路徑的INode下,添加子節點;
            4. 寫日志。
            改名操作 rename renameTo renameTo 1. 檢查相關路徑的權限;
            2. 從老路徑下移除,在新路徑下添加;
            3. 修改相關父路徑的修改時間;
            4. 寫日志;
            5. 將租約從老路徑移動到新路徑下。
            刪除操作 delete delete delete 1. 如果不是遞歸刪除,確認指定路徑是否是空目錄;
            2. 檢查相關權限;
            3. 在目錄結構上移除相關INode;
            4. 修改父路徑的修改時間;
            5. 將相關的數據塊,放入到廢棄隊列中去,等待處理;
            6. 寫日志;
            7. 廢棄相關路徑的租約。
            設置權限 setPermission setPermission setPermission 1. 檢查owner判斷是否有操作權限;
            2. 修改指定路徑下INode的權限;
            3. 寫日志。
            設置用戶 setOwner setOwner setOwner 1. 檢查是否有操作權限;
            2. 修改指定路徑下INode的權限;
            3. 寫日志。
            設置時間 setTimes setTimes setTimes 1. 檢查是否有寫權限;
            2. 修改指定路徑INode的時間信息;
            3. 寫日志。

            從上表可以看到,其實有的操作本質上還是涉及到了數據服務器,比如文件創建和刪除操作。但是,之前提到,主控服務器只于數據服務器是一個等待拉取的地位,它們不會主動聯系數據服務器,將指令傳輸給它們,而是放到相應的數據結構中,等待數據服務器來取。這樣的設計,可以減少通信的次數,加快操作的執行速度。。。
            另,上述步驟中,有些日志和租約相關的操作,從概念上來說,和目錄操作其實沒有任何聯系,但是,為了滿足分布式系統的需求,這些操作是非常有必要的,在此,按下不表。。。

            2、文件的讀取

            不論是文件讀取,還是文件的寫入,主控服務器扮演的都是中介的角色。客戶端把自己的需求提交給主控服務器,主控服務器挑選合適的數據服務器,介紹給客戶端,讓客戶端和數據服務器單聊,要讀要寫隨你們便。這種策略類似于DMA,降低了主控服務器的負載,提高了效率。。。
            因此,在文件讀寫操作中,最主要的通信,發生在客戶端與數據服務器之間。它們之間跑的協議是ClientDatanodeProtocol。從這個協議中間,你無法看到和讀寫相關的接口,因為,在Hadoop中,讀寫操作是不走RPC機制的,而是另立門戶,獨立搭了一套通信框架。在數據服務器一端,DataNode類中有一個DataXceiverServer類的實例,它在一個單獨的線程等待請求,一旦接到,就啟動一個DataXceiver的線程,處理此次請求。一個請求一個線程,對于數據服務器來說,邏輯上很簡單。當下,DataXceiver支持的請求類型有六種,具體的請求包和回復包格式,請參見這里這里這里。在Hadoop的實現中,并沒有用類來封裝這些請求,而是按流的次序寫下來,這給代碼閱讀帶來挺多的麻煩,也對代碼的維護帶來一定的困難,不知道是出于何種考慮。。。
            相比于寫,文件的讀取實在是一個簡單的過程。在客戶端DFSClient中,有一個DFSClient.DFSInputStream類。當需要讀取一個文件的時候,會生成一個DFSInputStream的實例。它會先調用ClientProtocol定義getBlockLocations接口,提供給NameNode文件路徑讀取位置讀取長度信息,從中取得一個LocatedBlocks類的對象,這個對象包含一組LocatedBlock,那里面有所規定位置中包含的所有數據塊信息,以及數據塊對應的所有數據服務器的位置信息。當讀取開始后,DFSInputStream會先嘗試從某個數據塊對應的一組數據服務器中選出一個,進行連接。這個選取算法,在當下的實現中,非常簡單,就是選出第一個未掛的數據服務器,并沒有加入客戶端與數據服務器相對位置的考量。讀取的請求,發送到數據服務器后,自然會有DataXceiver來處理,數據被一個包一個包發送回客戶端,等到整個數據塊的數據都被讀取完了,就會斷開此鏈接,嘗試連接下一個數據塊對應的數據服務器,整個流程,依次如此反復,直到所有想讀的都讀取完了為止。。。

            3、文件的寫入

            文件讀取是一個一對一的過程,一個客戶端,只需要與一個數據服務器聯系,就可以獲得所需的內容。但是,寫入操作,則是一個一對多的流程。一次寫入,需要在所有存放相關數據塊的數據服務器都保持同步的更新,有任何的差池,整個流程就告失敗。。。
            在分布式系統中,一旦涉及到寫入操作,并發處理難免都會淪落成為一個變了相的串行操作。因為,如果不同的客戶端如果是任意時序并發寫入的話,整個寫入的次序無法保證,可能你寫半條記錄我寫半條記錄,最后出來的結果亂七八糟不可估量。在HDFS中,并發寫入的次序控制,是由主控服務器來把握的。當創建、續寫一個文件的時候,該文件的節點類,由INodeFile升級成為INodeFileUnderConstruction,INodeFileUnderConstruction是INodeFile的子類,它起到一個鎖的作用。如果當一個客戶端想創建或續寫的文件是INodeFileUnderConstruction,會引發異常,因為這說明這個此處有爺,請另尋高就,從而保持了并發寫入的次序性。同時,INodeFileUnderConstruction有包含了此時正在操作它的客戶端的信息以及最后一個數據塊的數據服務器信息,當追加寫的時候可以更快速的響應。。。
            與讀取類似,DFSClient也有一個DFSClient.DFSOutputStream類,寫入開始,會創建此類的實例。DFSOutputStream會從NameNode上拿一個LocatedBlock,這里面有最后一個數據塊的所有數據服務器的信息。這些數據服務器每一個都需要能夠正常工作(對于讀取,只要還有一個能工作的就可以實現...),它們會依照客戶端的位置被排列成一個有著最近物理距離和最小的序列(物理距離,是根據機器的位置定下來的...),這個排序問題類似于著名旅行商問題,屬于NP復雜度,但是由于服務器數量不多,所以用最粗暴的算法,也并不會看上去不美。。。
            文件寫入,就是在這一組數據服務器上構造成數據流的雙向流水線。DFSOutputStream,會與序列的第一個數據服務器建立Socket連接,發送請求頭,然后等待回應。DataNode同樣是建立DataXceiver來處理寫消息,DataXceiver會依照包中傳過來的其他服務器的信息,建立與下一個服務器的連接,并生成類似的頭,發送給它,并等待回包。此流程依次延續,直到最后一級,它發送回包,反向著逐級傳遞,再次回到客戶端。如果一切順利,那么此時,流水線建立成功,開始正式發送數據。數據是分成一個個數據包發送的,所有寫入的內容,被緩存在客戶端,當寫滿64K,會被封裝成DFSOutputStream.Packet類實例,放入DFSOutputStream的dataQueue隊列。DFSOutputStream.DataStreamer會時刻監聽這個隊列,一旦不為空,則開始發送,將位于dataQueue隊首的包移動到ackQueue隊列的隊尾,表示已發送但尚未接受回復的包隊列。同時啟動ResponseProcessor線程監聽回包,直到收到相應回包,才將發送包從ackQueue中移除,表示成功。每一個數據服務器的DataXceiver收到了數據包,一邊寫入到本地文件中去,一邊轉發給下一級的數據服務器,等待回包,同前面建立流水線的流程。。。
            當一個數據塊寫滿了之后,客戶端需要向主控服務器申請追加新的數據塊。這個會引起一次數據塊的分配,成功后,會將新的數據服務器組返還給客戶端。然后重新回到上述流程,繼續前行。。。
            關于寫入的流程,還可以參見這里。此外,寫入涉及到租約問題,后續會仔細的來說。。。

            IV. 分布式支持

            如果單機的文件系統是田里勤懇的放牛娃,那么分布式文件系統就是刀尖上討飯吃的馬賊了。在分布式環境中,有太多的意外,數據隨時傳輸錯誤,服務器時刻準備犧牲,很多平常稱為異常的現象,在這里都需要按照平常事來對待。因此,對于分布式文件系統而言,僅僅是滿足了正常狀況下文件系統各項服務還不夠,還需要保證分布式各種意外場景下健康持續的服務,否則,將一無是處。。。

            1、服務器的錯誤恢復

            在分布式環境中,哪臺服務器犧牲都是常見的事情,犧牲不可怕,可怕的是你都沒有時刻準備好它們會犧牲。作為一個合格的分布式系統,HDFS當然時刻準備好了前赴后繼奮勇向前。HDFS有三類服務器,每一類服務器出錯了,都有相應的應急策略。。。
            a. 客戶端
            生命最輕如鴻毛的童鞋,應該就是客戶端了。畢竟,做為一個文件系統的使用者,在整個文件系統中的地位,難免有些歸于三流。而作為客戶端,大部分時候,犧牲了就犧牲了,沒人哀悼,無人同情,只有在在辛勤寫入的時候,不幸辭世(機器掛了,或者網絡斷了,諸如此類...),才會引起些恐慌。因為,此時此刻,在主控服務器上對應的文件,正作為INodeFileUnderConstruction活著,僅僅為占有它的那個客戶端服務者,做為一個專一的文件,它不允許別的客戶端染指。這樣的話,一旦占有它的客戶端服務者犧牲了,此客戶端會依然占著茅坑不拉屎,讓如花似玉INodeFileUnderConstruction孤孤單單守寡終身。這種事情當然無法容忍,因此,必須有辦法解決這個問題,辦法就是:租約。。。
            租約,顧名思義,就是當客戶端需要占用某文件的時候,與主控服務器簽訂的一個短期合同。這個合同有一個期限,在這個期限內,客戶端可以延長合同期限,一旦超過期限,主控服務器會強行終止此租約,將這個文件的享用權,分配給他人。。。
            在打開或創建一個文件,準備追加寫之前,會調用LeaseManageraddLease方法,在指定的路徑下與此客戶端簽訂一份租約。客戶端會啟動DFSClient.LeaseChecker線程,定時輪詢調用ClientProtocolrenewLease方法,續簽租約。在主控服務器一端,有一個LeaseManager.Monitor線程,始終在輪詢檢查所有租約,查看是否有到期未續的租約。如果一切正常,該客戶端完成寫操作,會關閉文件,停止租約,一旦有所意外,比如文件被刪除了,客戶端犧牲了,主控服務器都會剝奪此租約,如此,來避免由于客戶端停機帶來的資源被長期霸占的問題。。。
            b. 數據服務器
            當然,會掛的不只是客戶端,海量的數據服務器是一個更不穩定的因素。一旦某數據服務器犧牲了,并且主控服務器被蒙在鼓中,主控服務器就會變相的欺騙客戶端,給它們無法連接的讀寫服務器列表,導致它們處處碰壁無法工作。因此,為了整個系統的穩定,數據服務器必須時刻向主控服務器匯報,保持主控服務器對其的完全了解,這個機制,就是心跳消息。在HDFS中,主控服務器NameNode實現了DatanodeProtocol接口,數據服務器DataNode會在主循環中,不停的調用該協議中的sendHeartbeat方法,向NameNode匯報狀況。在此調用中,DataNode會將其整體運行狀況告知NameNode,比如:有多少可用空間、用了多大的空間,等等之類。NameNode會記住此DataNode的運行狀況,作為新的數據塊分配或是負載均衡的依據。當NameNode處理完成此消息后,會將相關的指令封裝成一個DatanodeCommand對象,交還給DataNode,告訴數據服務器什么數據塊要刪除什么數據塊要新增等等之類,數據服務器以此為自己的行動依據。。。
            但是,sendHeartbeat并沒有提供本地的數據塊信息給NameNode,那么主控服務器就無法知道此數據服務器應該分配什么數據塊應該刪除什么數據塊,那么它是如何決定的呢?答案就是DatanodeProtocol定義的另一個方法,blockReport。DataNode也是在主循環中定時調用此方法,只是,其周期通常比調用sendHeartbeat的更長。它會提交本地的所有數據塊狀況給NameNode,NameNode會和本地保存的數據塊信息比較,決定什么該刪除什么該新增,并將相關結果緩存在本地對應的數據結構中,等待此服務器再發送sendHeartbeat消息過來的時候,依照這些數據結構中的內容,做出相應的DatanodeCommand指令。blockReport方法同樣也會返回一個DatanodeCommand給DataNode,但通常,只是為空(只有出錯的時候不為空),我想,增加緩存,也許是為了確保每個指令都可以重復發送并確定被執行。。。
            c. 主控服務器
            當然,作為整個系統的核心和單點,含辛茹苦的主控服務器含淚西去,整個分布式文件服務集群將徹底癱瘓罷工。如何在主控服務器犧牲后,提拔新的主控服務器并迅速使其進入工作角色,就成了系統必須考慮的問題。解決策略就是:日志。。。
            其實這并不是啥新鮮東西,一看就知道是從數據庫那兒偷師而來的。在主控服務器上,所有對文件目錄操作的關鍵步驟(具體文件內容所處的數據服務器,是不會被寫入日志的,因為這些內容是動態建立的...),都會被寫入日志。另外,主控服務器會在某些時刻,將當下的文件目錄完整的序列化到本地,這稱為鏡像。一旦存有鏡像,鏡像前期所寫的日志和其他鏡像,都純屬冗余,其歷史使命已經完成,可以報廢刪除了。在主控服務器不幸犧牲,或者是戰略性的停機修整結束,并重新啟動后,主控服務器會根據最近的鏡像 + 鏡像之后的所有日志,重建整個文件目錄,迅速將服務能力恢復到犧牲前的水準。。。
            對于數據服務器而言,它們會通過一些手段,迅速得知頂頭上司的更迭消息。它們會立刻轉投新東家的名下,在新東家旗下注冊,并開始向其發送心跳消息,這個機制,可能用分布式協同服務來實現,這里不說也罷。。。
            在HDFS的實現中,FSEditLog類是整個日志體系的核心,提供了一大堆方便的日志寫入API,以及日志的恢復存儲等功能。目前,它支持若干種日志類型,都冠以OP_XXX,并提供相關API,具體可以參見這里。為了保證日志的安全性,FSEditLog提供了EditLogFileOutputStream類作為寫入的承載類,它會同時開若干個本地文件,然后依次寫入,防止日志的損壞導致不可估量的后果。在FSEditLog上面,有一個FSImage類,存儲文件鏡像并調用FSEditLog對外提供相關的日志功能。FSImage是Storage類的子類,如果對數據塊的講述有所印象的話,你可以回憶起來,凡事從此類派生出來的東西,都具有版本性質,可以進行升級和回滾等等,以此,來實現產生鏡像是對原有日志和鏡像處理的復雜邏輯。。。
            目前,在HDFS的日志系統中,有些地方與GFS的描述有所不同。在HDFS中,所有日志文件和鏡像文件都是本地文件,這就相當于,把日志放在自家的保險箱中,一旦主控服務器掛了,別的后繼而上的服務器也無法拿到這些日志和鏡像,用于重振雄風。因此,在HDFS中,運行著一個SecondaryNameNode服務器,它做為主控服務器的替補,隱忍厚積薄發為篡位做好準備,其中,核心內容就是:定期下載并處理日志和鏡像。SecondaryNameNode看上去像客戶端一樣,與NameNode之間,走著NamenodeProtocol協議。它會不停的查看主控服務器上面累計日志的大小,當達到閾值后,調用doCheckpoint函數,此函數的主要步驟包括:
            • 首先是調用startCheckpoint做一些本地的初始化工作;
            • 然后調用rollEditLog,將NameNode上此時操作的日志文件從edit切到edit.new上來,這個操作瞬間完成,上層寫日志的函數完全感覺不到差別;
            • 接著,調用downloadCheckpointFiles,將主控服務器上的鏡像文件和日志文件都下載到此候補主控服務器上來;
            • 并調用doMerge,打開鏡像和日志,將日志生成新的鏡像,保存覆蓋;
            • 下一步,調用putFSImage把新的鏡像上傳回NameNode;
            • 再調用rollFsImage,將鏡像換成新的,在日志從edit.new改名為edit;
            • 最后,調用endCheckpoint做收尾工作。
            整個算法涉及到NameNode和SecondaryNameNode兩個服務器,最終結果是NameNode和SecondaryNameNode都依照算法進行前的日志生成了鏡像。而兩個服務器上日志文件的內容,前者是整個算法進行期間所寫的日志,后者始終不會有任何日志。當主控服務器犧牲的時候,運行SecondaryNameNode的服務器立刻被扶正,在其上啟動主控服務,利用其日志和鏡像,恢復文件目錄,并逐步接受各數據服務器的注冊,最終向外提供穩定的文件服務。。。
            同樣的事情,GFS采用的可能是另外一個策略,就是在寫日志的時候,并不局限在本地,而是同時書寫網絡日志,即在若干個遠程服務器上生成同樣的日志。然后,在某些時機,主控服務器自己,生成鏡像,降低日志規模。當主控服務器犧牲,可以在擁有網絡日志的服務器上啟動主控服務,升級成為主控服務器。。。
            GFS與HDFS的策略相比較,前者是化整為零,后者則是批量處理,通常我們認為,批量處理的平均效率更高一些,且相對而言,可能實現起來容易一些,但是,由于有間歇期,會導致日志的丟失,從而無法100%的將備份主控服務器的狀態與主控服務器完全同步。。。

            2、數據的正確性保證

            在復雜紛繁的分布式環境中,我們堅定的相信,萬事皆有可能。哪怕各個服務器都舒舒服服的活著,也可能有各種各樣的情況導致網絡傳輸中的數據丟失或者錯誤。并且在分布式文件系統中,同一份文件的數據,是存在大量冗余備份的,系統必須要維護所有的數據塊內容完全同步,否則,一人一言,不同客戶端讀同一個文件讀出不同數據,用戶非得瘋了不可。。。
            在HDFS中,為了保證數據的正確性和同一份數據的一致性,做了大量的工作。首先,每一個數據塊,都有一個版本標識,在Block類中,用一個長整型的數generationStamp來表示版本信息(Block類是所有表示數據塊的數據結構的基類),一旦數據塊上的數據有所變化,此版本號將向前增加。在主控服務器上,保存有此時每個數據塊的版本,一旦出現數據服務器上相關數據塊版本與其不一致,將會觸發相關的恢復流程。這樣的機制保證了各個數據服務器器上的數據塊,在基本大方向上都是一致的。但是,由于網絡的復雜性,簡單的版本信息無法保證具體內容的一致性(因為此版本信息與內容無關,可能會出現版本相同,但內容不同的狀況)。因此,為了保證數據內容上的一致,必須要依照內容,作出簽名。。。
            當客戶端向數據服務器追加寫入數據包時,每一個數據包的數據,都會切分成512字節大小的段,作為簽名驗證的基本單位,在HDFS中,把這個數據段稱為Chunk,即傳輸塊(注意,在GFS中,Chunk表達的是數據塊...)。在每一個數據包中,都包含若干個傳輸塊以及每一個傳輸塊的簽名,當下,這個簽名是根據Java SDK提供的CRC算法算得的,其實就是一個奇偶校驗。當數據包傳輸到流水線的最后一級,數據服務器會對其進行驗證(想一想,為什么只在最后一級做驗證,而不是每級都做...),一旦發現當前的傳輸塊簽名與在客戶端中的簽名不一致,整個數據包的寫入被視為無效,Lease Recover(租約恢復)算法被觸發。。。
            從基本原理上看,這個算法很簡單,就是取所有數據服務器上此數據塊的最小長度當作正確內容的長度,將其他數據服務器上此數據塊超出此長度的部分切除。從正確性上看,此算法無疑是正確的,因為至少有一個數據服務器會發現此錯誤,并拒絕寫入,那么,如果寫入了的,都是正確的;從效率上看,此算法也是高效的,因為它避免了重復的傳輸和復雜的驗證,僅僅是各自刪除尾部的一些內容即可。但從具體實現上來看,此算法稍微有些繞,因為,為了降低本已不堪重負的主控服務器的負擔,此算法不是由主控服務器這個大腦發起的,而是通過選舉一個數據服務器作為Primary,由Primary發起,通過調用與其他各數據服務器間的InterDatanodeProtocol協議,最終完成的。具體的算法流程,參見LeaseManager類上面的注釋。需要說明的是此算法的觸發時機和發起者。此算法可以由客戶端或者是主控服務器發起,當客戶端在寫入一個數據包失敗后,會發起租約恢復。因為,一次寫入失敗,不論是何種原因,很有可能就會導致流水線上有的服務器寫了,有的沒寫,從而造成不統一。而主控服務器發起的時機,則是在占有租約的客戶端超出一定時限沒有續簽,這說明客戶端可能掛了,在臨死前可能干過不利于數據塊統一的事情,作為監督者,主控服務器需要發起一場恢復運動,確保一切正確。。。

            3、負載均衡

            負載的均衡,是分布式系統中一個永恒的話題,要讓大家各盡其力齊心干活,發揮各自獨特的優勢,不能忙得忙死閑得閑死,影響戰斗力。而且,負載均衡也是一個復雜的問題,什么是均衡,是一個很模糊的概念。比如,在分布式文件系統中,總共三百個數據塊,平均分配到十個數據服務器上,就算均衡了么?其實不一定,因為每一個數據塊需要若干個備份,各個備份的分布應該充分考慮到機架的位置,同一個機架的服務器間通信速度更快,而分布在不同機架則更具有安全性,不會在一棵樹上吊死。。。
            在這里說的負載均衡,是寬泛意義上的均衡過程,主要涵蓋兩個階段的事務,一個是在任務初始分配的時候盡可能合理分配,另一個是在事后時刻監督及時調整。。。
            在HDFS中,ReplicationTargetChooser類,是負責實現為新分配的數據塊尋找婆家的。基本上來說,數據塊的分配工作和備份的數量、申請的客戶端地址(也就是寫入者)、已注冊的數據服務器位置,密切相關。其算法基本思路是只考量靜態位置信息,優先照顧寫入者的速度,讓多份備份分配到不同的機架去。具體算法,自行參見源碼。此外,HDFS的Balancer類,是為了實現動態的負載調整而存在的。Balancer類派生于Tool類,這說明,它是以一個獨立的進程存在的,可以獨立的運行和配置。它運行有NamenodeProtocolClientProtocol兩個協議,與主控服務器進行通信,獲取各個數據服務器的負載狀況,從而進行調整。主要的調整其實就是一個操作,將一個數據塊從一個服務器搬遷到另一個服務器上。Balancer會向相關的目標數據服務器發出一個DataTransferProtocol.OP_REPLACE_BLOCK消息,接收到這個消息的數據服務器,會將數據塊寫入本地,成功后,通知主控服務器,刪除早先的那個數據服務器上的同一塊數據塊。具體的算法請自行參考源碼。。。

            4、垃圾回收

            對于垃圾,大家應該耳熟能詳了,在分布式文件系統而言,沒有利用價值的數據塊備份,就是垃圾。在現實生活中,我們提倡垃圾分類,為了更好的理解分布式文件系統的垃圾收集,搞個分類也是很有必要的。基本上,所有的垃圾都可以視為兩類,一類是由系統正常邏輯產生的,比如某個文件被刪除了,所有相關的數據塊都淪為垃圾了,某個數據塊被負載均衡器移動了,原始數據塊也不幸成了垃圾了。此類垃圾最大的特點,就是主控服務器是生成垃圾的罪魁禍首,也就是說主控服務器完全了解有哪些垃圾需要處理。另外還有一類垃圾,是由于系統的一些異常癥狀產生的,比如某個數據服務器停機了一段,重啟之后發現其上的某個數據塊已經在其他服務器上重新增加了此數據塊的備份,它上面的那個備份過期了失去價值了,需要被當作垃圾來處理了。此類垃圾的特點恰恰相反,主控服務器無法直接了解到垃圾狀況,需要曲線救國。。。
            在HDFS中,第一類垃圾的判定自然很容易,在一些正常的邏輯中產生的垃圾,全部被塞進了FSNamesystemrecentInvalidateSets這個Map中。而第二類垃圾的判定,則放在數據服務器發送其數據塊信息來的過程中,經過與本地信息的比較,可以斷定,此數據服務器上有哪些數據塊已經不幸淪為垃圾。同樣,這些垃圾也被塞到recentInvalidateSets中去。在與數據服務器進行心跳交流的過程中,主控服務器會將它上面有哪些數據塊需要刪除,數據服務器對這些數據塊的態度是,直接物理刪除。在GFS的論文中,對如何刪除一個數據塊有著不同的理解,它覺著應該先緩存起來,過幾天沒人想恢復它了再刪除。在HDFS的文檔中,則明確表示,在現行的應用場景中,沒有需要這個需求的地方,因此,直接刪除就完了。這說明,理念是一切分歧的根本:)。。。

            V. 總結

            整個分布式文件系統,計算系統,數據庫系統的設計理念,基本是一脈相承的。三類服務器、作為單點存在的核心控制服務器、基于日志的恢復機制、基于租約的保持聯系機制、等等,在后續分布式計算系統和分布式數據庫中都可以看到類似的影子,在分布式文件系統這里,我詳述了這些內容,可能在后續就會默認知道而說的比較簡略了。而刨去這一些,分布式文件系統中最大特點,就是文件塊的冗余存儲,它直接導致了較為復雜的寫入流程。當然,雖說分布式文件系統在分布式計算和數據庫中都有用到,但如果對其機理沒有興趣,只要把它當成是一個可以在任何機器上使用的文件系統,就不會對其他上層建筑的理解產生障礙。。。

            posted @ 2010-02-03 10:28 不會飛的鳥 閱讀(321) | 評論 (0)編輯 收藏

            SOCKS5協議的原理和應用

            首先解釋一下為什么它被稱之為SOCKS。其實該協議設計之初是為了讓有權限的用戶可以穿過過防火墻的限制,使得高權限用戶可以訪問一般用戶不能訪問的外部資源。當時設計者考慮到幾乎所有使用TCP/IP通信的應用軟件都使用socket(套接字,實際上是一組應用程序接口)完成底層的數據通信。為了方便軟件開發者使用該協議,協議設計者就刻意對應了幾組socket編程最經典的操作,并且將協議定名為SOCKS。

            最先被廣泛使用的SOCKS協議是其第四版本,就是SOCKS4。IE和一些其他應用程序直接用“Socks”表示SOCKS4協議。該版本支持TCP的connect(作為客戶端連接)和listen(打開一個監聽端口),不支持UDP協議。SOCKS4A對SOCKS4作了一點增強,即允許客戶端將域名發送給SOCKS服務器,讓SOCKS服務器進行域名解析。

            SOCKS5是第五版,相對第四版作了大幅度的增強。首先,它增加了對UDP協議的支持;其次,它可以支持多種用戶身份驗證方式和通信加密方式;最后,修改了SOCKS服務器進行域名解析的方法,使其更加優雅。經過這次脫胎換骨的升級,SOCKS5于1996年被IETF確認為標準通信協議,RFC編號為1928。經過10余年的時間,大量的網絡應用程序都支持SOCKS5代理。

            SOCKS5雖然可以支持多種用戶身份驗證方式,但是應用程序真正實現的一般也只有兩種:不驗證和用戶名密碼驗證。所以大多數應用程序SOCKS5代理設置也只有用戶名/密碼這一種可選驗證方法。另外,盡管從SOCKS4開始,就支持打開TCP監聽端口,但是直到SOCKS5,也只允許這個端口接收一個客戶端連接。因此網絡服務提供者(如http服務器)不能使用SOCKS。實際上,很多SOCKS服務器的實現也不支持打開TCP監聽端口。

            由于SOCKS5實際上仍然對應了socket的經典操作,所以有人利用這一點編寫了一種通用軟件,可以讓不支持SOCKS5協議的應用軟件也能通過SOCKS5服務器進行網絡通信,而應用軟件則對此一無所知。這類軟件最著名的莫過于SocksCap32了,它是Permeo公司(其前身是NEC北美公司的一個部門,而SOCKS最初就是NEC北美公司的工程師開發并維護的)早期推出的一款產品。用戶可以免費使用其試用版。試用版和正式版相比,沒有功能上的限制,只有使用時間的限制。但是到目前為止,Permeo總是會在老版本到期之前推出一個延后了期限的“新”版本,所以用戶實際上可以免費使用。SocksCap32是利用API鉤子,截獲應用軟件對socket函數的調用來實現對SOCKS5客戶端的模擬。盡管SocksCap32很有名,但是由于推出的時間較早,對很多現代應用軟件時常表現的力不從心,所以Permeo又提供了Permeo Security Driver(以下稱為PSD)。這款產品使用了驅動技術從底層直接截獲應用軟件的socket通信,因此幾乎可以為所有應用軟件提供SOCKS5客戶端的支持。PSD不提供試用版,但是可以找到其早期版本的注冊碼。

            雖然說設計SOCKS協議的初衷是在保證網絡隔離的情況下,提高部分人員的網絡訪問權限,但是國內似乎很少有組織機構這樣使用。一般情況下,大家都會使用更新的網絡安全技術來達到相同的目的。但是由于SocksCap32和PSD這類軟件,人們找到了SOCKS協議新的用途——突破網絡通信限制,這和該協議的初衷實際上正好相反。比如某些網游的部分服務器設置為只接收部分地區的IP地址的連接。為了突破這種限制,可以找一個該地區的SOCKS5代理服務器,然后用PSD接管網游客戶端,通過SOCKS5代理服務器連接游戲服務器。這樣游戲服務器就會認為該客戶端位于本地區,從而允許進行游戲。還有一種情況是:防火墻僅允許部分端口(如http的80端口)通信,那么可以利用SOCKS5協議和一個打開80端口監聽的SOCKS5服務器連接,從而可以連接公網上其他端口的服務器。利用一些額外的技術手段,甚至可以騙過內部的http代理服務器,這時在使用內網http代理上網的環境下也可以不受限制的使用網絡服務,這稱之為SOCKS over HTTP。通通通([url]www.tongtongtong.com[/url])是老牌SOCKS over HTTP代理提供商,實現了所有的SOCKS5的連接功能,且有多組國內外服務器。信天游([url]www.xtyproxy.com[/url]),則是最近剛剛出現的代理服務提供商,功能和通通通相比還有差距,但是目前完全免費。當然,使用代理服務器后,將不可避免的出現通信延遲,所以應該盡量選擇同網絡(指網通/ 電信),距離近的服務器。

            sock5代理的工作程序是:
            1.需要向代理方服務器發出請求信息。
            2.代理方應答
            3.需要代理方接到應答后發送向代理方發送目的ip和端口
            4.代理方與目的連接
            5.代理方將需要代理方發出的信息傳到目的方,將目的方發出的信息傳到需要代理方。代理完成。
            由于網上的信息傳輸都是運用tcp或udp進行的,所以使用socks5代理可以辦到網上所能辦到的一切,而且不輿目的方會查到你的ip,既安全又方
            便 sock5支持UDP和TCP,但兩種代理是有區別的,以下分類說明
            如何用代理TCP協議
            1.向服務器的1080端口建立tcp連接。
            2.向服務器發送 05 01 00 (此為16進制碼,以下同)
            3.如果接到 05 00 則是可以代理
            4.發送 05 01 00 01 + 目的地址(4字節) + 目的端口(2字節),目的地址和端口都是16進制碼(不是字符串!!)。 例202.103.190.27 -7201 則發送的信息為:05 01 00 01 CA 67 BE 1B 1C 21 (CA=202 67=103 BE=190 1B=27 1C21=7201)
            5.接受服務器返回的自身地址和端口,連接完成
            6.以后操作和直接與目的方進行TCP連接相同。
            如何用代理UDP連接
            1.向服務器的1080端口建立udp連接
            2.向服務器發送 05 01 00
            3.如果接到 05 00 則是可以代理
            4.發送 05 03 00 01 00 00 00 00 + 本地UDP端口(2字節)
            5.服務器返回 05 00 00 01 +服務器地址+端口
            6.需要申請方發送 00 00 00 01 +目的地址IP(4字節)+目的端口 +所要發送的信息
            7.當有數據報返回時 向需要代理方發出00 00 00 01 +來源地址IP(4字節)+來源端口 +接受的信息
            注:此為不需要密碼的代理協議,只是socks5的一部分,完整協議請RFC1928

            posted @ 2009-12-26 19:39 不會飛的鳥 閱讀(19471) | 評論 (0)編輯 收藏

            (RFC1929)SOCKS V5的用戶名/密碼鑒定

            SOCKS V5的用戶名/密碼鑒定
            (RFC1929 Username/Password Authentication for SOCKS V5)

             

            本備忘錄狀態:
            本文檔講述了一種Internet社區的Internet標準跟蹤協議,它需要進一步進行討論和建議以得到改進。請參考最新版的“Internet正式協議標準” (STD1)來獲得本協議的標準化程度和狀態。本備忘錄的發布不受任何限制。

            1. 介紹
            關于SOCKS V5的協議規范說明了在初始化SOCKS連接時所用到的任意驗證協議的大致框架。這篇文檔描述了這些協議中的其中一個適合SOCKS V5驗證子協商(subnegotiation)。
            注意:
            除非特別注明,所有出現在數據包格式圖中的十進制數字均以字節表示相應域的長度。如果某域需要給定一個字節的值,用X’hh’來表示這個字節中的值。如果某域中用到單詞’Variable’,這表示該域的長度是可變的,且該長度定義在一個和這個域相關聯(1 – 2個字節)的域中,或一個數據類型域中。

            2.初始協商
            一旦SOCKS V5服務器運行并且客戶端選擇了用戶名/密碼認證協議以后,就開始了用戶名/密碼協議的子協商過程。客戶端先產生一個用戶名/密碼協議的請求:

            VER ULEN UNAME PLEN PASSWD
            1 1 1 to 255 1 1 to 255

            VER中指明了子協商的當前版本,現在使用的是X’01’。ULEN域中包含了下一個UNAME域的長度。UNAME中包含一個源操作系統(source operating system)所知道的用戶名。PLEN中指明了緊隨其后的PASSWD的長度。PASSWD中則包含了對應UNAME用戶的密碼。
            服務器驗證用戶名和密碼,并且返回:

            VER STATUS
            1 1

            如果STATUS中返回X’00’則說明通過驗證。如果服務器返回非X’00’則說明驗證失敗,并且關閉連接。

            3.安全考慮
            這篇文檔描述了為SOCKS V5協議提供驗證服務的子協商過程。因為密碼是以明文傳輸的,所以這個子協商過程在可能被工具“嗅探(sniffing)”到的環境中不建議使用該子協商過程。

            posted @ 2009-12-26 19:36 不會飛的鳥 閱讀(606) | 評論 (0)編輯 收藏

            (RFC1928)Socket5協議中文文檔

            譯者:Radeon(Radeon bise@cmmail.com)
            譯文發布時間:2001-6-18

            目錄

            1.介紹
            2.現有的協議
            3.基于TCP協議的客戶
            4.請求
            5.地址
            6.應答
            7.基于UDP協議的客戶
            8. 安全性考慮
            9. 參考書目

            1.介紹

            利用網絡防火墻可以將組織內部的網絡結構從外部網絡如INTERNET中有效地隔離,這種方法在許多網絡系統中正變得流行起來。這種防火墻系統通常以應用層網關的形式工作在兩個網絡之間,提供TELNET、FTP、SMTP等的接入。隨著越來越多的使全球信息查找更容易的復雜的應用層協議的出現,有必要提供一個通用框架來使這些協議安全透明地穿過防火墻。而且在實際應用中還需要一種安全的認證方式用以穿越防火墻。這個要求起源于兩個組織的網絡中客戶/服務器關系的出現,這個關系需要得到控制并要求有安全的認證。
            在這兒所描述的協議框架是為了讓使用TCP和UDP的客戶/服務器應用程序更方便安全地使用網絡防火墻所提供的服務所設計的。這個協議從概念上來講是介于應用層和傳輸層之間的“中介層(shim-layer)”,因而不提供如傳遞ICMP信息之類由網絡層網關的所提供的服務。


            2.現有的協議
            當前存在一個協議SOCKS 4,它為TELNET、FTP、HTTP、WAIS和GOPHER等基于TCP協議的客戶/服務器程序提供了一個不安全的防火墻。而這個新的協議擴展了SOCKS V4,以使其支持UDP、框架規定的安全認證方案、地址解析方案(addressing scheme)中所規定的域名和IPV6。為了實現這個SOCKS協議,通常需要重新編譯或者重新鏈接基于TCP的客戶端應用程序以使用SOCKS庫中相應的加密函數。
            注意:
            除非特別注明,所有出現在數據包格式圖中的十進制數字均以字節表示相應域的長度。如果某域需要給定一個字節的值,用X’hh’來表示這個字節中的值。如果某域中用到單詞’Variable’,這表示該域的長度是可變的,且該長度定義在一個和這個域相關聯(1 – 2個字節)的域中,或一個數據類型域中。


            3.基于TCP協議的客戶
            當一個基于TCP協議的客戶端希望與一個只能通過防火墻可以到達的目標(這是由實現所決定的)建立連接,它必須先建立一個與SOCKS服務器上SOCKS端口的TCP連接。通常這個TCP端口是1080。當連接建立后,客戶端進入協議的“握手(negotiation)”過程:認證方式的選擇,根據選中的方式進行認證,然后發送轉發的要求。SOCKS服務器檢查這個要求,根據結果,或建立合適的連接,或拒絕。
            除非特別注明,所有出現在數據包格式圖中的十進制數字均以字節表示相應域的長度。如果某域需要給定一個字節的值,用X’hh’來表示這個字節中的值。如果某域中用到單詞’Variable’,這表示該域的長度是可變的,且該長度定義在一個和這個域相關聯(1 – 2個字節)的域中,或一個數據類型域中。
            客戶端連到服務器后,然后就發送請求來協商版本和認證方法:

            VER NMETHODS METHODS
            1 1 1 to 255

            這個版本的SOCKS協議中,VER字段被設置成X'05'。NMETHODS字段包含了在METHODS字段中出現的方法標示的數目(以字節為單位)。
            服務器從這些給定的方法中選擇一個并發送一個方法選中的消息回客戶端:

            VER METHOD
            1 1

            如果選中的消息是X’FF’,這表示客戶端所列出的方法列表中沒有一個方法被選中,客戶端必須關閉連接。
            當前定義的方法有:
            · X’00’ 不需要認證
            · X’01’ GSSAPI
            · X’02’ 用戶名/密碼
            · X’03’ -- X’7F’ 由IANA分配
            · X’80’ -- X’FE’ 為私人方法所保留的
            · X’FF’ 沒有可以接受的方法
            然后客戶和服務器進入由選定認證方法所決定的子協商過程(sub-negotiation)。各種不同的方法的子協商過程的描述請參考各自的備忘錄。
            開發者如果要為自己的方法得到一個方法號,可以聯系IANA。可以參考關于已經被分配號碼的文檔以得到當前所有方法的列表和相應的協議。
            符合本文檔的SOCKS V5實現必須支持GSSAPI,并且在將來支持用戶名/密碼認證方式。

            4.請求

            一旦子協商過程結束后,客戶端就發送詳細的請求信息。如果協商的方法中有以完整性檢查和/或安全性為目的的封裝,這些請求必須按照該方法所定義的方式進行封裝。
            SOCKS請求的格式如下:

            VER CMD RSV ATYP DST.ADDR DST.PROT
            1 1 X’00’ 1 Variable 2

            其中
            · VER 協議版本: X’05’
            · CMD
            · CONNECT:X’01’
            · BIND:X’02’
            · UDP ASSOCIATE:X’03’
            · RSV 保留
            · ATYP 后面的地址類型
            · IPV4:X’01’
            · 域名:X’03’
            · IPV6:X’04’'
            · DST.ADDR 目的地址
            · DST.PORT 以網絡字節順序出現的端口號
            SOCKS服務器會根據源地址和目的地址來分析請求,然后根據請求類型返回一個或多個應答。

            5.地址
            ATYP字段中描述了地址字段(DST.ADDR,BND.ADDR)所包含的地址類型:
            · X'01'
            基于IPV4的IP地址,4個字節長
            · X'03'
            基于域名的地址,地址字段中的第一字節是以字節為單位的該域名的長度,沒有結尾的NUL字節。
            · X'04'
            基于IPV6的IP地址,16個字節長


            6.應答
            一旦建立了一個到SOCKS服務器的連接,并且完成了認證方式的協商過程,客戶機將會發送一個SOCKS請求信息給服務器。服務器將會根據請求,以如下格式返回:

            VER REP RSV ATYP BND.ADDR BND.PORT
            1 1 X’00’ 1 Variable 2

            其中:
            · VER 協議版本: X’05’
            · REP 應答字段:
            · X’00’ 成功
            · X’01’ 普通的SOCKS服務器請求失敗
            · X’02’ 現有的規則不允許的連接
            · X’03’ 網絡不可達
            · X’04’ 主機不可達
            · X’05’ 連接被拒
            · X’06’ TTL超時
            · X’07’ 不支持的命令
            · X’08’ 不支持的地址類型
            · X’09’ – X’FF’ 未定義
            · RSV 保留
            · ATYP 后面的地址類型
            · IPV4:X’01’
            · 域名:X’03’
            · IPV6:X’04’
            · BND.ADDR 服務器綁定的地址
            · BND.PORT 以網絡字節順序表示的服務器綁定的段口
            標識為RSV的字段必須設為X’00’。
            如果選中的方法中有以完整性檢查和/或安全性為目的的封裝,這些應答必須按照該方法所定義的方式進行封裝。

            CONNECT
            在對一個CONNECT命令的應答中,BND.PORT包含了服務器分配的用來連到目標機的端口號,BND.ADDR則是相應的IP地址。由于SOCKS服務器通常有多個IP,應答中的BND.ADDR常和客戶端連到SOCKS服務器的那個IP不同。

            SOCKS服務器可以利用DST.ADDR和DST.PORT,以及客戶端源地址和端口來對一個CONNECT請求進行分析。

            BIND
            BIND請求通常被用在那些要求客戶端接受來自服務器的連接的協議上。FTP是一個典型的例子。它建立一個從客戶端到服務器端的連接來執行命令以及接收狀態的報告,而使用另一個從服務器到客戶端的連接來接收傳輸數據的要求(如LS,GET,PUT)。
            建議只有在一個應用協議的客戶端在使用CONNECT命令建立主連接后才可以使用BIND命令建立第二個連接。建議SOCKS服務器使用DST.ADDR和DST.PORT來評價BIND請求。
            在一個BIND請求的操作過程中,SOCKS服務器要發送兩個應答給客戶端。當服務器建立并綁定一個新的套接口時發送第一個應答。BND.PORT字段包含SOCKS服務器用來監聽進入的連接的端口號,BAND.ADDR字段包含了對應的IP地址。客戶端通常使用這些信息來告訴(通過主連接或控制連接)應用服務器連接的匯接點。第二個應答僅發生在所期望到來的連接成功或失敗之后。在第二個應答中,BND.PORT和BND.ADDR字段包含了連上來的主機的IP地址和端口號。

            UDP ASSOCIATE
            UDP ASSOCIATE請求通常是要求建立一個UDP轉發進程來控制到來的UDP數據報。DST.ADDR和DST.PORT 字段包含客戶端所希望的用來發送UDP數據報的IP地址和端口號。服務器可以使用這個信息來限制進入的連接。如果客戶端在發送這個請求時沒有地址和端口信息,客戶端必須用全0來填充。
            當與UDP相應的TCP連接中斷時,該UDP連接也必須中斷。
            應答UDP ASSOCIATE請求時,BND.PORT 和BND.ADDR字段指明了客戶發送UDP消息至服務器的端口和地址。

            應答處理
            當一個應答(REP值不等于00)指明出錯時,SOCKS服務器必須在發送完應答消息后一小段時間內終止TCP連接。這段時間應該在發現錯誤后少于10秒。
            如果一個應答(REP值等于00)指明成功,并且請求是一個BIND或CONNECT時,客戶端就可以開始發送數據了。如果協商的認證方法中有以完整性、認證和/或安全性為目的的封裝,這些請求必須按照該方法所定義的方式進行封裝。類似的,當以客戶機為目的地的數據到達SOCKS服務器時,SOCKS服務器必須用正在使用的方法對這些數據進行封裝。


            7.基于UDP協議的客戶
            在UDP ASSOCIATE應答中由BND.PORT指明了服務器所使用的UDP端口,一個基于UDP協議的客戶必須發送數據報至UDP轉發服務器的該端口上。如果協商的認證方法中有以完整性、認證和/或安全性為目的的封裝,這些數據報必須按照該方法所定義的方式進行封裝。每個UDP數據報都有一個UDP請求頭在其首部:

            RSV FRAG ATYP DST.ADDR DST.PORT DATA
            2 1 1 Variable 2 Variable

            在UDP請求頭中的字段是:

            · RSV 保留 X’0000’
            · FRAG 當前的分段號
            · ATYP 后面的地址類型
            · IPV4:X’01’
            · 域名:X’03’
            · IPV6:X’04’
            · DST.ADDR 目的地址
            · DST.PORT 以網絡字節順序出現的端口號
            · DATA 用戶數據
            當一個UDP轉發服務器轉發一個UDP數據報時,不會發送任何通知給客戶端;同樣,它也將丟棄任何它不能發至遠端主機的數據報。當UDP轉發服務器從遠端服務器收到一個應答的數據報時,必須加上上述UDP請求頭,并對數據報進行封裝。
            UDP轉發服務器必須從SOCKS服務器得到期望的客戶端IP地址,并將數據報發送到UDP ASSOCIATE應答中給定的端口號。如果數據報從任何IP地址到來,而該IP地址與該特定連接中指定的IP地址不同,那么該數據報會被丟棄。
            FRAG字段指明數據報是否是一些分片中的一片。如果SOCKS服務器要實現這個功能,X’00’指明數據報是獨立的;其他則越大越是數據報的尾端。介于1到127之間的值說明了該分片在分片序列里的位置。每個接收者都為這些分片提供一個重組隊列和一個重組的計時器。這個重組隊列必須在重組計時器超時后重新初始化,并丟棄相應的數據報。或者當一個新到達的數據報有一個比當前在處理的數據報序列中最大的FRAG值要小時,也必須重新初始化從組隊列。重組計時器必須小于5秒。只要有可能,應用程序最好不要使用分片。
            分片的實現是可選的;如果某實現不支持分片,所有FRAG字段不為0的數據報都必須被丟棄。
            一個SOCKS的UDP編程界面(The programming interface for a SOCKS-aware UDP)必須報告當前可用UDP數據報緩存空間小于操作系統提供的實際空間。
            · 如果 ATYP是 X’01’ - 10+method_dependent octets smaller
            · 如果 ATYP是X’03’ - 262+method_dependent octets smaller
            · 如果 ATYP是X’04’ - 20+method_dependent octets smaller

            8. 安全性考慮
            這篇文檔描述了一個用來透過IP網絡防火墻的應用層協議。這種傳輸的安全性在很大程度上依賴于特定實現所擁有以及在SOCKS客戶與SOCKS服務器之間經協商所選定的特殊的認證和封裝方式。
            系統管理員需要對用戶認證方式的選擇進行仔細考慮。

            posted @ 2009-12-26 19:35 不會飛的鳥 閱讀(662) | 評論 (0)編輯 收藏

            手把手教你安裝SVN

            1.安裝程序準備
            你需要準備TortoiseSVN-1.5.5.14361-win32-svn-1.5.4.msi和VisualSVN-1.5.4.msi兩個安裝程序,其中TortoiseSVN-1.5.5.14361-win32-svn-1.5.4.msi安裝之后主要用于察看和管理使用;VisualSVN-1.5.4.msi主要是為了VS2005使用。
            先安裝TortoiseSVN-1.5.5.14361-win32-svn-1.5.4.msi,一直下一步到底,然后安裝VisualSVN-1.5.4.msi一直下一步到底,
            提示:如果你不使用VS2005,那么不需要安裝VisualSVN-1.5.4.msi

            2.破解VisualSVN
            由于默認安裝VisualSVN-1.5.4.msi只有29天使用時限,下面說破解方法:
            提示:只針對于VisualSVN 1.5.x
            第一步:首先去認你系統安裝了.NET Framework;
            第二步:進入.NET Framework命令提示符,輸入或者直接復制:
            ildasm "C:\Program Files\VisualSVN\bin\VisualSVN.Core.dll" /out="C:\Program Files\VisualSVN\bin\VisualSVN.Core.il"
            第三步: 切換到“C:\Program Files\VisualSVN\bin\”目錄,使用文本編輯器打開剛才輸出的il文件,查找
            .method public hidebysig static bool  IsValid(
            將該方法括號({})內的代碼體替換成
            .maxstack  8

            IL_0000:  ldc.i4.
            1

            IL_0001:  ret

            第四步: 回到命令行輸入
            ilasm "C:\Program Files\VisualSVN\bin\VisualSVN.Core.il" /dll

            編譯得到新的dll覆蓋原VisualSVN.Core.dll,默認執行后就是覆蓋了。

            第五步: 破解完畢,打開VS.NET,點擊VisualSVN菜單->Registration,輸入任意字符點擊OK注冊成功。

            posted @ 2009-12-16 14:03 不會飛的鳥 閱讀(1702) | 評論 (2)編輯 收藏

            powerdesigner中怎么給一主鍵設為自增型auto_increment-針對于Mysql數據庫

            在你所要設為自增型的鍵上(比如你的id)雙擊,彈出一個Column Properties對話框,右下角有一個Identify的選擇框,選中它OK,就可以了。
            再去查看Preview,就能看到用大寫標識出來的AUTO_INCREMENT。

            posted @ 2009-12-16 13:59 不會飛的鳥 閱讀(1086) | 評論 (0)編輯 收藏

            在Linux操作系統下修改IP、DNS和路由配置

            在RH Linux下修改IP、DNS和路由配置

              ifconfig eth0 新ip

              然后編輯/etc/sysconfig/network-scripts/ifcfg-eth0,修改ip

              一、修改IP地址

              [aeolus@db1 network-scripts]$ vi ifcfg-eth0

              DEVICE=eth0

              ONBOOT=yes

              BOOTPROTO=static

              IPADDR=219.136.241.211

              NETMASK=255.255.255.128

              GATEWAY=219.136.241.254

              二、修改網關

              vi /etc/sysconfig/network

              NETWORKING=yes

              HOSTNAME=Aaron

              GATEWAY=192.168.1.1

              三、修改DNS

              [aeolus@db1 etc]$ vi resolv.conf

              nameserver 202.96.128.68

              nameserver 219.136.241.206

              四、重新啟動網絡配置

              /etc/init.d/network restart

              修改ip地址

              即時生效:

              # ifconfig eth0 192.168.0.20 netmask 255.255.255.0

              啟動生效:

              修改/etc/sysconfig/network-scripts/ifcfg-eth0

              修改default gateway

              即時生效:

              # route add default gw 192.168.0.254

              啟動生效:

              修改/etc/sysconfig/network-scripts/ifcfg-eth0

              修改dns

              修改/etc/resolv.conf

              修改后可即時生效,啟動同樣有效

              修改host name

              即時生效:

              # hostname fc2

              啟動生效:

              修改/etc/sysconfig/network.

            posted @ 2009-11-12 09:18 不會飛的鳥 閱讀(252) | 評論 (0)編輯 收藏

            僅列出標題
            共9頁: 1 2 3 4 5 6 7 8 9 
            国内精品久久久久久久影视麻豆| 亚洲欧美一区二区三区久久| 久久亚洲国产中v天仙www| 久久香蕉国产线看观看乱码| 久久久久无码中| 国产精品无码久久综合| 久久久久国产日韩精品网站| 亚洲国产精品无码久久久不卡 | 9999国产精品欧美久久久久久| 26uuu久久五月天| 香蕉久久夜色精品升级完成| 精品久久人人爽天天玩人人妻 | 伊人久久综在合线亚洲2019 | 国产成人AV综合久久| 亚洲熟妇无码另类久久久| 久久精品国产第一区二区| 久久免费国产精品一区二区| 午夜天堂av天堂久久久| 午夜精品久久久久成人| 国内精品久久久久久久久电影网 | 日本亚洲色大成网站WWW久久| 日韩亚洲欧美久久久www综合网| 亚洲欧美成人综合久久久| 欧美精品丝袜久久久中文字幕| 久久亚洲综合色一区二区三区| 久久香蕉超碰97国产精品| 久久香综合精品久久伊人| 久久综合久久鬼色| 久久噜噜久久久精品66| 精品视频久久久久| 国产真实乱对白精彩久久| 亚洲嫩草影院久久精品| 久久国产精品久久久| 国产精品久久久久AV福利动漫| 日本久久久久亚洲中字幕| 亚洲成色WWW久久网站| 亚洲精品无码成人片久久| 久久精品中文闷骚内射| 精品人妻久久久久久888| 97久久天天综合色天天综合色hd| 国产三级久久久精品麻豆三级|