Swift 1.8.0(Grizzly) 中region的理解 -> A Globally Distributed Cluster

在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等工作流程进行了详细描述。(这篇需要FQ-。-)

 

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

 

posted @ 2013-05-20 16:09  YUKI小糖  阅读(1069)  评论(0编辑  收藏  举报