基于consistent hashing实现Web Caching系统

近段时间的工作涉及高效CDN、海量数据存储和并行计算方面的内容,研读了不少这方面的论文。我会抽空把觉得比较精彩的论文翻译出来,供交流和学习之用,欢迎指正。

 

基于 consistent hashing 实现 Web Caching 系统

David Karger, Alex Sherman , Andy Berkheimer, Bill Bogstad, Rizwan Dhanidina Ken Iwamoto, Brian Kim, Luke Matkins, Yoav Yerushalmi.

MIT 计算机科学实验室

邓辉

 

原文地址: http://www8.org/w8-papers/2a-webserver/caching/paper2.html

摘要:

       对于互联网来说,一项重要的性能度量标准是向用户提供内容的速度。随着网络流量的增加,用户将会面临越来越多的数据传送方面的延迟和失败。 Web caching 是目前所研究的提升互联网性能的主要策略之一。

 

       Caching 系统中有个重要的问题,那就是如何确定某个时刻所 cache 的内容的位置。已有的解决方案有:多播查询以及目录系统。

 

       在本文中,我们介绍了一种基于 consistent hashing 进行 web caching 的新策略。 Consistent hashing 提供了一种多播和目录系统的替代方案,并且具有负载均衡和容错方面优势。对于其性能的理论分析之前已经有人做过;本文将介绍一个基于 consistent hashing 系统的实现以及用于支持本文观点——其可以带来性能提升——的实验。

关键字:

Caching Hashing Consistent

1、 介绍

       随着互联网成为信息发布的主要媒体,就需要更为高效和可靠的内容传递手段。但是,目前的数据传递方法既会导致不可预测的延迟,同时又很容易失败。主要有两个原因:网络拥塞以及服务器过载。在拥塞的网络中,数据只能缓慢地进行传递,而过载的服务器(请求超出其能力)要么拒绝某些请求,要么变得非常缓慢。由于网络和服务器基础设施的扩展无法跟上互联网应用的巨大增长,因此网络拥塞和服务器过载会经常发生。

      

       网络和服务器的过载往往无法预测,也没有前兆。比如,如果晚上的一条新闻中提到某个网站是“当今最酷的网站”,那么它也许在第二天就得应对上万倍的访问量。因此,预先计划的好处有限,处理负载的最好方法就是适应变化的环境。

1.1   Web Caching

       Caching 技术已经被用来提高互联网上数据传送的效率和可靠性。即使原始的服务器过载或者通往它的网络路径拥塞,附近的 cache 也能够很快的提供一个(被 cached 的)页面。虽然这会让一个自私的用户产生不道德地使用 caches 的动机,但是值得注意的是, cache 的广泛使用会带来一个总体上的好处:如果请求被附近的 caches 截获,那么到源服务器的请求就会少一些,从而减少服务器和网络的负载,为所有用户带来了好处。

 

       用单个机器为一组用户提供共享的 caching 服务,是最容易想到的方法,但是它有几个缺点。如果 caching 机器故障了,那么所有用户都无法使用互联网。即使正常运行,单个 cache 所能服务的用户数也会受到限制,从而在用户密集访问时会成为瓶颈。另外,单个 cache 所能达到的命中率具有两个重要的限制。首先,受限于可用的存储容量,当它因为空间的不足被迫删除掉一些被重复请求的对象时,就会发生“ false misses ”。其次,受限于 cache 所能够服务的用户数量,无法出于 caching 的目的尽可能地把来自多个用户的请求进行聚合:一般来讲,所聚合的用户请求越多,命中率就会越好,因为其要请求的对象已经被其他用户请求过了。

1.2 相关工作

       不少组织【 2 7 5 4 】曾经提议采用 cooperating caches 系统来达成容错、可伸缩以及聚合更多数量请求(提高命中率)的目标。这些系统具有一些共同的属性。每个客户在系统中选择一个主 cache 。来自该客户的请求首先发送到它的主 cache 。如果主 cache 没有命中,该请求不会直接被发送到内容源那里,而是会试图从其他协同工作的 cache 那里定位所请求的资源。如果成功,那么就会从(更近一些的)协同工作的 cache 那里获取资源,从而避免了从内容服务器获取资源的低效情况。因此,其他协同工作的 cache 其实充当了“二级 cache ”的作用,减少了主 cache 没有命中的开销。

 

       准确地说,这些系统之间的区别在于:在主 cache 没有命中的情况下,如何去定位数据。有些系统使用多播【 7 】或者 UDP 广播【 2 】的方法把查询广播给所有其他的 cache 。这种做法除了因为广播查询消耗额外的带宽外,主 cache 还得等待所有协同工作的 cache 都报告没有命中后才能去联系内容服务器;这会在二级 cache 没有命中时导致性能的下降。还有些系统采用目录的方法(有集中式的【 5 】,也有定期广播以支持本地查询的【 4 】)。目录信息的查询和传送也会消耗带宽,并且集中式的目录会成为系统中新的故障点【 5 】。

 

       所有这些系统存在的另外一个问题是: caches 间的数据重复。可以向任意的 cache 查询任意的数据,这会导致 cache 存储一份该数据的拷贝。在二级命中时,网络带宽和时间被浪费在把数据拷贝到另外一个 cache 上面。更糟糕的是,这些拷贝会使得其他页面无法被请求,从而降低了 cache 命中的次数。如果协同工作的 caches 都彼此“相邻”,那么我们很可能会希望把每个对象只存储在一个或者几个机器上,形成一个更大一些的 cache (减少 false misses )【 9 】。

1.3 我们做的工作

       在本文中,我们提出了一种方法,可以在无需任何 cache 间通信的情况下,使得 caches 协同工作,形成一个一致的系统。我们所开发的分布式 web caching 系统叫做 Cache Resolver ,通过让客户自己决定哪个 cache 具有所要求的数据,避免了在没有命中时 cache 间的通信。

其中没有主 cache 的概念,也不用在主 cache 没有命中时从其他 cache 中定位所需要的资源,用户浏览器会直接联系应该包含所需资源的那个 cache 。浏览器采用一个 hash 函数来决定资源(或者 URL )和一组动态变化的可用 caches 之间的映射关系。

 

       和基于广播和目录的方法相比, hashing 方法具有一些优势。机器可以在本地准确地算出哪个 cache 应该包含一个指定的对象。一次单播就足以获取到该对象或者确定其没有被 cached ,从而减少了网络流量(和其他方法相比)。同时,和多播方法必须得等待所有 caches 响应相比,其发现没有命中的速度也更快一些。它还避免了基于目录方法所带来的维护和查询开销。它没有在系统中产生新的故障点——事实上,我们的方法具有一些重要的可靠性属性,我们会在后面讨论。

 

       上述优点足以推动我们去提出一个基于 hash 的方案,不过我们还是发现了另外一个优势:该方法把页面定位的任务推给了每个客户。基于目录定位的方法需要太多开销来把每个浏览器包含进来,而基于 hash 的方法却很容易在 browser 层面进行实现。因此,原先的方法都假设客户总是先去访问一个固定的主 cache ,在没有命中时,再去访问其他的 caches 来确定是否包含所请求的页。我们让浏览器直接决定去访问哪个 cache 。这样就无需一个中间 cache ,从而改善了响应时间。此外,对于一个给定的页面,所有的客户都会去访问同一个 cache ,因此,我们的 caching 系统中,无论协同工作的 caches 有多少,针对一个页面只会出现一次不命中,而不是针对每个 cache 都会出现不命中的情况。这就降低了不命中率。这个不命中率还会得到进一步的降低,因为我们避免了对页面的冗余拷贝,从而为其他页面在 cache 中留下了更多的空间和命中率。

 

       虽然基于 hash 的方法具有一些诱人的特性,但是要想正确地把它实现出来还需要考虑几个重要的问题。有一篇理论论文,其中提出了一个名为“ consistent hashing ”的工具可以解决其中的几个问题【 6 】。这是我们目前实现工作的基础。

 

       一个类似的关于 Cache Array Routing Protocol CARP )的提议出现在一份 Internet Draft 中【 3 】。 CARP 被用在微软的 Proxy Cache 中【 8 】。虽然 CARP 并没有在如【 6 】的文献中进行过理论论证,不过其和我们的方法具有很多直觉上的共同点。我们的方法和 CARP 之间的一个重要不同在于我们的 hash 函数的实现方式。当前的浏览器并不具备支持这种方法所需的全部能力。 CARP 把所有的 consistent hashing 职责全部放到浏览器中,这会导致一些问题,我们后面会谈到。我们对域名系统( DNS )的使用方式是不寻常的(但是是正确的),以支持在浏览器侧对于 hash 函数的使用。如果去修改浏览器,那么可以无需借助于 DNS 就能够在浏览器中实现完全的 consistent hashing 功能;但是,长远看来,借助于 DNS 的方法所表现出来的一些优点,使其成为一个正确的做法。

1.4 论文概述

       在本文的第 2 小节中,我们会对 consistent hashing 进行更为细致的描述。在第 3 小节中,会介绍使用 consistent hashing 实现的 web caching 系统。在第 4 小节中,对我们的系统和其他 web caching 系统进行了对比。在第 5 小节中,介绍了我们的 web caching 系统的其他一些优点,比如:容错和负载均衡。我们在第 6 小节中进行了总结。

2 Consistent Hashing

       我们的系统基于 consistent hashing ,一种在一篇以前的理论论文中提出的方法【 6 】。这里,我们将使用 consistent hashing 并介绍其简单实现。在概述其在理论上的正确性之后,我们会介绍一些实验,以证明其在现实中同样工作地很好。

2.1 需求

       我们系统的目标是要让任何客户可以在本地计算出 URL 和包含它的特定 cache 之间的映射关系。 Hashing 是完成该项任务的常见工具。例如,如果一组数目为 23 caches ,其编号为: 0 22 ,我们可以把 URL u hash cache h(u)=7u+4mod23 (可以把 URL u 看做是表示一个大数的位字符串)。我们一般会直观地认为 hash 函数会把其输入“随机地”分布在可能地位置上。这种随机分布在直觉上应该是均匀的,也就是说,任何一个 cache 所负责的请求页都不会出现不成比例的情况。 Hashing 的这种负载均衡的特性正是我们的应用所迫切需要的,因为那些承担不均衡负载的 cache 会成为整个系统的瓶颈。

 

       糟糕的是,标准的 hashing 在应用到 caching 系统时有一些问题。其中最明显的一个也许是: caching 机器会随时故障或者加入。试想,如果在刚刚描述的系统中增加一个 24 cache 会怎样呢。一个自然的变化是:我们得使用新的 hash 函数 h’(u)=7u+4mod24 。不幸地是,在这个改变下,几乎所有的 URL 都被重新映射到一个新的 cache 。这就等于把所有的 URL 都清除出 caching 系统:如果我们的系统在新的位置寻找一个 URL ,那么它被 cached 在老位置的事实将变得毫无用处——我们将面临一个命中失败。互联网中信息传播的异步特性将使得该问题进一步恶化。在任何一个时刻,不同的客户具有不同的 cache 加入和离开信息。我们把一个机器所知道的一组 caches 称为其视图,我们发现在任何时刻,系统中会存在许多不同的视图。这会带来两个潜在的问题。如果每个视图都导致一个 URL 映射到不同的 cache 中,那么很快就会发现每个 URL 会被存储在所有的 cache 中——而这正是我们想要避免的。此外,由于存在这么多的不同视图,就很难保证所有的 caches 均匀地承担负载——因为不同的视图会把更多的负载导向一个 cache ,即使每个视图单独看来似乎是负载均衡的。

 

       因此,对于我们的 hash 函数来说,能够一致地对对象进行映射是至关重要的:即使系统中存在多个变化的视图,每个对象也应该被映射到少量的机器上,这样就可以保证所有机器能够大致均匀地分担负载。

2.2 Consistent hashing

       Consistent hash 函数【 6 】的一个简单实现就可以满足上小节中描述的需求。选择某个标准的基准 hash 函数,把字符串映射到数字区间 [0 M] 。然后除以 M ,可以把它看做是一个映射到 [0 1] 区间的 hash 函数,也可以把它看做是单位圆 [1] 。这样,每个 URL 被映射到单位圆上的一个点,同时把系统中的每个 cache 也映射到单位元上的一个点。现在,把每个 URL 分配给从该 URL 的映射点顺时针出发所遇到的第一个 cache 点所对应的 cache 。图 1 展示了一个示例。

 

1 (i)URLs caches 都被标准 hash 函数映射成圆上的点。 URL 被分配给沿圆周顺时针方向最近的 cache 。对象 1 2 3 被分配给 cache A 。对象 4 5 被分配给 cache  B (ii) 当增加一个新 cache 时,只有那些沿圆周顺时针方向最接近新 cache URLs 需重新分配。在本例中,当增加新 cache 时,只有对象 1 2 需要移动到新 cache C 上。无需在原来的 caches 间移动对象。

/includegraphics[width=4.5in]{chash}

 

       Consistent hashing 很容易实现。可以把所有“ cache 点”存放在一个二叉树中,只需在二叉树中进行一次搜索,就可以找到一个 URL 点的顺时针后继。对于具有 n caches 的系统来说,这种方法可以达到 O(logn) 时间复杂度。另外一种实现方法【 6 】是把圆分割成等长的区间,并根据区间来把 cache 点分成“组”,这样无论 caches 数目是多少,都可以把查询的时间复杂度提高到常数级。

 

       值得注意的是, CARP 3 8 】中的方法的时间复杂度和 caches 的数目是线性相关的,因此难以伸展到具有更多 caches 的系统。

 

       出于技术的原因(【 6 】中有详述),把每个 cache 点做多份拷贝是非常重要的——也就是说,把每个 cache 的多份拷贝映射成单位圆上不同的“随机”点。这会产生 URLs caches 的更加均衡的分布。

2.3 分析

       现在,我们来解释一下刚刚介绍的 consistent hashing 方法为何具有所期望的特性。在此,我们只是对规范论证【 6 】做一个直观的总结。

 

       我们的根据来自于基础 hash 函数会把 URLs cache 点都“随机地”映射到单位圆上这个直观认识上。考虑一下,如果我们向系统中增加一个新的 cache c 会发生什么呢。该 cache 会被映射到单位圆上,并且会从其他 caches “窃取”某些 URLs 。哪些 URLs 会被窃取呢?是那些圆周上临近 c 的。但是根据我们的直觉, URLs 在圆上是随机分布的。因此,很少一部分会和 c 临近,这就意味着很少一部分会被窃取。由于那些没有被新 cache 窃取的 URLs 是不会移动的,我们得到第一个所期望的特性:当增加一个新 cache 时,只有少数 URLs 会变动。因此,在发生变化的系统中,大多数被 cached 的对象会保持命中。

 

       同样的论断可以应用于多视图的问题。一个 cache 仅当其是某个视图中距离一个对象最近的 cache 时,才会负责存储该对象。不过,如果该 cache 和该对象距离很远,那么(基于 caches 随机分布的直观认识)就很可能在每个视图中,都会有另外一个 cache 距离该对象更近一些。从而就阻止了该 cache 去存储该对象。因此,只有那些和一个对象临近的 caches 才会去存储该对象。但是, caches 随机分布的直观认识却告诉我们:对于任何一个对象来说,最终只会有少数 caches 与其临近。换句话说,即使系统中有大量 caches ,对于任何一个对象来说,也只会有少数 caches 对其负责。

 

       这些直观认识可以用下面【 6 】中的定理进行形式化。该定理中采用了一个良好的基础 hash 函数(用来把 URLs caches 映射到单位圆上)。【 6 】中告诉我们,根据某些特定原则构造出来的随机通用 hash 函数是良好的。在实际中,具有良好混合特性的标准 hash 函数(比如 MD5 )基本上就足够好了。

 

定理 2.1 (【 6 】) 一个具有 m caching 机器和 c 个客户的系统,其中每个客户都具有包含任意半数 caching 机器的视图。如果对每个 caching 机器都进行 Ω (logm) 份拷贝,并且拷贝和 URLs 都是用一个良好的基础 hash 函数映射到单位圆上,那么如下特性成立:

平衡:

         在任何一个视图中, URLs 在该视图中是均衡分布在 caching 机器上的。

负载:

    对所有的视图来说,任何机器所存储的 URLs 数目都不会超过平均数目的 O(logc) 倍。

分布:

         任何一个 URL 都不会存储在超过 O(logc) 数量的 caches 中。

 

       因此,对于动态变化和不确定的互联网域来说, Consistent hashing 是一种好的 hashing 方法。

2.4 实现结果分析

       我们列出了一些简洁的统计数据以说明 consistent hashing 的有效性。首先, consistent hashing 是一个相对高效的操作。在我们的测试环境中,我们建立了一个 cache 视图,其中使用了 100 caches ,对每个 cache ,都在单位圆上创建了 1000 份拷贝。我们在 Pentium II 266MHz 机器上对 consistent hashing 的动态步骤进行了测试。(动态步骤包括: URL 字符串求值、基础 hash 函数求值以及遍历二叉树)。平均来说, hash 函数的单次调用耗时 20 微秒。这相当于在 10M 以太网上从本地 cache 传输一个 20KB 的文件所耗总时的 0.1% 。如果用桶数组( bucket array )而不是我们实现中采用的标准二叉树来表示底层的 cache 视图,那么 20 微秒这个值还会被极大地减少。

 

       同时,我们也对 consistent hashing caches 间均衡方面进行一些度量(其低负载特性保证)。我们使用了从 theory.lcs.mit.edu 服务器上获取到的一周的日志数据。期间,共发生了 26,804 次不同的 URL 请求。我们使用我们的 hash 函数针对不同数目的 caches 处理这些不同的 URLs ,以观察这些文件在 caches 间分布的良好程度。

 

caches

Avg entries in cache

Std Dev

Std Dev as % of mean

3

8934

246

2.7

5

5360

173

3.2

8

3350

112

3.4

10

2680

68

2.6

 

       上述数据表明关于对象数目的标准偏差非常的低:平均大约 3% 。当数据集变大时,会得到更好的结果。

 

       在第二项试验中,我们在多视图环境中把 1500 个名字映射到 caches ,其中涉及 80 台机器,每个视图中都会有 5 台机器不定期地加入或者离开。(对象, cache )对的总数上升至 1877 ,仅比基数多了 25%

3 我们的系统

       如上所述,我们想要实现的系统看起来很简单:把一些标准的、互不交互的 web caches 和浏览器中的一些 hashing 逻辑组合在一起就行了。但是当我们开始实现系统时,很快就发现当前的浏览器的灵活性无法独立支撑 consistent hashing 。因此,为了所构建的系统能和浏览器兼容,我们在域名系统( DNS )上做了大量工作以支持 consistent hashing 。我们的 web caching 系统,也就是 Cache Resolver ,由三个主要部件组成:用于存储内容的 cache 机器,把请求导向虚拟 caches 的用户浏览器以及使用 consistent hashing 把虚拟 caches 解析到具体 cache 机器物理地址的域名服务器(也称为解析单元)

3.1 Caches

       我们安装了 Squid proxy cache 包的一个发布版本。当 Proxy cache 存储了所请求数据的有效拷贝时,就会把该数据回应给请求者。否则,它就从原始 web 服务器获取数据,并保存一份拷贝。 Squid 所使用的是 LRU 置换策略。出于负载均衡和容错方面的考虑(我们后面会讨论),我们在 cache 所在的机器上安装了另外的软件来监控 squid 进程。当系统中的其他部分对其进行查询时,该软件会返回 cache 的状态(故障或者正常)以及负载信息。当 squid 正常工作时,负载信息主要是 cache 在最近 30 秒的服务时间段内处理 web 请求的字节传输速率。

3.2 Caches 的映射

       为了在浏览器中实现 consistent hashing ,我们首先希望能够利用最广泛使用的浏览器( Netscape 2.0,IE3.01 或者它们的更高版本)中的“ autoconfiguration function ”特性。用户可以指定一个用 JavaScript 编写的函数,该函数会在每次请求时被调用,并会基于所请求的 URL 字符串选择一组要访问的 proxy caches 列表。浏览器会依次按照列表中的顺序去访问 proxy caches ,直到得到了数据响应。

 

       遗憾的是, autoconfiguration 具有太多的限制,从而无法支持 consistent hashing 。其最根本的问题为, autoconfiguration 脚本只能被手工下载一次。当 proxy caches 改变映射函数时,就会出现错误。

      

       为了绕过这个问题,我们决定利用 DNS 。我们可以架起自己的 DNS 服务器,对它们进行修改以支持 consistent hashing 。这种 consistent hashing 的方式可以在名字解析时“传播”给浏览器。更准确地说,我们编写了一个 autoconfigure 脚本,其执行一个标准 hash 算法把输入的 URL 映射到 1000 个我们称之为虚拟 caches 的名字区间内。接着 DNS 使用 consistent hashing 把这 1000 个虚拟 cache 名字映射到 cache 的实际 IP 地址。

 

       在测试中,我们发现某些操作系统上的某些版本的浏览器即使在其所包含的虚拟名字过期时,也不去重新调用 DNS 。那么当由该虚拟名字所标示的 cache 故障时,浏览器将无法加载页面。为了解决该问题,我们让脚本返回一个名字列表(我们的实现中是 5 个名字)而不是只有一个。这样,这些出现问题的浏览器就具有更多的机会去得到一个物理名字可用的解析。该列表中最后一项为“ DIRECT ”,其含义为如果其他名字都不可用就直接联系内容服务器。

3.3 DNS 服务器

       在我们的系统中, DNS 的主要工作是把由用户 JavaScript 函数产生的虚拟名字解析成 caches 的实际物理 IP 地址。我们使用了多个 DNS 服务器,其上都运行着没有经过任何修改的 BIND 8.0 (一个实现了 DNS 协议的软件包)。 BIND 会从记录文件(该文件会被另外一个称为 dnshelper 的程序动态更新)中读取虚拟名字和 IP 地址间的映射关系, Dnshelper caches 组进行监控,并使用 consistent hashing 1000 个虚拟名字(参见 3.2 小节)全部映射到目前处于正常工作状态的 cache 机器。如果可用的 caches 发生了变化,那么 dnshelper 会通知 BIND 重新加载包含新的映射关系的记录文件。

 

       虚拟 caches 的数量众多,这意味着每个虚拟 cache 只负责整个系统负载中的一小部分。 Consistent hashing 保证了分配给每个 cache 的虚拟名字的数量将会在这些 caches 间均匀地分布。这两个事实确保了页面请求的负载在 caches 间是均衡分布的。我们会在 5.2 小节中对基于 consistent hashing 的更为高级的负载均衡策略进行介绍。

3.4 DNS 讨论

       我们对于 DNS 的使用方法在某种程度上违反了原先把 cache 定位功能放在浏览器中的计划。但是,下面的原因足以支持我们的做法。 DNS 是一个用于定位对象的标准工具,我们就是使用了它的这种能力,而不是花费精力去实现一套自己的协议。对于任何一次页面请求,一般来说,没有 caching 的浏览器都会去执行 DNS 解析去找到该页面所在的服务器。我们只是把这个解析替换成去找到一个 cache DSN 解析一般都会由附近的 DSN 解析器执行,这样就不会给页面请求增加大的延迟。我们的系统正当地使用了 DNS 的功能, DNS 的任何故障都会使得用户浏览行为终止,因此,并没有给系统中增加故障点。

 

       第二个理由是,针对每个请求的查询不是必需的,只要我们的系统具有这个能力。 DNS 只是用来创建从 1000 个名字的集合到 IP 地址的映射。只需对浏览器做少许更改,这个映射就可以容易地存储在浏览器中,事实上,目前的浏览器已经可以保存大约 10 个左右的 DNS 条目;我们所需要做的只是把它变成 1000 。由于 consistent hashing 本身的特性,这些条目甚至无需是最新的。因此,浏览器在更新其映射关系时可以非常的“懒惰”,在某个 cache 没有响应时将其去除,当和某个 caceh 建立连接了连接关系时再懒式地下载最新的可用 caches 列表。

 

       虽然我们可以不使用 DNS ,但是目前我们看不到任何这样做的理由。在我们的实验中, DNS 解析对于系统的性能没有任何大的影响。

4 测试

       正是因为相信我们所设计的系统比我们所研究的现有 web caching 系统具有更好的性能,我们才实现自己的 web caching 系统。在本文中,我们比较了 Cache Resolver 和其他两个功能类似的系统: Harvest 系统和 1.2 小节中介绍过的 CRISP 系统。我们将展示测试结果以证明我们的设想。

4.1 测试准备

       为了测试我们的系统,我们搭建了一个由 7 台机器组成的网络,通过一个 100Mb 交换机相连。在其中 3 台机器上运行着 Squid proxy cache 程序)。另有一台机器用来充当 web server ,为了让 web 服务器的数据传输变得缓慢,我们把其放置在一条 10Mb 的链路上。还有一台机器运行着 BIND 程序,用来充当域名服务器,其使用 Consistent Hashing 进行 1000 个虚拟名字空间到 3 proxy caches 间的名字解析。第 6 台机器上运行着测试驱动程序。还有最后一台机器,在针对 CRISP 进行测试时,其可以用作目录服务器,对 Harvest 系统测试时,其可以充当一个父 cache

4.2 测试驱动程序

       我们使用 Surge 1 】作为测试驱动程序,它是由 Boston 大学开发的一个 web 流量生成工具。 Suger 的设计者们对 web 流量进行了研究,并开发出了用于模拟对象请求数量和频度准确分布的生成器。在测试之前, Surge 会生成一个文件数据库,需要拷贝到 web 服务器上。我们生成了一个具有 1500 个大小不等文件的数据库,共 34MB 。在测试期间, Surge 会产生一系列经过仔细检查的请求,每个文件都会被请求多次。客户的数目(其中每个都包含特定数目的线程)作为 Surge 的参数给出。 Surge 会模拟出这些客户,这些客户都被 Surge 所创建的请求调度器所调度。每个请求都代表一个具有许多内嵌 web 文件的对象,这些文件会在多个对象中重叠,并且不同的文件被请求的次数可能并不相同。文献【 1 】中对 Surge 模拟 web 服务器流量所使用的数学分布模型进行了详细的介绍。我们对 Surge 进行了修改,以使其可以运行在两种独立的模式中:公共模式和 Cache Resolver 模式。在公共模式中,会设置一个公共的 cache ,一组用户总是使用一个本地 cache ,当命中失败时,该 cache 要么会从另外的 cache 中获取数据,要么从主内容服务器中获取数据。在公共模式中,我们把 Surge 配置成 3 个客户,每个客户都和自己的 proxy cache 通信。( Surge 中的客户有多个线程组成,以表示众多的用户)。 Cache Resolver 模式是用来测试我们的系统的。在该模式中,这 3 个客户中的每一个都执行一个简单的 hash 函数(和用户使用的 autoconfiguration 函数类似)。该函数以 URL 请求作为输入,返回一组虚拟名字。 Surge 客户接着通过我们的 DNS 来把名字解析成 IP 地址。

4.3 结果

       我们在多种不同 proxy cache 配置的情况下运行了测试,以对 Cache Resolver 模式和公共模式进行比较。然后,我们对 proxy cache 日志进行了分析以比较针对不同测试的不命中率。我们期望 Cache Resolver 模式的不命中率会更低一些,有两个原因。首先,在 Cache Resolver 模式中,数据被分配到指定的 caches 。同时,这些测试中,我们 cache 的容量配置是不同的(从 9MB 36MB )。因为数据库的总大小为 34MB ,所以容量小的 cache 的不命中率会高一些(因为 LRU 会把数据强迫清除出 cache )。在每次测试前,都会先把 cache 中的数据清空。对于每个测试来说,这三个 caches 的容量大小是一样的。针对不同的测试,容量的值分别为: 9 12 18 24 30 36MB 。在第一组测试中,我们把 proxy caches 配置成之间无法通信的。它们在命中失败时直接去原始服务器获取数据。在这个配置下,公共模式的不命中率要高于 Cache Resolver 模式(如图 2 所示)。 Cache 的容量越小,其间的差别就越大,因为公共模式中的数据冗余会带来更坏的影响。

     

2: 公共模式 vs Cache Resolver 模式。 X 轴表示测试中使用的所有 cache 的大小( MB ), Y 轴表示 cache 的不命中率(上面)以及以毫米为单位的平均反映时间(下面)。

/includegraphics[width=2.75in]{misses2} /includegraphics[width=2.75in]{times1}

 

       在我们的环境中,我们也对其他三种常见的 cache 系统配置进行了测试。我们测试了 Sibling 配置,其中这 3 caches 被配置兄弟关系,在命中失败时,使用多播协议从其他 caches 中请求数据。我们测试了 Hierarchy 配置,我们增加了另外一个 cache 来充当兄弟 caches 的父 cache (类似 Harvest 方法)。最后,我们还测试了 CRISP 配置,其中设置了一个中心目录服务器,当命中失败时, caches 会向它发起查询。对所有这些配置,我们都度量了其主 cache 不命中率(用户查询的第一个 cache 的不命中率)以及系统不命中率(当所有 cache 都没有所请求的数据时,整个 cache 系统的不命中率)。图 3 中展示了这三种配置下的主 cache 不命中率(和图 2 中关于公共 cache 模式的不命中率的展示类似),其系统不命中率则和 Cache Resolver 模式的一样良好。对于这三种配置来说,在主 cache 的不命中时,会导致额外的开销,也就是说,需要额外的 cache 间通信以确定出哪个具有所请求的数据,并且还有额外的 cache 间数据传输。此外,每次 cache 间数据传输都会导致数据冗余,从而减少了 cache 的磁盘空间以及更为重要的用于快速服务 RAM 空间。如果由用户来通过 hash 函数来决定该去访问哪个 cache ,那么就可以避免额外的通信开销。

   

3: 另外 3 种配置下的不命中率和反应时间

/includegraphics[width=2.7in]{plotAll} /includegraphics[width=2.7in]{times2}

5 扩展

       在本小节中,我们将探讨对基本系统的扩展,以提供就近服务、负载均衡以及容错等功能。

5.1 就近服务

       无论采用何种 cache 方法, cache 服务器的远近都会极大地影响对用户的响应时间。我们的系统可以保证总是由本地的 caches 为用户提供服务。我们把 caches 分布在多个地理区域中,用户将只被和其属于同一个区域的 caches 服务。我们把判断用户地理区域的信息放到用户浏览器的 JavaScript 函数中。 JavaScript 函数是可定制的:当用户下载它时,会为其提供一个选择区域的机会。 JavaScript 函数生成的虚拟名字具有如下格式: A456.ProxyCache3.com ,其中‘ 456 URL hash 结果,‘ 3 表示一个可变的地理区域。

 

       我们把 DNS 系统分成两个层次。顶层 DNS 服务器负责解析名字中的地理信息部分,接着把用户的 DNS 解析请求导向一组和用户地理区域对应的底层 DNS 服务器。底层 DNS 服务器被放置在和 caches 同样的特定地理区域中,它们仅负责这些 caches 的虚拟名字和 IP 地址间的解析。

 

       当解析诸如 A456.ProxyCache3.com 之类的名字时,用户的 DNS 解析请求会首先被导向一个能够解析名字中第 2 部分:“ proxyCache3 的顶层 DNS ,然后该 DNS 把请求导向一组底层 DNS 服务器来解析名字中的第 1 部分:“ A456 底层 DNS 服务器仅负责和其处于同一地理区域的本地 cache 服务器的解析,这些 cache 服务器和用户也处于同一地理区域。

5.2 高级负载均衡——热点页面

       在第 2 小节中,我们曾经提到,我们的系统具有负载均衡特性,因为对局部区域中的每个服务器,都为其分配了相同数目的虚拟名字。当大部分对象具有相同的请求频率时,这种做法确实能够达成大致的负载均衡。不过,互联网上有些页面非常的受欢迎,有些却不怎么受欢迎。比如:像 CNN 主页这样受欢迎的页面被请求的频度要远高于大多数其他页面。这种“热点”对象会对负责存储其的 cache 服务器造成很高的负载压力。这些服务器很容易被请求淹没,要么故障,要么响应缓慢。

 

       为了应对这种情况,理想的情况是,我们知道哪个资源是热点,并让更多的 caches 来对热点资源进行服务。这个决策应该由负责虚拟名字和 IP 地址映射关系的底层 DNS 服务器做出。解决方案为,把热点虚拟名字映射为一组 IP 地址,而不是一个。在缺省情况下, BIND DNS 会在该列表上进行轮询,因此当向 DNS 询问该虚拟名字时,只有一小部分用户会得到同一个 IP 地址。因此,热点虚拟名字所产生的负载就被分散在由 BIND DNS 所返回的一组 IP 地址上。

 

       糟糕的是,知道哪个虚拟名字是热点,哪个不是并不是一件容易的事情。不过,从对每个 cache 的负载度量中,我们可以得到一些提示。因为 DNS 服务器知道从虚拟名字到 IP 地址的映射,所以我们可以确定在一个特定 cache 中,哪一组虚拟名字所承担的负载高一些。这组中将包含一个或者多个热点虚拟名字。我们不希望看到服务器被淹没,因此采用了一个激进的方法:把映射到一个热点服务器的所有虚拟名字分散到该区域的所有 caches 中。(这意味着我们把该组中的每个虚拟名字都映射到该区域中全部 cache IP 地址列表,这样由该虚拟名字所导致的负载就被分散到整个区域)。接下来,我们通过每次减少一个 IP 地址的方法,慢慢地缩小这个映射的范围。如果在某个时刻,发现缩减后导致服务器上的负载重新增加,那么就进行回退,并从另外一个不同的名字子集进行缩减。这样,很快就会达到虚拟名字和不同大小 caches 列表间的平衡。

5.3 容错

       除了 cache 的高效管理和负载均衡外,我们的系统还显示出非常高级的容错特性。首先,由于用户所看到的是虚拟名字而不是物理 IP 地址,因此就避免了个别 caches 故障导致的用户请求失败。在试图连接这些 cache 机器时,浏览器无需启动定时器,因为虚拟名字会被解析成可选的 IP 地址。此外,我们的系统不会出现类似 CRISP 的中心目录那样的单点故障情况。如果中心目录不可用, CRISP 系统就会崩溃, cache 机器就开始各自为政。在我们的系统中,不存在有任何仅仅负责某些关键任务(像 CRISP 中的中心目录,或者为一组用户提供入口服务的主 cache )的部件。在 Cache Resolver 中,只要有某个 DNS 机器工作正常,虚拟名字就会得到解析,并且只要区域中的有 caches 工作正常,虚拟名字就会被解析成 IP 地址。

6 结论

       在第 4 小节中,我们对 Cache Resolver 和其他 cache 系统进行了比较。结果表明,当由用户来负责决定哪个 cache 具有所需要的数据时,就可以避免用户请求循环中的主要开销。使用 hashing 函数可以为用户提供这方面的决策信息。由于像互联网这样的大规模网络的用户很可能会具有不一致的活动 cache 视图,因此我们采用了 Consistent Hash 函数,无论用户的视图多么的不一致,这种方法都可以做到很好的数据均衡。此外,为了证明使用 Consistent Hash 函数的方法在像互联网这样的系统中也能取得不错的实际效果,我们还对系统的实现进行了描述。我们的系统具有其他一些 web caching 系统所没有的就近服务、负载均衡以及高级容错特性。总之,我们相信通过高效的使用 caches consistent thashing 可以极大地提升互联网的效率。

7 致谢

       我们感谢 Frans Kaashoek 的帮助和建议。

8 参考文献

  [1] Paul Barford and Mark Crovella. Generating Representative Web Workloads for Network and Server Performance Evaluation. In Sigmetrics , 1997.

  [2] Anawat Chankhunthod, Peter Danzig, Chuck Neerdaels, Michael Schwartz and Kurt Worrell. A Hierarchical Internet Object Cache. In USENIX , 1996.

  [3] J. Cohen, N. Phadnis, V. Valloppillil and K. W. Ross. Cache Array Routing Protocol v 1.0. http://www.ietf.org/internet-drafts/draft-vinod-carp-v1-03.txt , September 1997.

  [4] L. Fan, P. Cao, J. Almeida and A. Z. Broder. Summary Cache: a Scalable Wide-Area Web-cache Sharing Protocol. Technical Report 1361, Computer Science Department, University of Wisconsin, February, 1998.

  [5] Syam A. Gadde, Jeff Chase and Michael Rabinovich. A Taste of Crispy Squid. In Workshop on Internet Server Performance , June 1998. http://www.cs.duke.edu/ari/cisi/crisp/.

  [6] David Karger, Eric Lehman, Tom Leighton, Matthew Levine, Daniel Lewin and Rina Panigrahy. Consistent hashing and random trees: Distributed cachine protocols for relieving hot spots on the World Wide Web. In Proceedings of the Twenty-Ninth Annual ACM Symposium on Theory of Computing, pages 654-663 , 1997.

  [7] Radhika Malpani, Jacob Lorch and David Berger. Making World Wide Web Caching Servers Cooperate. In Fourth International World Wide Web Conference, pages 107-110 , 1995.

  [8] Microsoft Proxy Server. White paper. http://www.microsoft.com/proxy/guide/CarpWP.asp

  [9] Ph.Yu and E. A. MaxNair. Performance Study of a Collaborative Method for Hierarchical Caching in Proxy Servers. Technical Report RC 21026, IBM T. J. Watsin Research Center, 1997.


[1] 单位圆:为了方便起见,我们把具有单位周长的圆称为单位圆。一般来讲,单位圆是指具有单位半径的圆。

 

你可能感兴趣的:(基于consistent hashing实现Web Caching系统)