一种精确的基于DHT的p2p网络搜索算法与网络拓扑模型
关键字:p2p,搜索,DHT,网络拓扑
提要:本文对通行的DHT算法进行了修改,使之可以进行精确的查找,如关键字之间的逻辑运算等等,并在此基础上提出一个基于中央地址服务器的星型P2P拓扑结构模型。
版权声明:本文由SkyMountain发表,引用地址为http://blog.csdn.net/skymountain/。作者保留一切版权,转载务必注明出处,否则视作侵权。
一、目前的p2p搜索算法研究状况及其缺点
目前对于p2p系统的自动搜索算法研究已经成为p2p研究的一个热点。最初的p2p系统,如Napster,采用的是中心服务器式的搜索算法,在这样的系统中,中央搜索服务器很容易就成为系统性能的瓶颈和不稳定的根源。其后的一些系统,如Gnutella,采用的是分布式的搜索算法,查找按照简单洪泛(flooding)的方式进行。查找命令首先传播到用户的所有相邻结点,然后再传播到相邻节点的相邻节点,直到达到预先确定的层次为止。这样的查找算法无疑会造成巨大的带宽和资源浪费。
现在的分布式p2p系统普遍采取的是DHT(Distributed Hash Table,分布式哈希表)搜索方法。该方法的思想是:每一份资源都由一组关键字进行标识。系统对其中的每一个关键字进行hash,根据hash的结果决定此关键字对应的那条信息由哪个用户负责储存。用户搜索的时候,用同样的算法计算出每个关键字的hash,再根据hash知道该关键字对应信息的储存位置,从而能够迅速定位资源的位置。下面举例说明。
例如某份文件名为“雪狼湖.mp3“。当用户共享此文件时,客户端把“雪狼湖.mp3“这个字符串进行分词,结果可能是"雪","狼","湖","mp3"(其中点号已经被自动去掉)四个词。我们假设上面几个词的hash结果分别为15,26,37,48。DHT一般规定系统中第一个id号比hash值大的用户(记作newid=success(hash),下同),负责保存这些关键字所对应的信息。假设此时系统中有id号分别为1, 9, 11, 19, 21, 29, 31, 39, 41几个用户,那么其中id=19的用户应当储存“雪”字对应的信息(可能包括文件hash,用户id等等)。同样id=29的用户储存“狼”字对应的信息,如此类推。
用户在搜索关键字“雪狼湖”的时候,客户端用同样的分词算法分得"雪","狼","湖"三词,并用同样的方式确定了这三个词分别由id=19, id=29, id=39的用户保存,于是客户端就分别向这三个用户发出查询请求。这三个用户分别将每个关键字对应的文件信息返回,客户端再将其中不包括所有关键字的信息过滤掉,最后将结果文件列表上报给用户,搜索完成。
这种DHT算法有多个变体,如Chord,CAN,Pastry,Tapestry等等,这几个系统的区别主要是路由算法的不同,在资源定位方法上并无不同。
我们先讨论资源定位的方法,第三章再讨论网络拓扑模型与路由算法。
一般认为,用这种DHT算法,用户基本上都可以在一次跳转之内得到查找结果,所以非常方便。每个用户要储存的数据=平均文件数*平均每文件关键字数,与系统规模无关,所以采取这种方案的系统可以支撑起非常多的用户。
但是用户加入和退出时,相关的用户要进行处理,这方面的花销比较大,所以当用户频繁加入和退出时,有可能导致系统性能的下降。这方面的处理算法,可以参考其它文献,这里仅指出一点:通常,用户上线下线对系统的影响是按O(logN)的方式随用户数增大而递增的。
这种算法最大的问题在于:无法精确查找,往往会浪费很多带宽。以上面的搜索“雪狼湖”为例,系统中所有关于“雪”,“狼”,“湖”的文件信息都会返回给搜索者,搜索者需要过滤掉不包含所有关键词的条目,才能得到比较精确的结果。我们知道,查找单独一个关键字的结果往往是非常多的,所有包含“雪”字的文件中,可能只有不到1%的文件含有完整的“雪狼湖”一词。因此这种算法中,99.99%的搜索都是无用功,带宽和CPU资源的浪费非常大。
二、改进的DHT算法
改进的DHT算法中,每个用户储存的信息不是文件的信息,而是其它用户的信息。
前面所说的DHT算法中,一般每个关键字所指向的值都是文件的hash(可能还有用户id,扩展资料等信息)。实际上,我们只要将关键字对应的值改为用户id和地址即可,不需要保存文件的信息。
搜索者在搜索一组关键字的时候(这组关键字之间可以有并、或、减等通用的逻辑运算符),用同样的算法可以定位关键字信息的保存位置(即用户,可称为关键字保存者),然后向这个位置查询,得到一组与查询的关键字联系的用户列表。搜索者再对各个关键字对应的用户列表的并、交、相减,最后得到可能拥有所需信息的用户(以下简称目标用户)列表。
客户端得到目标用户列表后,再逐个向这些用户发送查询请求。目标用户进行精确搜索后将结果返回,搜索完成。
这样,搜索被划分为两步进行:首先根据关键字组得到目标用户列表,再对目标用户逐个进行精确查找。这种搜索的速度比通常的DHT要稍慢一些,但是对网络资源的节约是巨大的:
A)带宽几乎没有任何浪费。用户搜索时,第一步搜索得到的是每个关键字对应的一个id列表,数据量很少(如果每个用户的信息是id+ip+port,那么才十个字节,一万个用户的信息才 0.1M 字节);经剔除不可能拥有目标信息的用户之后,该列表通常都会比较小。向该列表中的每一个用户发送搜索请求,如果该用户搜索没有发现可用资料,仅需返回一个空结果,这样导致的数据浪费也非常小。
B)储存表的节省。前面说过,平均每个用户要储存的数据 = 平均用户共享文件数 * 平均每文件关键字数。这个数字一般不大,但有的时候会很大,特别是热门关键字。现在这个公式变小了很多:平均每个用户要储存的数据 = 平均每个用户有关联的关键字数。 例如,如果一个用户共享了两个文件:雪狼湖-望月.mp3 和 雪狼湖-葬月.mp3,那么系统原来需要储存6+6=12条纪录,如今仅需要保存7条纪录即可。在文件数比较多的时候,这个节省是巨大的。汉语的词条总数仅6万多个,这意味着即使用户共享十万个文件,它对应的关键字数字也不会有太大的变化。
这种算法的一个更大的优势是它系统结构的巨大改进:由每个用户直接控制对它自己的内容的搜索。用户不仅因此可以提供精确的搜索结果,在将来还有可能可以推出一些个性化的服务。例如,用户仅仅将自己感兴趣、愿意提供搜索服务的关键字发布出去,自定义其它用户对这些关键字的搜索结果等等,都成为可能。
这种方式的另一个优点就是,搜索到的文件基本上都可以下载,因为只有能连通的用户才能取到搜索结果。
这种方法可以说是简单洪泛(flooding)搜索方式与DHT搜索方式的结合,它避免了简单洪泛方式带来的巨大带宽浪费,也避免了DHT算法带来的搜索不精确、带宽浪费等等缺点。
三、关于路由算法的讨论
事实上几乎所有的DHT模型都是一并将搜索和路由混合在一起讨论并实现的,搜索过程也是资源定位的的过程。因此在这一章我们也来讨论路由的问题。
路由问题其实不会太复杂,只要我们愿意将我们的对等网络模型改造成星型结构,由一个中央地址服务器记录每个用户的地址,那么路由定位的问题几乎可以不必考虑。中央地址服务器不必考虑搜索问题,仅仅是记录每个用户的地址并提供查询(对于需要考虑内网穿透情况的系统,还需要提供STUN服务),那么几乎只要一台普通的pc就能承担起百万级的同时在线用户量,所以它也不可能成为系统的瓶颈;何况我们还能很轻易地将一台中央地址服务器改为多台地址服务器联合提供服务?
值得一提的是,中央地址服务器的一部分工作可以交给关键字保存者完成。用户每次开始搜索时,需要向地址服务器查表一次,以便与关键字保存者联系,但是在执行搜索的第二步时,目标用户的地址也在搜索中一并返回了,搜索者可以根据这个地址直接与目标用户联系。
只是这种方式有可能导致关键字保存者过于繁忙。因为一些比较热门的关键字是对应着非常多的用户的。特别是考虑到局域网穿透时,关键字保存者的工作量就更大了。
四、用户上线和下线对系统的影响
这一章的讨论使用的是第三章中描述的星型p2p模型,也就是存在一个中央地址服务器,让任意两个客户端都能在短时间之内建立连接。下面我们来讨论用户频繁的上下线对系统的影响。
用户上线的时候需要执行两个动作:作为关键字保存者,它应接手保存由自己负责的关键字;作为一个对等点,它应当发布自己关键字。
如何接手接手保存由自己负责的关键字?
它需要接手的关键字都保存在它的下家那里,也即第一个id比它大的用户。用户首先向服务器查询success(id),得到自己的下家号码,然后与下家连接,并将下家手上应由自己负责的关键字迁移到本地来。
发布自己的关键字更加简单,对自己的所有关键字进行success(hash),确定该关键字的保存用户(关键字保存者),然后将关键字信息提交该(关键字保存者)保存即可。
用户下线的成本呢?用户下线,要确保两点:自己应属的搜索任务应当能妥善转移;自己的关键字信息应当从系统中“擦去”。
如果用户是忽然掉线的,那么第一个任务恐怕就非常困难了。所以我们必须要事先做好准备,让用户的应属关键字保存到他的下家那里去。下家此时并不提供查询,仅仅是保留一个备份而已。用户与下家必须保持握手,一旦发现握手中断,那么应当立刻采取措施:上家断则下家应当提供服务(根据前面的讨论可知,上家断的时候,搜索会自动转移到下家里去),下家断则上家应当立刻迁移数据备份位置。采取了这样的措施之后,用户掉线对系统产生的影响就微乎其微了。
两个用户同时掉线的可能性是比较小的,因此我们的备份措施可以将用户断线导致的系统开支降到最小。但是两个用户同时掉线的可能我们还是需要考虑的。所以关键字的发布者应当定时查询自己发布的关键字是否还存在于系统中,如果找不到了,立即重新发布。考虑到系统中的每个用户都定时对自己发布的每个关键字进行查询,开销过大,因此这个查询时间不应当太短,至少要二十分钟左右查询一次才是比较合理的。查询的时候还可以同时更新自己的映射地址,以免映射地址失效,导致其它人查询时要通过中央地址服务器才能与自己建立连接。
用户下线后,它负责的关键字应当如何“擦去”呢?
有两种方法可以实现这一点。一种方法是,由它的“下家”负责将它的关键字擦去。前面已经说过,下家是需要与用户保持握手关系的。要达到此目的,下家应当保存用户发布的关键字列表。第二种方法是,由搜索者负责擦去。搜索者发现某个目标用户不存在之后,向关键字保存者报告这个信息,让关键字保存者擦去。两种方法可以结合来使用,对系统的开销都不会太大,也是常数级的。
结论是:用户上线和下线对系统的影响都不大,都是常数级别的。
五、系统规模估计
这一章回答的问题是,按上面提出的结构,系统大约能支撑多少同时在线的用户数呢?
前面的讨论已经发现,平均每个用户要储存的数据 = 平均每个用户有关联的关键字数,这与在线用户人数无关,因此不会对系统造成重大性能影响。
需要注意的是:
1)一些热门的关键字,可能会与非常多的用户有关联,这样有可能造成负责此关键字搜索的用户CPU和网络带宽过于繁忙。系统中的总关键字数量越小,出现这种问题的可能性越大。对于汉语来说,常用词条仅有6万多个,如果系统用户数达到十万以上的级别,必然会造成系统中某些用户非常繁忙,大部分用户非常空闲的状况。这是需要另外设计算法来均衡负载的,比方说一个关键字可以让多个用户同时负责等等。但这些问题过于细节,本文就不赘述了。
2)这种算法的一个缺点是它没考虑到不同用户的计算能力和上下行带宽的巨大差异。能力很小的用户负责的关键字搜索,可能也会形成系统的瓶颈。不过这个问题并不会非常大,因为用户搜索一个关键字迟迟得不到结果时,可以换用另一个关键字来搜索。当然也可以考虑使用更复杂的算法来避免这一点。
如果用算法解决了这两个问题,那么普通用户不会成为系统的瓶颈。
系统中最繁忙的仍然是中央地址服务器。
中央地址服务器有两种,一种是不考虑局域网穿透的,只要记录用户地址、并提供查询服务即可。 另一种是提供STUN服务的,提供了STUN服务就需要与客户端保持握手关系,这是一个巨大的开销。我们仅考虑第一种服务器的负载能力。
假设用户每1000秒搜索一次,每次搜索需要与100个用户进行连接,这100个用户中平均有20个是地址失效,需要到中央地址服务器重新定位寻址的(假设80%的用户都可以通过搜索得到的地址直接访问得到,这个数值与前面提到的客户端定时重新发布自己的关键字的时间间隔有关系)。设系统中的用户数量为N,那么中央服务器每秒钟的收到的查询次数为Q=N*100*20%/1000=N*2%。所以,这个查询次数是与系统规模成正比的,比例系数大约是2%。按:一般的廉价服务器能支持每秒钟2万左右的查询。因此一台廉价的服务器大约能支持100万左右的同时在线用户。
不要小看这个100万的数字,一般的网络系统能有10%的在线率已经很高了,何况用户在线的时间越长,平均搜索频率就会越少。因此这个系统是可以为一千万左右的用户提供服务器的。
如果是STUN型的服务器,那么中央服务器能支撑的用户数还会少很多,所以我们应当采用考虑分布式的服务器系统,用多台廉价服务器提供。
参考文献:
[1]: 徐恪等,对等网络研究综述。
[2]: 罗杰文,Peer to Peer ( P2P ) 综述,http://www.intsci.ac.cn/users/luojw/papers/p2p.htm。