weed-fs 源碼解讀—分佈式處理過程

上次看完 weed-fs 在存儲處理上的代碼之後,原想着能很快把分佈式處理這一塊的代碼看完的,沒想到到現在還是處於半懵懂狀態,只能邊寫邊整理思路了。

由於我經驗尚淺,加上學習上走了不少的彎路,所以對很多東西真的是完全不懂。像 weed-fs 這樣子的東西,它們是如何處理分佈式的,如何把數據分不到不到結點上的,如何保證數據一致性的等等這些問題我都不知道答案。因此這次剛好藉着 weed-fs,以點帶面,逐步把這一塊的相關知識給補全了。

weed-fs 分佈式處理的代碼,主要在 topology 和 weed/weed_server 這兩個包裏面。 weed_server 主要提供外部以及結點間訪問接口, 而 topology 則是表達結點特徵以及處理結點間關係的。

注意以下行文提及 server 時, 分兩種,volume_server 指的是 VolumeServer 這個結構的完整實現。

weed_server 的代碼佈局如圖:

filer_server 上篇文章已經提及,此處不表。

關於分佈式處理的,就主要是三種 server: master_server, volume_server, raft_server。 這裏 master_server 和 volume_server 都比較好理解, 所有的請求都先放給 master_server, 然後 master_server 再決定發送給那個 volume_server。

關於 raft_server, 則不引申過多,首先要記住的是我們這裏使用了一個 GO 語言實現的 Raft 協議,即 goraft, 爲了使用 goraft,使得它能爲 weed-fs 這種結構服務,那就需要告訴它一些東西,比如配置文件放哪了,你數據結點的拓撲結構是怎麼樣的, 發過來的命令要怎麼處理等等。 所以 raft_server 乾的就是初始化好一個 raft 客戶端 , 然後 Set 到 MasterServer 上, 讓 master_server 一致性判斷等功能。

所以,很明顯,master_server 是這部分代碼的核心,因此理解代碼也先從 master_server 開始。 但是要理解 server 部分的代碼,又要先了解 topology 部分的代碼。 而瞭解 topology 部分的代碼,又需要知道 server 部分的代碼,只有這樣才能夠了解 topology 部分的代碼是用來幹什麼的。 正是因爲這種交叉關聯,所以讀懂代碼真的是好難呀。

首先,要明確瞭解的一點:weed-fs 是可以同時啓動多個 master_server 的,然後由 Raft 選出一個 Leader。 你發送請求到指定的 master_server 上去,然後每個 master_server 都會把請求轉發到 Leader 這個 master_server 上來,由它發送給各個 volume 結點。

先來看看 MasterServer 的結構:

type MasterServer struct {
  port					int
  metaFolder			  string
  volumeSizeLimitMB	   uint
  pulseSeconds			int
  defaultReplicaPlacement string
  garbageThreshold		string
  whiteList			   []string

  Topo   *topology.Topology
  vg	 *topology.VolumeGrowth
  vgLock sync.Mutex

  bounedLeaderChan chan int
}

port 是指這個 MasterServer 開放給外部的端口;

metaFolder 是啓動 master_server 時指定的文件夾,裏面放着這個 master_server 的元數據。命令 weed master -mdir="." 就會在當前文件夾上啓動一個 master_server 。裏面有幾個初始文件,看看就大概明白。

pulseSeconds 是指心跳指令發送時間間隔, 主要是用於 Leader 監控各個結點,把死結點從網絡中移除出去。

defaultReplicaPlacement 是 001, 200, 110 等等這樣的字符串,表示一種複製策略, 具體含義可以去看 weed-fs 的文檔,上面的說明已經很清晰了。

whiteList 是信任白名單,爲了安全而設置的,暫時不管。

bounedLeaderChan 起到類似於鎖的功能,於我們要理解的部分關係不大,暫時不管。

Topo 和 vg 這兩個就難點了,特別是 vg, 到底 VolumeGrowth 的作用是什麼,現在我還不是很清楚。

現在則要回過頭來看看 weed-fs 的 Replica (複製集)配置腳本:

<Configuration>
  <Topology>
    <DataCenter name="dc1">
      <Rack name="rack1">
        <Ip>192.168.1.1</Ip>
      </Rack>
    </DataCenter>
    <DataCenter name="dc2">
      <Rack name="rack1">
        <Ip>192.168.1.2</Ip>
      </Rack>
      <Rack name="rack2">
        <Ip>192.168.1.3</Ip>
        <Ip>192.168.1.4</Ip>
      </Rack>
    </DataCenter>
  </Topology>
</Configuration>

這裏是把整個搭建起來的 weed-fs 分佈式網絡的結點佈局稱之爲 Topology (拓撲),完整的佈局下有 DataCenter (數據中心), 每個數據中心下會有多個 Rack (支架,可理解爲服務器機器集中放置的機櫃), 每個 Rack 下有多個 DataNode (結點)。

所以,這裏對 Topo 的理解就明白多了。 vg 暫時先不管,後面再提及。

現在來看看 VolumeServer 的結構:

type VolumeServer struct {
  masterNode   string
  pulseSeconds int
  dataCenter   string
  rack		 string
  whiteList	[]string
  store		*storage.Store
  FixJpgOrientation bool
}

從上可以看出, 每個 volume_server 都和一個 master_server 聯繫,同時也記錄自己屬於哪個 rack,哪個 dataCenter。

FixJpgOrientation 是存儲圖片時是否要對圖片進行處理的選項,這裏不管。

這裏值得注意的是 store, storage.Store 是什麼? 上一篇在存儲的關係圖裏面已經提及了 Store 了。 實際上,我們可以把 storage.Store 看做是每個 volume_server 裏對硬盤上數據的管理者。

啓動一個 volume_server 的命令: weed volume -max=100,90 -mserver="localhost:9333" -dir="./data1,./data2" 。

這裏實際上是將 data1 和 data2 這兩個文件夾都作爲 volume 數據,即上篇所說的 dat 數據存放處, 用逗號分隔。 -max 表示這個文件夾裏可以生成的 dat 文件數量。 mserver 指定了 master_server

這裏可以看到, 每個 volume_server 其實都關聯着一個 master_server, 同時標記自己屬於哪個 rack, 哪個 dataCenter。

現在則又牽扯到另一個問題,即 rack 和 dataCenter 到底是怎麼樣的存在,以及他們與 volume_server , master_server ,raft_server 的存在之間又有什麼區別?

總的來說是這樣的: Topology, DataCenter, Rack, DataNode 是模擬物理結點的拓撲結構,而 volume_server 則是 DataNode 的對外接口,每個 volume_server 都與一個指定的 master_server 聯繫,但是如何處理則交給 master_server 裏中的 Leader 。

那麼,問題來了:

  • 啓動一個 master server 和啓動第二個 master server 並加入到網絡中到底發生了什麼?
  • 啓動一個 volume 並加入到網絡中又發生了什麼?
  • 一個數據操作請求發送過來,會經過哪些個步驟?
  • 一個 master server 結點死了,會發生什麼事情?
  • 一個 volume server 結點死了,又會發生什麼事情?

先來看第一個問題,啓動一個 master server 的時候發生了什麼,這部分代碼可以在 weed/master.go 和 weed/weed server/master server.go 看到。

首先,在 master.go 裏可以看到, 這裏是先 NewMasterServer,然後 啓動一個 NewRaftServer 並 set 到master server 上, 自動選擇當前master server 爲 Leader。然後去加載 配置 xml,勾勒出當前服務器的拓撲結構。

當前的 master server 被初始化爲 Leader 的代碼就只是這幾句:

_, err := s.raftServer.Do(&raft.DefaultJoinCommand{
    Name: s.raftServer.Name(),
    ConnectionString: "http://" + s.httpAddr,
})

這個 raftServer 是 goraft 實現的那個。上面基本沒有文檔,所以暫時還不太清楚具體緣由。

那啓動了一個 master server, 再啓動一個,與第一個連接,有發生什麼事情呢?

按照如下命令啓動兩個 master server:

weed master -defaultReplication=010
weed master -port=9334 -peers="localhost:9333"

這裏其實我還有兩個疑問的, 上一篇文章我說 volume 上會保存 Replication 模式數據,而這裏啓動的 master server 也設置了一個,這兩者什麼關係呢? 啓動第二個 master server 不加 defaultReplication 參數,會有什麼不同嗎?暫時先把問題擱置一下,後面看看能否解答。

看了代碼,主要的過程是這樣的:

先調用 raft server.go 的 Join 函數,然後到 peers master server的 raftserver_handlers.go 的 joinHandler 函數,然後執行一句:

if _, err:= s.raftServer.Do(command);

這裏應該是試圖讓當前啓動的 master server 成爲 Leader, 然後 raft 協議內部就進行選舉了。

說完了 master server, 就到了 volume server 如何加入到網絡中的問題了。這部分的代碼相當龐大和複雜,許多地方我還沒有看明白。 大致過程就是把一個 volume 封裝爲一個 DataNode, 然後加入到 master server 裏的 Topo 結構中去, 這個結構應該是用一個字典來保存映射關係的。

由於不是很清晰,所以關於 volume server 的內容暫時不多說了。

寫入數據的過程也大致類似。個人感覺這部分的代碼比較複雜和奇怪,沒怎麼看懂,所以略過了。

那麼 volume 的ReplicaPlacement 和 master server 自己的 ReplicaPlacement 有什麼區別呢?

每次提交文件,都可以指定一種 replicaPlacement,如果沒有設定,則默認使用 master server 設定的。而 volume 的 replicaPlacement 則主要在 volume_layout.go 這裏使用,一個 volume 的增刪改都要使用這個進行判斷。這裏則牽涉到 master server 如何根據 replicaPlacment 去找到對應 volume 的過程,因爲同一個 volume id,兩個server都會有,volume_layout 裏就是根據 replica 數量要求,判斷當前 volume id全部加起來的 server 數量是否小於這個數量。

心跳指令的發送則在啓動 server 的時候用一個 goroutine 一直 for 循環執行。

寫到這就發現這篇文章越寫越亂,其實我還有很多個地方沒有搞清楚,但是由於看這份代碼實在花了我太多的時間了,所以想先暫停一下。下一步我打算先了解一下 raft 協議的實現, 然後再用 Lua 實現一個簡單版的 weed—fs ,之所以選擇 Lua,是因爲我們公司是超重度使用 Lua 的遊戲公司,我希望一兩年後可以去做遊戲服務器開發。

要是用 Lua 重寫的話,估計還要繼續再看 weed-fs 的代碼很多次,所以這裏暫時不寫了。等以後有了更深的體會,我會回來繼續寫的。

時常會想,我到底適不適合當個程序員呢?因爲這方面我似乎真的很笨的樣子,一個大學高富帥同學一年的功夫就拿到 CMU 的offer了,又花了一年,就拿到 Facebook的 offer了,而我自稱從大二開始學編程到現在,連騰訊的offer 都拿不到。

轉貼:http://www.tuicool.com/articles/yyUr2e
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章