PeerSim中文教程
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
PeerSim中文教程(1):解析 Cycle-based 模式仿真
本文介绍了PeerSim的基本概念,并解析了两个示例以更清晰地说明PeeSim的仿真流程。
Peersim支持两种仿真模式,即Cycle-based的模型和传统的event-based的模型,本文专注于前者,
Cycle-based模型是一个简化的模型,拥有更好的伸缩性及性能,在拥有4GB内存的情况下,event-driven模式目前最多支持十万节点级别,而cycle-based
模式则支持千万个节点级别。
但是Cycle-based模型缺少对传输层的仿真和并行处理,节点之间是直接通信的,仿真核心以一定的顺序周期性地给以节点控制。
在运行时,可以进行任意的操作,如调用其它对象的方法并执行一些计算。
Cycle-based模型损失了一些真实性,虽然一些简单的协议可以忽略这些差别,但是在选择使用这个模型时,需要注意这些区别。
我们可以相对简单地将
Cycle-based的仿真移植到Event-driven引擎上,但在本文中不讨论这个话题。
一.基本介绍
PeerSim鼓励基于接口的模块化编程,每一个组件都能被其它实现了相同接口的组件代替,一般的仿真过程如下:
1.选择网络大小(即节点数量)。
2.选择要实验的一个或多个协议并进行初始化。
3.选择一个或多个Control对象来监视感兴趣的属性,并在仿真时修改一些参数(比如,
网络大小,协议的内部状态,等等)。
4.根据配置文件,调用Simulator类运行仿真。
在仿真时创建的对象都是实现了一个或多个接口的类的实例,主要的接口如下所示:
Node P2P网络是由节点组成的,节点是协议的容器。
Node接口提供了对节点所包含的协议的访问方法,并为节点提供了固定的ID。
CDProtocol 这是一个特定的协议,被设计用来在Cycle-based模型中运行,它只定义了在每一个周期中要运行的操作。
Linkable 一般都由协议来实现,这个接口为其它协议提供了访问邻居节点集合的服务,节点间相同的linkable协议类的实例定义了一个覆盖网络。
Control 实现了这个接口的类可以在仿真期间的某个时间点调度执行,这些类一般用于观察或修改仿真过程。
Cycle-based仿真的生命周期是这样的:
1. 读取配置文件(通过命令行参数传递进来),然后仿真器初始化网络中的节点和节点
中的协议,每个节点都拥有相同的协议栈。
节点和协议的实例是通过克隆来创建的,只有一个原型是通过构造方法创建,其它的节点和协议都是从这个原型中克隆而来。
基于这个原因,协议类中clone 方法的实现是很重要的。
2. 初始化操作,设置每个协议的初始状态。
初始化阶段是由Control 对象控制运行的,
仅在实验开始时运行一次。
在配置文件中,初始化的组件可以由init 前缀识别,在下面讨论的initializer 对象也是controls ,但为了标记其功能以区别于一般的Control 对象,它被配置用来在初始阶段运行。
3. 在初始化完成后,Cycle-based 引擎在每一个周期中调用所有组件(protocols 和
controls )一次,直到完成了指定的周期数,或者某个组件决定终止仿真为止。
在PeerSim 中每一个对象(controls 和protocols )都被赋以一个Scheduler 对象,它定义了什么时候本组件将会被执行。
在默认情况下,所有对象都会在每个周期中运行。
但我们也可以配置一个protocol 或control 只在某些特定的周期中运行,也可以在每一个周期中指定组件的执行顺序。
下图展示了对controls 和protocols 的调度,其中C 代表Control 而P 代表一个协议。
图下方的数字代表PeerSim 的周期,在最后一个周期后,可以运行一个control 来获取最后的快照(snapshot
)。
peersim-schedule
在一个Control 收集数据时,数据将会被格式化并发送到标准输出或重定向到一个文件以进行后续的处理。
配置文件只是一个普通的ASCII 文本,本质上就是java.util.Properties ,以#开头的行代表注释。
用以下的方式在命令行的模式下运行,比如:
1 java -cp peersim.Simulator config-edexample.txt
具体来说,可能是:
1 java -cp
D:\library\peersim-1.0.5\jep-2.3.0.jar;D:\library\peersim-1.0.5\djep -1.0.0.jar;D:\library\peersim-1.0.5\peersim-1.0.5.jar;D:\library\pee rsim-1.0.5\peersim-doclet.jar peersim.Simulator
D:\library\peersim-1.0.5\example\config-edexample.txt
当然你的jar包和配置文件的位置可能有所不同,也可以将这个jar包直接加到classpath上,就不必使用-cp参数来显示指定classpath。
二.配置文件示例一
Gossip-based Aggregation协议,Aggregation是聚集的意思,这里是指对一个分布于网络中的数值集合运行一个特定的函数进行计算(如求平均数,最大值,最小值等等),每个节点周期性地选择一个邻居节点进行通讯(基于覆盖网),并且在每次通讯时,基于前一个取得的近似值,相互更新它们下次计算的近似值。
本例将创建一个由50000个节点组成的固定P2P随机拓扑,选定的协议是使用average函数的Aggregation协议,每个节点中用于求平均的值使用一个区间在(0,100)的线性分布来初始化,最后再定义一个Control监视平均值。
01 # PEERSIM EXAMPLE 1
02
03 random.seed 1234567890
04 simulation.cycles 30
05
06 control.shf Shuffle
07
08 network.size 50000
09
10 protocol.lnk IdleProtocol
11
12 protocol.avg example.aggregation.AverageFunction
13 protocol.avg.linkable lnk
14
15 init.rnd WireKOut
16 init.rnd.protocol lnk
17 init.rnd.k 20
18
19 init.peak example.aggregation.PeakDistributionInitializer
20 init.peak.value 10000
21 init.peak.protocol avg
22
23 init.lin LinearDistribution
24 init.lin.protocol avg
25 init.lin.max 100
26 init.lin.min 1
27
28 # you can change this to select the peak initializer instead
29 include.init rnd lin
30
31 control.avgo example.aggregation.AverageObserver
32 control.avgo.protocol avg
上面的配置中,一部份是全局属性,另一部分对应单个组件的实例。
如simulation.cycles是全局属性,而protocol.lnk.xxx则定义了lnk协议的xxx 参数。
第6行的control.shf Shuffle,Shuffle类是用来重新洗牌,在每次重新洗牌后,在一个Cycle-based类型的仿真周期中,节点迭代的次序将会变成随机的,这个类只对Cycle-based类型的仿真起作用。
每个组件都有一个名字,比如lnk。
对于协议,这个名字将会被映射到一个在PeerSim引擎中称为protocol ID的数值型索引,虽然这个索引不出现在配置文件中,但在仿真时需要使用它来访问协议,这在后面将进一步解释。
一个组件,即protocol或control由下面的语法来声明:
1 [protocol|init|control].string_id [full_path_]classname
注意到类的全路径是可选的,事实上PeerSim可以在类路径中搜索类名,只有在多个类拥有相同的名称时必须使用全路径。
init前缀定义了一个Initializer 对象,它实现了Control接口。
组件的参数(如果有的话)则以下面的语法定义:
1 [protocol|init|control].string_id.parameter_name
第10行定义了第一个协议,键部份包含了它的类型,而值则是组件的类名,由于IdleProtocol类在peersim包中,所以不必使用全路径。
可以为每一个组件声明参数,如第13行;而从第3行到第8行一些全局的仿真属性被引入,如仿真的总周期数和覆盖网的大小。
Shuffle control对每一个周期中节点的访问顺序进行重新洗牌。
从第10行到第13行,引入了两个协议:
∙ IdleProtocol 是存储邻居节点链路的一个静态容器,在进行静态拓扑建模的时候尤其
有用,这个协议的唯一功能是作为其它协议的邻居信息的源,它没有实现CDProtocol 接口但实现了Linkable 接口,Linkable 接口提供了到邻居节点的链路。
∙
AverageFunction 是聚集协议的求平均数版本。
它的参数(linkable )是很重要的,
aggregation 协议需要与邻居节点交流但是本身没有邻居节点列表。
在模块化的方式中,它能应用于任何覆盖网络 ;定义覆盖网的协议栈应当在这里指定,参数linkable 的值是实现了Linkable 接口的协议的类名(在这里是IdleProtocol )。
从15行到26行用于初始化之前声明的所有组件。
前面声明了3个初始化组件,但只有其中的2个被使用了(见29行)。
第一个初始化器,
peersim.init.WireKOut ,进行的是对静态覆盖网的布线,特别的,节点以度数k 随机地与其它节点相连接。
第2个和第3个初始化器是初始化aggregation 协议的可选方案,在这里是指需要求平均的初值。
初始化器设置初始值遵循peak 分布或线性分布。
Peak 的意思是只有一个节点拥有与0不同的值。
而线性则代表节点被拥有一个线性增加的值。
两个初始化都需要一个指定了协议来进行初始化(协议参数)的协议名。
额外的参数是PeakDistributionInitializer 的range(max,min 参数)。
使用peak 还是linear 分布是由include.init 属性来决定的(29行),它指定了选择哪个初始化器。
这个属性也定义了组件运行的顺序,注意到默认的顺序(即如果没有include 属性),是根据字母排序的,对于protocol 和control 的include 属性也是如此。
最后,31行和32行声明了最后一个组件:aggregation.AverageObserver 。
它使用的唯一参数是protocol ,它引用了aggregation.AverageFunction 协议类型,所以这个参数的值是avg 。
在命令行下,注释掉第3行的seed ,运行这个仿真,得到的结果将是:
01 control.avgo: 0 1.0 100.0 50000 50.49999999999998 816.7990066335468 1 1
02 control.avgo: 1 1.2970059401188023 99.38519770395408 50000 50.50000000000005 249.40673287686545 1 1
03 control.avgo: 2 9.573571471429428 84.38874902498048 50000 50.500000000000085 77.89385877895182 1 1
04 control.avgo: 3 23.860361582231647 71.93627224106982 50000 50.49999999999967 24.131366707228402 1 1
05 control.avgo: 4 34.920915967147465 68.92828482118958 50000 50.49999999999994 7.702082905414273 1 1
06 control.avgo: 5 42.37228198409946 59.94511004870823 50000 50.49999999999987 2.431356211088775 1 1
07 control.avgo: 6 45.19621912151794 54.855516163070746 50000
50.499999999999844 0.7741451706754877 1 1
08 control.avgo: 7 47.68716274528092 53.11433934745646 50000 50.49999999999949 0.24515365729069857 1 1
09 control.avgo: 8 48.97706271318158 52.38916238021276 50000 50.50000000000026 0.07746523384731269 1 1
10 control.avgo: 9 49.59674440194668 51.46963472637451 50000 50.49999999999937 0.024689348817011823 1 1
11 control.avgo: 10 49.946490417215266 51.13343750384934 50000 50.50000000000048 0.007807022577928414 2 1
12 control.avgo: 11 50.18143472395333 50.858337267869565 50000 50.49999999999982 0.002493501256296898 2 1
13 control.avgo: 12 50.30454978101492 50.67203454827276 50000 50.500000000000206 7.90551008686205E-4 1 1
14 control.avgo: 13 50.3981394834783 50.60093898689035 50000 50.49999999999967 2.518940347803474E-4 1 1
15 control.avgo: 14 50.449347314832124 50.54962989951735 50000 50.5000000000003 8.071623184942779E-5 1 1
16 control.avgo: 15 50.47368195506415 50.52608817343459 50000 50.49999999999999 2.566284350168338E-5 1 1
17 control.avgo: 16 50.48510475374435 50.518871021756894 50000 50.50000000000012 8.191527862075119E-6 1 1
18 control.avgo: 17 50.49082426764112 50.51000681641142 50000 50.49999999999945 2.570199757692886E-6 1 1
19 control.avgo: 18 50.494810505765045 50.50556221303088 50000 50.5000000000003 8.197012224814065E-7 1 1
20 control.avgo: 19 50.496876367842034 50.50296444951085 50000 50.499999999999524 2.640584231868471E-7 1 1
21 control.avgo: 20 50.498457906558905 50.50182062146254 50000 50.500000000000334 8.565428611988968E-8 1 1
22 control.avgo: 21 50.49905541635283 50.50096466374638 50000 50.49999999999974 2.721171621666857E-8 1 1
23 control.avgo: 22 50.49946061473347 50.500553628252945 50000 50.49999999999975 8.590349265230611E-9 1 1
24 control.avgo: 23 50.49972602272376 50.500315571370415 50000 50.5000000000004 2.6248542064007986E-9 2 1
25 control.avgo: 24 50.4998450606816 50.50018053311878 50000 50.50000000000005 8.845012874999227E-10 1 1
26 control.avgo: 25 50.499894793874255 50.500096923965216 50000
50.50000000000079 1.864501428663076E-10 1 2
27 control.avgo: 26 50.4999267984512 50.500056126785694 50000 50.5000000000003 8.594896829690765E-11 1 1
28 control.avgo: 27 50.49996613170552 50.50003198608762 50000
50.50000000000017 1.9554527178661528E-11 1 1 29 control.avgo: 28 50.49997903068333 50.500019172164286 50000 50.499999999999766 3.274246411310768E-11 1 1
30 control.avgo: 29 50.49998958653935 50.5000099409645 50000
50.50000000000045 0.0 1 1 Observer 组件产生了很多数字,从第3列和第4列的数据(网络中的最大值和最小值),可以很容易地看到方差衰减得非常快,从第12个周期开始,几乎所有的节点都近似于真实的平均值50。
可以用不同的数字或改变初始的分布(例如,使用aggregation.PeakDistributionInitializer )。
同时,也可以替换覆盖网,比如你可以用Newscast 来代替IdleProtocol 。
三. 配置文件二
第二个例子是前面例子的改进版本。
现在aggregation 协议将运行于Newscast 并添加了一些扩展。
例如,有一个Control 对象用来改变网络的大小:在第5个周期至第10个周期间,每次调用时删除500个节点。
01 # PEERSIM EXAMPLE 2
02
03 random.seed 1234567890
04
05 simulation.cycles 30
06
07 control.shf Shuffle
08
09 network.size 50000
10
11 protocol.lnk example.newscast.SimpleNewscast
12 protocol.lnk.cache 20
13
14 protocol.avg example.aggregation.AverageFunction
15 protocol.avg.linkable lnk
16
17 init.rnd WireKOut
18 init.rnd.protocol lnk
19 init.rnd.k 20
20
21 init.pk example.aggregation.PeakDistributionInitializer
22 init.pk.value 10000
23 init.pk.protocol avg
24
25 init.ld LinearDistribution
26 init.ld.protocol 1
27 init.ld.max 100
28 init.ld.min 1
29
30 # you can change this to include the linear initializer instead
31 include.init rnd pk
32
33 control.ao example.aggregation.AverageObserver
34 control.ao.protocol avg
35
36 control.dnet DynamicNetwork
37 control.dnet.add -500
38 #control.dnet.minsize 4000
39 control.dnet.from 5
40 control.dnet.until10
在这里,全局参数与前面的例子相同,现在只讨论添加的扩展。
11行到12行选择了Newscast协议,它唯一的参数是缓存的大小。
Newscast是一个流行性的内容分布和拓扑管理协议,系统中的每个peer都有一个部份的节点信息(事实上是一个固定大小的的节点描述符(node descriptor)的集合),每个描述符是由peer地址和一个创建描述符的时间戳组成的元组。
每个节点通过选择一个随机的邻居并交换信息来更新自身的状态,在交换信息时,两个peer归并信息并且只留下最新的项。
在这种方式中,陈旧的信息(描述符)从系统中删除。
这个过程允许协议修复覆盖网拓扑,用最小的代价删除死链,这种特性在一个节点频繁加入退出的动态系统中是很有用的。
17到28行是初始化部分,与前面的例子相同,然而这里选择使用peak分布。
为了将其转换为线性分布,在31行改变include init的属性。
peak分布将用0初始化所有节点的值,除了取得value参数的那个节点除外。
在36到40行,DynamicNetwork是定义的最后一个组件,如前所述,一个Control 对象可以用来修改仿真中的一些参数,这种改变可以在每个仿真周期中进行(默认的行为),或者使用一种更好的途径。
示例中选择的对象每次在control执行时删除500个节点。
参数add指定了要添加的节点的数量,它可以是负值。
而参数size则为网络大小设定了一个下限值,如果达到了下限,那不会再删除节点;参数from和until 是一个可以为每个组件指定的一般化参数,它们指定了组件所要执行的周期,还有一个未使用的参数是step,如果是2,则表示每两个周期才执行一次。
至于其它的参数则可以参考PeerSim的文档。
四.高级配置特性
高级配置特性由Java Expression Parser提供,用于处理一些表达式。
例如:
01 MAG 2
02 SIZE 2^MAG
03
04 A B+C
05 B D+E
06 C E+F
07 D 1
08 E F
09 F 2
10
11 # 等价于 A=7, B=3, C=4, D=1, E=2 and F=2.
但是注意不允许递归定义。
对于组件的集合,可以指定执行的顺序,默认是根据组件名的字母顺序来排序的,但也可以显式地覆写为:
1 control.conn ConnectivityObserver
2 control.myClass Class1
3 control.1 Class2
4 order.observer myClass conn 1
如果不是所有的名字都出现在这个列表中,那些缺失的对象会按字母顺序执行,例如:
1 order.observer myClass
会导致下面的运行顺序:
observer.myClass, observer.1, observer.conn.
另外一个特性是告知仿真器哪些项是允许执行的:
1 include.control conn myClass
这样可以让control.conn和control.myClass以这种顺序执行,但如果这个列表为空,则什么都不会执行。
PeerSim中文教程(2):编写一个新协议
本文的目的是在PeerSim中用Cycle-based模型实现一个简单的负载均衡算法。
节点的状态有两种值:本地负载(local load)和配额(quota),其中配额是指节点在每个周期中允许传输的“负载”的大小。
配额是必要的,是一个时间单元中能传输的负载上限。
每个节点与和它距离最远的邻居节点交换配额值,这里“距离最远”是指与当前节点的负载差异最大。
经过对比距离,协议将在负载均衡时选用push或pull的方式。
在每个周期之后,配额值将会被存储。
协议并不关心拓扑管理,它依赖于其它组件来访问邻居节点(例如,Newscast,或者由IdleProtocol实现的静态拓扑)。
一. 必要的组件
一般来说只编写一个协议类是不足够的,还需要一些附加的组件。
例如,为了在每个周期结束时为每个节点存储配额值,需要一个特定的Control对象。
基本上来说,PeerSim是一个可替换的组件集合,所以在开发时需要注意模块化以让代码尽可能重用,出于这样的目的,我们这样设计下面的类:
∙protocol 它基于peersim.vector.SimpleValueHolder,这是一个简单的基类,用于访问一个浮点变量。
Aggreation协议也使用了同样的基类。
∙ResetQuota 用于在每个周期结束时存储每个节点配额的control。
∙QuotaObserver 一个control,用于监视quota参数,即覆盖网中交换的流量大小。
∙initialization 这是在aggregation示例中的初始化器,这里也可以直接使用,因为它们实现了同样的接口SingleValueHolder。
注意在example包中提供的初始化器是轻量
级的,开发者应当更多地使用peersim.vector.*包中的初始化器。
∙observers 可以使用aggreagation.AverageObserver,因为这些组件实现了相同接口。
下面将根据源代码来解释这一过程:
01 package example.loadbalance;
02
03 import peersim.config.Configuration;
04 import peersim.config.FastConfig;
05 import peersim.core.*;
06 import peersim.vector.SingleValueHolder;
07 import peersim.cdsim.CDProtocol;
08
09
public class BasicBalance extends SingleValueHolder implements CDProtocol { 10 protected static final String PAR_QUOTA = "quota";
11
12
/** Quota amount. Obtained from config property {@link #PAR_QUOTA}. */ 13 private final double quota_value;
14 protected double quota; // current cycle quota 15
16 // 初始化
17 public BasicBalance(String prefix) { 18 super(prefix);
19 // get quota value from the config file. Default 1. 20 quota_value = (Configuration.getInt(prefix + "." 21 + PAR_QUOTA, 1)); 22 quota = quota_value;
23 }
这个类需要实现 peersim.cdsim.CDProtocol ,它提供了一个nextCycle()方法,这个方法包含了协议的算法。
而且,这个协议继承了SingleValueHolder 类(它是SingleValue 接口的实现),为内部变量提供getter 和setter 方法,允许control 以可重用的方式来操作这些数据,在这个例子中,变量保存了节点的实际负载。
在构造方法中的String 参数是配置组件的全名,例如,对于LoadBanlance 协议来说是protocol.lb 。
1 // Resets the quota.
2 protected void resetQuota() {
3 this.quota = quota_value;
4 }
resetQuota 方法在每个周期结束时被一个control 对象调用,显然地,一个恰当的control 条目应该在配置文件中出现,这里是loadbalance.ResetQuota 01 public void nextCycle(Node node, int protocolID) { 02 int linkableID = FastConfig.getLinkable(protocolID);
03 Linkable linkable = (Linkable) node.getProtocol(linkableID); 04 if (this.quota == 0) {
05 return; // quota is exceeded
06 }
07 // this takes the most distant neighbor based on local load
08 BasicBalance neighbor = null;
09 double maxdiff = 0;
10 for(int i = 0; i < linkable.degree(); ++i) {
11 Node peer = linkable.getNeighbor(i);
12 // The selected peer could be inactive
13 if(!peer.isUp())
14 continue;
15 BasicBalance n = (BasicBalance)peer.getProtocol(protocolID);
16 if(n.quota == 0.0)
17 continue;
18 double d = Math.abs(value - n.value);
19 if(d > maxdiff) {
20 neighbor = n;
21 maxdiff = d;
22 }
23 }
24 if(neighbor == null) {
25 return;
26 }
27 doTransfer(neighbor);
28 }
这个方法是由CDProtocol接口声明的,它定义了协议的行为。
这里的参数代表了一个对节点自身的引用(即仿真器调用其nextCycle方法的那个节点)和正在运行的协议的protocol ID。
首先我们要取得实现了Linkable接口的协议的protocol ID来访问节点的邻居节点,这可以由下面的代码来完成:
1 int linkableID = FastConfig.getLinkable(protocolID);
2 Linkable linkable = (Linkable)node.getProtocol(linkableID);
使用静态类peersim.config.FastConfig我们可以取得为正在执行的协议而配
置的linkable协议的protocol ID。
如果本地的配额是0,代表着这个节点已经使用完网络流量,所以直接return。
为了取得与本地节点距离最远的节点,我们循环遍历所有邻居节点的负载值;邻
居节点的数量等于节点的度(这可以通过linkable接口来访问),通过linkable 接口来取得节点的代码如下:
1 Node peer = linkable.getNeighbout(i);
而从这个节点就可以取得BasicBanlance协议:
1 BasicBalance n = (BasicBalance)peer.getProtocol(protocolID);
当协议寻找到一个合适的邻居节点后,调用doTransfer方法来进行负载均衡。
01 protected void doTransfer(BasicBalance neighbor) {
02 double a1 = this.value;
03 double a2 = neighbor.value;
04 double maxTrans = Math.abs((a1 - a2) / 2);
05 double trans = Math.min(maxTrans, quota);
06 trans = Math.min(trans, neighbor.quota);
07 if(a1 <= a2) {// PULL phase
08 a1 += trans;
09 a2 -= trans;
10 } else{ // PUSH phase
11 a1 -= trans;
12 a2 += trans;
13 }
14 this.value = a1;
15 this.quota -= trans;
16 neighbor.value = a2;
17 neighbor.quota -= trans;
18 }
doTransfer方法将会在当前节点和由参数指定的邻居节点间进行实际的负载交换,它决定了在负载均衡时是用pull还是push方法:在Push的情况下,本地值增加而其它节点的值减少,在push情况下则反之。
maxTrans变量是两个涉及的节点需要达到平衡而传输的负载的绝对值;由于配额(quota)是每个周期中传输的上限,这个算法将会选择quota和maxTrans中的最小值,最后两个节点都会减去相同数量的负载值。
二. 负载均衡的control类代码
01 package example.loadbalance; 02 import peersim.config.*; 03 import peersim.core.*; 04
05 public class ResetQuota implements Control { 06 //参数
07 private static final String PAR_PROT = "protocol";
08
09 /** Value obtained from config property {@link #PAR_PROT}. */ 10 private final int protocolID;
11
12 // 初始化
13 public ResetQuota(String prefix) {
14 protocolID = Configuration.getPid(prefix + "." + PAR_PROT); 15 } 16
17 public boolean execute() {
18 for (int i = 0; i < Network.size(); ++i) { 19
((BasicBalance) Network.get(i).getProtocol(protocolID)).resetQuota(); 20 }
21 return false; 22 }
23 }
这段代码很简洁,部份原因是Control 接口本身是很简单的,它只有一个execute 方法。
构造方法利用配置文件来进行初始化。
execute 方法会在所有的协议上调用 resetQuota 方法,它通过Network 类来访问协议,Network 是一个只拥有静态数据域的静态类,你可将它视为是一个节点的数组。
三. 扩展协议
这是对前面的协议的扩展。
核心部份是相同的,但是算法在决定将要发送或接收多少负载时,使用了全局的负载平均而不是根据距离最远的邻居节点的负载值。
为了计算全局的负载平均值,这里有一个小技巧,虽然本来可以通过聚集来求取平均数,但我们可以通过运行一个拥有全局信息的静态方法来仿真aggregation 协议,这个方法为所有节点初始化了一个全局变量,这样我们就能提升性能的同时又不损失太多的真实性。
这个协议也是为了利用newscast 协议:当一个节点到达了全局负载值(平均),它将会将它的fail-state 转变为DOWN ,然后这个节点会从覆盖网退出,因为newscast 协议会删除它。
这样的影响是有多少节点达到平均负载则会减少多少个节点。
01 package example.loadbalance; 02
03 import peersim.core.*;
04 import peersim.config.FastConfig; 05
06 public class AvgBalance extends BasicBalance { 07 08 /**
09 * The overall system average load. It is computed once by 10 * {@link #calculateAVG(int)} method.
11 */
12 public static double average = 0.0; 13 14 /**
15
* This flag indicates if the average value computation has been performed 16 * or not. Default is NO. 17 */
18 public static boolean avg_done = false; 19
20 // 初始化
21 public AvgBalance(String prefix) {
22 super(prefix); // calls the BasicBalance constructor. 23 } 24 25 /**
26 * Calculates the system average load. It is run once by the first 27 * node scheduled. 28 */
29 private static void calculateAVG(int protocolID) { 30 int len = Network.size();
31 double sum = 0.0;
32 for (int i = 0; i < len; i++) { 33
AvgBalance protocol = (AvgBalance)
Network.get(i).getProtocol(protocolID); 34 double value = protocol.getValue();
35 sum += value; 36 }
37
38 average = sum / len; 39 avg_done = true; 40 }
第一部份是很简单的,定义了两个全局变量,average 和avg_done ,其中avg_done 是个用来确定不进行超过一次计算的标志。
注意,虽然看起来在构造方法中定义一个计算平均值的方法是一个更优雅的方案,但这种方案是错误的!因为在构造方法运行时,并不能保证负载的分布已经定义了:那样的话全局的平均数是未定义的。
1 protected static void suspend(Node node) {
2 node.setFailState(Fallible.DOWN);
3 }
这个功能函数用于让节点从覆盖网中退出,这里只是简单地在Fallible 接口中设置节点的状态。
01 public void nextCycle(Node node, int protocolID) {
02 // 只运行一次: 03 if (avg_done == false) { 04 calculateAVG(protocolID);
05 System.out.println("AVG only once " + average); 06 }
07
08 if (Math.abs(value - average) < 1) {
09 AvgBalance.suspend(node); // switch off node 10 return;
11 }
12
13 if (quota == 0)
14 return; // skip this node if it has no quota
15 Node n = null; 16
17 if (value < average) {
18 n = getOverloadedPeer(node, protocolID); 19 if (n != null) {
20 doTransfer((AvgBalance) n.getProtocol(protocolID)); 21 } 22 } else {
23 n = getUnderloadedPeer(node, protocolID); 24 if (n != null) {
25 doTransfer((AvgBalance) n.getProtocol(protocolID));
26 }
27 } 28
29 if (Math.abs(value - average) < 1) 30 AvgBalance.suspend(node); 31
32 if (n != null) {
33
if (Math.abs(((AvgBalance) n.getProtocol(protocolID)).value- average) < 1) 34 AvgBalance.suspend(n);
35 } 36 }
nextCycle 方法是核心的协议算法,它首先检查平均数,如果标志没有设置就会进行计算。
如果当前负载和平均负载的差别小于1(每个周期中固定的配额值),那么节点将会根据newcast 协议从覆盖网退出;进一步地,如果配额已经使用完,将会直接return 。
然后,协议会检查本地的负载值是小于还是大小平均值,并分别查找负载最大和最小的邻居,最后进行交换。
01 private Node getOverloadedPeer(Node node, int protocolID) {
02 int linkableID = FastConfig.getLinkable(protocolID);
03 Linkable linkable = (Linkable) node.getProtocol(linkableID); 04 Node neighborNode = null;
05 double maxdiff = 0.0;
06 for (int i = 0; i < linkable.degree(); ++i) {
07 Node peer = linkable.getNeighbor(i);
08 if (!peer.isUp()) // only if the neighbor is active 09 continue;
10 AvgBalance n = (AvgBalance) 11 peer.getProtocol(protocolID); 12 if (n.quota == 0)
13 continue;
14 if (value >= average && n.value >= average) 15 continue;
16 if (value <= average && n.value <= average) 17 continue;
18 double d = Math.abs(value - n.value); 19 if (d > maxdiff) { 20 neighborNode = peer; 21 maxdiff = d; 22 }
23 }
24 return neighborNode; 25 }
01 private Node getUnderloadedPeer(Node node, int protocolID) { 02
int linkableID =
FastConfig.getLinkable(protocolID);
03 Linkable linkable = (Linkable) node.getProtocol(linkableID);
04 Node neighborNode = null;
05 double maxdiff = 0.0;
06 for (int i = 0; i < linkable.degree(); ++i) { 07 Node peer = linkable.getNeighbor(i);
08 if (!peer.isUp()) // only if the neighbor is active 09 continue;
10 AvgBalance n = (AvgBalance) peer.getProtocol(protocolID); 11 if (n.quota == 0) 12 continue;
13 if (value >= average && n.value >= average) 14 continue;
15 if (value <= average && n.value <= average)
16 continue;
17 double d = Math.abs(value - n.value);
18 if(d < maxdiff) {
19 neighborNode = peer;
20 maxdiff = d;
21 }
22 }
23 return neighborNode;
24 }
查找最大和最小负载的邻居节点的代码是很相似的,在这里都展示是出于完整性的缘故。
四. 协议的评估
负载均衡协议是为了减少负载的变化,而变化可以使用
aggregation.AverageObserver或者loadbalance.LBObserver(它们是非常相似的)来进行分析,出于这个标准,两个协议几乎拥有相同的性能,并独立于最初使用的分布。
然而,AVGBalance协议相对BasicBalance来说提升了整体的负载传输,AVGBalance传输了一个可证明是最小的负载。
我们可以实现一个control来观察正被传输的负载:
01 package example.loadbalance;
02
03 import peersim.config.*;
04 import peersim.core.*;
05 import peersim.util.*;
06
07 public class QuotaObserver implements Control {
08 /**
09 * The protocol to operate on.
10 */
11 private static final String PAR_PROT = "protocol";
12
13 /**
14 * The name of this observer in the configuration file.
15 */
16 private final String name;
17
18 /** Protocol identifier,*/
19 private final int pid;
20
21 // 构造方法
22 public QuotaObserver(String name) {
23 = name;
24 pid = Configuration.getPid(name + "." + PAR_PROT); 25 }
26
27 public boolean execute() {
28 IncrementalStats stats = new IncrementalStats();
29 for (int i = 0; i < Network.size(); i++) {
30 BasicBalance protocol = (BasicBalance)
Network.get(i).getProtocol(pid);
31 stats.add(protocol.quota);
32 } 33 /* 打印统计量*/
34 System.out.println(name + ": " + stats);
35 return false;
36 }
37 }
原理是很简单的,在每一个仿真周期中,它收集剩余的quota 并在终端上打印统计数据,从这些统计数据和配额的初始值就可以计算出已经被传输的负载。
PeerSim中文教程(3):拓扑生成器
本教程描述了如何构建一个新的PeerSim拓扑生成器。
1. 什么是拓扑?为什么它很重要?
在一个大型的动态P2P系统中,节点没有关于整个网络的信息,而所有的节点都可能拥有一些邻居节点,即节点能“感知”的peers,这种“感知”的关系就定义了一个覆盖网络,这是P2P系统中的一个基本概念。
很多P2P协议都需要在多个不同的网络拓扑上进行实验。
PeerSim中的peersim.dynamic.Wire*类已经包含了很多拓扑结构,可以直接用来对linkable 协议进行初始化,本教程将展示如何构建一个自定义的拓扑生成器。
2. 一个用来模拟Internet的简单模型
下面我们将编写一个拓扑生成器来构建类似于Internet的树状拓扑,整个构建过程基于一个特定的,与位置相关的preferential attachment方法,编写规则很简单,并且会考虑几何和网络的限制以更好地模拟真实的网络。
Preferential attachment由参数a来调整,这个参数能扩大或减少几何位置所带来的影响。
这个规则的策略如下:给定一个单位正方形,将x0置于中心,即x0= (0.5,0.5),这个节点被称为root,令W()为与root相隔的跳数(hops),对于i=1 … n-1,随机在单位正方形中选择一个x i,然后选择使下面的表达式值最小的节点x j来连接它:
在这里dist()是欧几里德距离而a (alpha)是权重参数,显然,
通过这个方案我们得到了一个x0以为根的树。
这个拓扑中每个节点(除了root 外)的出度都为1,如果想更深入地理解这个模型,可以参考下面的文章:
∙Heuristically Optimized Trade-offs: A New Paradigm for Power Laws in the Internet
∙Degree distributions of the FKP network model
∙On Power-Law Relationships of the Internet Topology
3. 如何编码
我们的目标是编写一个可以根据 a (alpha)参数生成所需拓扑的PeerSim组件,并且能对生成的拓扑进行分析。
这个拓扑可以在仿真过程中逐步生成,也可以用一个步骤生成拓扑,在这里我们倾向后者。
为了构建需要的拓扑结构,我们需要下面的组件(注意这只是其中一种方案)。
∙一个protocol 类,可以存储坐标,它不具备行为元素,只是一个普通的容器。
∙一个initializer 类,可以为每个节点设置坐标值。
∙一个control 类,可在一个任意的linkable协议中根据坐标连接拓扑(在节点间添加link)
∙一个observer 类,将拓扑结构打印到一个文件中(例如用GnuPlot对图进行可视化)。
∙一个observer 类,用来收集节点入度的分布的统计数据
∙一个observer 类,用来测试对随机节点失效的健壮性
在下节我们将看到,一些我们列出来的类是PeerSim中的基本组件,它们都实现了Linkable接口,Linkable以模块化的方式为用户提供了一个能处理任何拓扑结构的抽象。
4. 编写代码
a. Protocol类
01 import peersim.core.Protocol;
02
03 public class InetCoordinates implements Protocol {
04
05 /** 2d coordinates components. */
06 private double x, y;
07
08 public InetCoordinates(String prefix) {
09 /* Un-initialized coordinates defaults to -1. */
10 x = y = -1;
11 }
12
13 public Object clone() {
14 InetCoordinates inp = null;
15 try{
16 inp = (InetCoordinates) super.clone();。