Home > P2P, PeerSim > PeerSim 中文教程:Event-driven 模型

PeerSim 中文教程:Event-driven 模型

1. 介绍

本教程使用Event-driven模型来演示一个简单的例子,仍然使用的是gossip-based平均数协议,对消息的发送将进行更细节的建模;通过与cycle-based模型的对比,可以发现本协议存在的问题。

在Event-based模型中,除了时间管理和control传递给protocols的方式以外,其它与cycle-based模型相同。不可执行的Protocols(只用于存储数据,比如只存储邻居节点的linkable协议,或存储数值的vectors)可以以同样的方式应用和初始化,在peersim.cdsim包之外的controls也都可以使用。在默认情况下,在cycle-based模型中,controls会的每个周期中调用 ,但在event-based模型中,它们需要进行明确的调度,因为事件驱动模型并不存在周期的概念。

显然,我们可以编写专用于event-based模型的controls,即可以给协议发送事件(消息)。在很多情况下,这是必要的,因为系统经常完全或部分地由外部事件如用户的查询来驱动 ,这能很好地用由生成这些事件,并且驱动仿真执行的controls进行建模。

有些组件是不可用的。例如依赖于静态类peersim.cdsim.CDState(它提供了读取cycle相关的全局状态的接口)的所有组件。我们的经验是,很多依赖于这个状态的 cycle-based 组件可以经过简单的修改并删除这个依赖。

然而,可能有些令人吃惊的是,实现了cycle-based接口的peersim.cdsim.CDProtocol也可以使用于event-based模型,但是必须指出,在大部份的情况下这样做没有什么意义。然而,这个特性的有用之处在于:它让周期性地调用protocols变得很简单,这是一个几乎对所有与housekeeping,失效检测和sending heartbeat message有关的P2P协议来说典型的特性。

2. Protocol

下面以event-based模型来实现平均数协议:

package example.edaggregation;

import peersim.vector.SingleValueHolder;
import peersim.config.*;
import peersim.core.*;
import peersim.transport.Transport;
import peersim.cdsim.CDProtocol;
import peersim.edsim.EDProtocol;

/**
* Event driven version of epidemic averaging.
*/
public class AverageED extends SingleValueHolder
			implements CDProtocol, EDProtocol {

首先,这里同时实现了EDProtocol和CDProtocol接口,前者能让这个类能处理输入的消息,后者则可能令人困惑,因为它属于cycle-based模型的接口。但注意 event-based 的协议不是必须实现CDProtocol接口的,然而想要实现一个可以周期性取得control的协议时,可以通过实现CDProtocol接口,并在配置文件中设置一个CDScheduler 来实现。这样,代码就显得更清晰:以一个单独的组件来管理周期性的执行,而且能单独地进行配置。最后,我们还能简单地将 cycle-based 的协议移植到 event-based 模型上。

	/**
	* @param prefix string prefix for config properties
	*/
	public AverageED(String prefix) {
		super(prefix);
	}

这个简单的协议并不读取任何配置参数,现在来关注cycle-based接口的实现,这个方法定义了周期性进行的行为。

	/**
	* This is the standard method the define periodic activity.
	* The frequency of execution of this method is defined by a
	* {@link peersim.edsim.CDScheduler} component in the configuration.
	*/
	public void nextCycle( Node node, int pid ) {
		Linkable linkable =
			(Linkable) node.getProtocol( FastConfig.getLinkable(pid) );

		if (linkable.degree() > 0) {
			Node peern = linkable.getNeighbor(
				CommonState.r.nextInt(linkable.degree()));

			// XXX quick and dirty handling of failures
			// (message would be lost anyway, we save time)
			if(!peern.isUp())
				return;

			AverageED peer = (AverageED) peern.getProtocol(pid);

			((Transport)node.getProtocol(FastConfig.getTransport(pid))).
			send(node,peern,new AverageMessage(value,node),pid);
		}
	}

在这里要观察的是event-based模型中的方法,即如何处理传输层。首先,FastConfig类让我们能访问为这个协议配置的传输层,通过使用这个传输层,我们可以在其它节点上将消息发送给协议。一条消息可以是任意的对象 ,由于这个仿真器并不是分布式的,所以不用处理序列化等等问题;而对象则会通过引用来存储。
目标协议是由目标节点peern定义的,协议的标识符则是pid,在这个例子中,我们在一个不同的节点发送消息给同一个协议。显然,目标协议应当实现EDProtocol接口。

	/**
	* This is the standard method to define to
	* process incoming messages.
	*/
	public void processEvent( Node node, int pid, Object event ) {
		AverageMessage aem = (AverageMessage)event;

		if( aem.sender!=null )
			((Transport)node.getProtocol(FastConfig.getTransport(pid))).
			send(node,aem.sender,
				new AverageMessage(value,null),pid);

		value = (value + aem.value) / 2;
	}
}

上面实现的是EDSimulator中的方法,它用于处理进入的消息。本例中,消息只有一种类型,我们只需要检查sender是否为null,因为如果为null,则我们无需应答消息(当然,实际上这已经是一种“应答”),而如果需要应答时则是通过传输层来处理。

/**
* The type of a message. It contains a value of type double
* and the sender node of type {@link peersim.core.Node}.
*/
class AverageMessage {
	final double value;

	/** If not null,
	/*  this has to be answered, otherwise this is the answer. */
	final Node sender;

	public AverageMessage( double value, Node sender ) {
		this.value = value;
		this.sender = sender;
	}
}

这个私有类是协议所使用的消息类型,说其私有是因为没有任何其它组件需要处理这种类型的消息。

3. 配置文件

配置文件与Cycle-based的配置相似,只有很少地方不同,但这些区别很重要。

# network size
SIZE 1000
# parameters of periodic execution
CYCLES 100
CYCLE SIZE*10000
# parameters of message transfer
# delay values here are relative to cycle length, in percentage,
# eg 50 means half the cycle length, 200 twice the cycle length, etc.
MINDELAY 0
MAXDELAY 0
# drop is a probability, 0<=DROP<=1
DROP 0

这里定义了一些常量以让配置文件更清晰,同时也更易于在命令行中修改,例如,CYCLE定义了一个周期的长度。

random.seed 1234567890
network.size SIZE
simulation.endtime CYCLE*CYCLES
simulation.logtime CYCLE

在这里,simulation.endtime 是最关键的参数,它通知仿真器何时终止。PeerSim用一个64位的long 长整型来表示时间,在启动时它为0,并且由消息的延迟来推进。在事件队列为空,或者队列中所有的事件的调度时间都迟于终止时间时,仿真将会终止。

仿真器将在标准错误窗口打印时间的进度,simulation.logtime 指定了打印这些消息的频率。

################### protocols ===========================
protocol.link peersim.core.IdleProtocol
protocol.avg example.edaggregation.AverageED
protocol.avg.linkable link
protocol.avg.step CYCLE
protocol.avg.transport tr
protocol.urt UniformRandomTransport
protocol.urt.mindelay (CYCLE*MINDELAY)/100
protocol.urt.maxdelay (CYCLE*MAXDELAY)/100
protocol.tr UnreliableTransport
protocol.tr.transport urt
protocol.tr.drop DROP

在这里我们配置了协议(avg),指定了覆盖网络(link)和传输层(tr),同时也需要指定step参数,这与cycle-based模型是相似的。这是由于我们实现了cycle-based接口,所以我们需要指定一个周期的长度以使用它。

覆盖网络只是一个links的容器,在仿真过程中会保持不变,它会按如下初始化:
传输层也被视为一个协议进行配置的,它对随机延迟和消息丢失进行了建模。首先我们定义了一个拥有随机延迟(urt)的传输层,然后将它封装在一个通用的传输层包装器中,并以给定的概率tr来丢弃消息。传输层被定义在peersim.transport包中,和其它组件一样,它也是模块化的,用户可以简单地开发和使用一个新的传输层协议。

################### initialization ======================
init.rndlink WireKOut
init.rndlink.k 20
init.rndlink.protocol link
init.vals LinearDistribution
init.vals.protocol avg
init.vals.max SIZE
init.vals.min 1
init.sch CDScheduler
init.sch.protocol avg
init.sch.randstart

在这里唯一属于event-based模型范畴的是sch,它对cycle-based接口(nextCycle)的周期性调用进行调度,在这个配置中,这个组件首先会在0到CYCLE的时间内给所有的节点赋一个随机的点,这是在协议avg上第一次调用nextCycle。而下一次的调用将严格地发生在恰好CYCLE的时间间隔之后。更多之前(advanced)的方法也会存在,除此之外,这个调度可以通过类的扩展进行自定义,但在这里不作详述。

################ control ==============================
control.0 SingleValueObserver
control.0.protocol avg
control.0.step CYCLE

注意和协议avg一样,我们也需要指定step参数,它指定了这个control多久会调用一次,否则controls能像cycle-based模型那样进行调度,只是没有默认的step,因为这里没有周期。

4. 运行协议

如果我们用上面的配置文件运行仿真,我们将在标准错误窗口中得到输出:

Simulator: loading configuration
ConfigProperties: File config-edexample.txt loaded.
Simulator: starting experiment 0 invoking peersim.edsim.EDSimulator
Random seed: 1234567890
EDSimulator: resetting
Network: no node defined, using GeneralNode
EDSimulator: running initializers
- Running initializer init.rndlink: class peersim.dynamics.WireKOut
- Running initializer init.sch: class peersim.edsim.CDScheduler
- Running initializer init.vals: class peersim.vector.LinearDistribution
EDSimulator: loaded controls [control.0]
Current time: 0
Current time: 10000000
Current time: 20000000
Current time: 30000000
Current time: 40000000
Current time: 50000000
.
.
.
Current time: 980000000
Current time: 990000000
EDSimulator: queue is empty, quitting at time 999980413

标准输出窗口的输出如下 :

control.0: 1.0 1000.0 1000 500.5 83416.66666666667 1 1
control.0: 37.5 919.0 1000 500.5 25724.159091250687 1 1
control.0: 206.7109375 767.890625 1000 500.5 8096.807036889389 1 1
control.0: 352.373046875 695.453125 1000 500.5 2578.022573176135 1 1
control.0: 412.430419921875 625.474609375 1000 500.5 801.1082179446831 1 1
control.0: 436.43787479400635 570.459858417511 1000 500.5 243.53994072762902 1 1
control.0: 470.7608990445733 527.0359845032217 1000 500.49999999999994 74.13788674564383 1 2
control.0: 483.6040476858616 518.0301055684686 1000 500.49999999999903 23.428974301677556 1 1
control.0: 490.5196089811798 512.0301471857779 1000 500.4999999999993 7.285566419597019 1 1
control.0: 494.97216907397836 506.0375954180854 1000 500.4999999999999 2.1798299307442246 1 1
control.0: 497.18190345272336 503.5837144460532 1000 500.5000000000001 0.6073148838336206 1 1
control.0: 498.54320551492475 502.3533156558903 1000 500.5 0.1786794435445898 1 2
control.0: 499.4023441821402 501.4962048486104 1000 500.49999999999966 0.055257607540637785 1 1
control.0: 500.0032071191514 501.09832936709677 1000 500.4999999999995 0.017914865984002482 1 1
.
.
.
control.0: 500.5 500.5 1000 500.5 0.0 1000 1000

这些值分别代表最小值,最大值,样本的数量,平均数,方差,最小值实例的数量,最大值实例的数量。这个输出代表已经找到正确的平均数500.5,方差为 0,即所有的节点都拥有相同的正确的平均值。

这看上去很不错,我们可以添加一些延迟并观察一下会发生什么(在默认的配置文件中delay是0),如果在命令行中添加MINDELAY=10和MAXDELAY=10(即表示所有的消息都会恰好延迟10%的周期时间),我们将会得到:

.
.
.
control.0: 499.126081326076 499.126081326076 1000 499.12608132608807 0.0 1000 1000
control.0: 499.126081326076 499.126081326076 1000 499.12608132608807 0.0 1000 1000
control.0: 499.126081326076 499.126081326076 1000 499.12608132608807 0.0 1000 1000

也就是说,简单的延迟方案已经破坏了这个协议的良好属性:尽管是收敛的,但结果不正确,还可以验证不同的随机种子将会导致不同的结果,而改变延迟的和丢失率也对性能有影响。

那么,将延迟和丢失率保持为0能保证得到正确的行为吗?不一定。下面用更短的周期长度来进行实验,例如,CYCLE=SIZE,这意味着在同一个时间点一般有更多的事件调度发生,在这样的情况下,PeerSim 以随机的顺序来执行这些事件,我们将得到:

.
.
.
control.0: 500.4835099381911 500.4835099381911 1000 500.48350993818605 7.807634196601234E-9 1000 1000
control.0: 500.4835099381911 500.4835099381911 1000 500.48350993818605 7.807634196601234E-9 1000 1000
control.0: 500.4835099381911 500.4835099381911 1000 500.48350993818605 7.807634196601234E-9 1000 1000

结论是什么?必须指出,这个错误的结果是由于时间分辨率(time resolution)不足导致的。如果消息的确是零延迟的,那么执行时间的微小差别也会导致不重叠的成对交换,很明显,在连续的时间上没有事件会在相同的时间点执行。然而,在消息传送时微小的随机延迟让结果变得很有意义,因为顺序的不确定性是确实存在的。

大体而言,使用event-based模型能看到很多在cycle-based模型中看不到的问题,然而,这也会引入一些假象。

5. 说明

这个例子仅是为了入门,推荐进一步学习相关文档,如peersim.edsim, peersim.transport等。

Related posts:

  1. PeerSim 中文教程:编写一个新协议
  2. PeerSim 中文教程:拓扑生成器
  3. PeerSim 中文教程:解析 Cycle-based 模式仿真
  4. PeerSim 源代码分析之一:程序入口
  5. 常用的 P2P 仿真平台
Categories: P2P, PeerSim Tags:
  1. No comments yet.
  1. No trackbacks yet.