在Swift 1.8.0(Grizzly)的新特性中有這麼一條:Added support for a region tier above zones,即swift可以允許開發人員將zones組織成一個組進行管理了,這個組就是region。
這兩天在看Swift G版Ring部分的源碼的時候,也發現device字典中增加了region屬性,因此決定仔細的理解一下這個層次概念。
首先推薦兩篇文章,我對region的理解從這兩篇文章中獲益頗多:
第一篇是Swift的重要貢獻者、region特性的貢獻者SwiftStack的文章:A Globally Distributed OpenStack Swift Cluster;
第二篇不止介紹了region的概念,更將region層次下的proxy server等工作流程進行了詳細描述。(這篇需要翻牆-。-)
The Ring without Multi-regions
標準的Swift ring是一個允許你將存放裝置劃分為buckets或者zones的資料結構。在介紹包含regions概念的Ring之前,讓我們先來回顧一下沒有添加region概念版本的Ring結構:
在Essex版本中,Ring builder嚴格的保證同一個對象的不同的副本分布在不同的zones中,否則Ring的建立過程無法完成。因此,Swift developer就必須在叢集中部署至少和副本數一樣多的zones以保證Ring可以被成功建立。
在Folsom版本中,Ring檔案的結構被進行了改動,並重新定義了ring balancing的演算法,從而極大的提高了Ring建立的效率。在這個版本中,原本嚴格的保證副本分布在不同zones中的策略被替換成了另一種更為靈活的演算法,這個新的演算法將zones、nodes、devices組織,從而形成tiers的結構進行分配。
The Ring with Multi-tiers
以下代碼是swift 1.7.6(Folsom)中構造tiers結構的代碼(common.ring.utils.py)。
def tiers_for_dev(dev): """ Returns a tuple of tiers for a given device in ascending order by length. :returns: tuple of tiers """ t1 = dev['zone'] t2 = "{ip}:{port}".format(ip=dev.get('ip'), port=dev.get('port')) t3 = dev['id'] return ((t1,), (t1, t2), (t1, t2, t3))
Folsom版本的ring balancer將同一個對象的副本分配到儘可能遠的位置(as-far-as-possible or as-unique-as-possible)。當然,最完美的方案是將這些副本放置到不同的zones中([zone] tier),但是如果只有一個zone可以使用時,那麼就盡量將副本分配到不同的nodes中([zone ip:port] tier),相似的,當如果只有一個node可以使用時,那麼就盡量將副本分配到這個節點上的不同devices([zone ip:port device] tier)。
這種“as-unique-as-possible”的演算法,具有支援 geographically distributed cluster 的潛質(每向上一層,就增大了一個地理範圍),它可以方便的將一個小的叢集擴充成一個大叢集。因此,我們可以通過增加另一個region tier到這個層次中實現叢集的地理分布支援。一個region本質上是一組共用相同位置的zones,這組zones可以是一個機架,也可以是一個資料中心。
是“as-unique-as-possible”策略下,不同規模Swift中副本存放策略的,其中綠色圓點代表一個副本,橢圓代表一個disk,一個立方體代表一個node...
The Ring with Multi-regions -> A Globally Distributed Cluster
通過多tiers結構的Ring的介紹,region的概念就非常好理解了。
為了建立一個global cluster,SwiftStack為swift增加了region的概念。Region擴充了Tier的層次,是一個比zone更大的地區,一組zones構成的tier是一個region。
一個全球的、支援副本的叢集可以通過將storage nodes部署在不同的region中進行建立。同一個region中的zones之間的延遲是相對較低的,Proxy nodes會對距離它近的region具有親和性(local affinity,優先訪問),並根據storage nodes所在的region採用“樂觀寫”的方式將資料寫入最近region的storage nodes。如果需要的話,用戶端可以通過選項執行一個跨地區(忽略local affinity)的讀/寫操作。
如下,為swift 1.8.0 中構造tiers結構的代碼(common.ring.utils.py)。
def tiers_for_dev(dev): """ Returns a tuple of tiers for a given device in ascending order by length. :returns: tuple of tiers """ t1 = dev['region'] t2 = dev['zone'] t3 = "{ip}:{port}".format(ip=dev.get('ip'), port=dev.get('port')) t4 = dev['id'] return ((t1,), (t1, t2), (t1, t2, t3), (t1, t2, t3, t4))
從以上代碼中,我們可以清晰的看到region被增加到了最頂層的tier,並作為裝置dev的一個新屬性,實現為dev字典的一個新key-value對。
預設情況下,swift叢集的region為1,從而保證cluster中一定存在一個region,並用“()”表明一個region,作為tier tree的root。以下代碼為tier tree的構建過程,代碼的doc string將包含region層次的tier tree結構描述的非常清楚,為了便於理解,所以我就一併貼上來了。
def build_tier_tree(devices): """ Construct the tier tree from the zone layout. The tier tree is a dictionary that maps tiers to their child tiers. A synthetic root node of () is generated so that there's one tree, not a forest. Example: region 1 -+---- zone 1 -+---- 192.168.101.1:6000 -+---- device id 0 | | | | | +---- device id 1 | | | | | +---- device id 2 | | | +---- 192.168.101.2:6000 -+---- device id 3 | | | +---- device id 4 | | | +---- device id 5 | +---- zone 2 -+---- 192.168.102.1:6000 -+---- device id 6 | | | +---- device id 7 | | | +---- device id 8 | +---- 192.168.102.2:6000 -+---- device id 9 | +---- device id 10 region 2 -+---- zone 1 -+---- 192.168.201.1:6000 -+---- device id 12 | | | +---- device id 13 | | | +---- device id 14 | +---- 192.168.201.2:6000 -+---- device id 15 | +---- device id 16 | +---- device id 17 The tier tree would look like: { (): [(1,), (2,)], (1,): [(1, 1), (1, 2)], (2,): [(2, 1)], (1, 1): [(1, 1, 192.168.101.1:6000), (1, 1, 192.168.101.2:6000)], (1, 2): [(1, 2, 192.168.102.1:6000), (1, 2, 192.168.102.2:6000)], (2, 1): [(2, 1, 192.168.201.1:6000), (2, 1, 192.168.201.2:6000)], (1, 1, 192.168.101.1:6000): [(1, 1, 192.168.101.1:6000, 0), (1, 1, 192.168.101.1:6000, 1), (1, 1, 192.168.101.1:6000, 2)], (1, 1, 192.168.101.2:6000): [(1, 1, 192.168.101.2:6000, 3), (1, 1, 192.168.101.2:6000, 4), (1, 1, 192.168.101.2:6000, 5)], (1, 2, 192.168.102.1:6000): [(1, 2, 192.168.102.1:6000, 6), (1, 2, 192.168.102.1:6000, 7), (1, 2, 192.168.102.1:6000, 8)], (1, 2, 192.168.102.2:6000): [(1, 2, 192.168.102.2:6000, 9), (1, 2, 192.168.102.2:6000, 10)], (2, 1, 192.168.201.1:6000): [(2, 1, 192.168.201.1:6000, 12), (2, 1, 192.168.201.1:6000, 13), (2, 1, 192.168.201.1:6000, 14)], (2, 1, 192.168.201.2:6000): [(2, 1, 192.168.201.2:6000, 15), (2, 1, 192.168.201.2:6000, 16), (2, 1, 192.168.201.2:6000, 17)], } :devices: device dicts from which to generate the tree :returns: tier tree """ tier2children = defaultdict(set) for dev in devices: for tier in tiers_for_dev(dev): if len(tier) > 1: tier2children[tier[0:-1]].add(tier) else: tier2children[()].add(tier) return tier2children
更多的關於在multi-regions下swift中各個操作的演算法細節,可以參見我在文章開頭處給出的兩個blogs連結,其中都有非常詳細、圖文並茂的介紹,我就不重複造輪子啦 =D