本文介紹了PeerSim的基本概念,並解析了兩個示例以更清晰地說明PeeSim的仿真流程。
Peersim支持兩種仿真模式,即Cycle-based的模型和傳統的event-based的模型,本文專注於前者,
Cycle-based模型是一個簡化的模型,擁有更好的伸縮性及性能,在擁有4GB內存的情況下,event-driven模式目前最多支持十萬節點級別,而cycle-based模式則支持千萬個節點級別。 但是Cycle-based模型缺少對傳輸層的仿真和並行處理,節點之間是直接通信的,仿真核心以一定的順序週期性地給以節點控制。在運行時,可以進行任意的操作,如調用其它對象的方法並執行一些計算。
Cycle-based模型損失了一些真實性,雖然一些簡單的協議可以忽略這些差別,但是在選擇使用這個模型時,需要注意這些區別。我們可以相對簡單地將Cycle-based的仿真移植到Event-driven引擎上,但在本文中不討論這個話題。
一.基本介紹
PeerSim鼓勵基於接口的模塊化編程,每一個組件都能被其它實現了相同接口的組件代替,一般的仿真過程如下:
-
選擇網絡大小(即節點數量)。
-
選擇要實驗的一個或多個協議並進行初始化。
-
選擇一個或多個Control對象來監視感興趣的屬性,並在仿真時修改一些參數(比如,網絡大小,協議的內部狀態,等等)。
-
根據配置文件,調用Simulator類運行仿真。
在仿真時創建的對象都是實現了一個或多個接口的類的實例,主要的接口如下所示:
Node |
P2P網絡是由節點組成的,節點是協議的容器。Node接口提供了對節點所包含的協議的訪問方法,併爲節點提供了固定的ID。 |
CDProtocol |
這是一個特定的協議,被設計用來在Cycle-based模型中運行,它只定義了在每一個週期中要運行的操作。 |
Linkable |
一般都由協議來實現,這個接口爲其它協議提供了訪問鄰居節點集合的服務,節點間相同的linkable協議類的實例定義了一個覆蓋網絡。 |
Control |
實現了這個接口的類可以在仿真期間的某個時間點調度執行,這些類一般用於觀察或修改仿真過程。 |
Cycle-based仿真的生命週期是這樣的:
-
讀取配置文件(通過命令行參數傳遞進來),然後仿真器初始化網絡中的節點和節點中的協議,每個節點都擁有相同的協議棧。節點和協議的實例是通過克隆來創建的,只有一個原型是通過構造方法創建,其它的節點和協議都是從這個原型中克隆而來。基於這個原因,協議類中clone方法的實現是很重要的。
-
初始化操作,設置每個協議的初始狀態。初始化階段是由Control對象控制運行的,僅在實驗開始時運行一次。在配置文件中,初始化的組件可以由init前綴識別,在下面討論的initializer對象也是controls,但爲了標記其功能以區別於一般的Control對象,它被配置用來在初始階段運行。
-
在初始化完成後,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\peersim-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監視平均值。
03 |
random.seed
1234567890 |
10 |
protocol.lnk
IdleProtocol |
12 |
protocol.avg
example.aggregation.AverageFunction |
13 |
protocol.avg.linkable
lnk |
19 |
init.peak
example.aggregation.PeakDistributionInitializer |
21 |
init.peak.protocol
avg |
23 |
init.lin
LinearDistribution |
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個節點。
03 |
random.seed
1234567890 |
11 |
protocol.lnk
example.newscast.SimpleNewscast |
14 |
protocol.avg
example.aggregation.AverageFunction |
15 |
protocol.avg.linkable
lnk |
21 |
init.pk
example.aggregation.PeakDistributionInitializer |
25 |
init.ld
LinearDistribution |
33 |
control.ao
example.aggregation.AverageObserver |
34 |
control.ao.protocol
avg |
36 |
control.dnet
DynamicNetwork |
在這裏,全局參數與前面的例子相同,現在只討論添加的擴展。
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提供,用於處理一些表達式。例如:
但是注意不允許遞歸定義。
對於組件的集合,可以指定執行的順序,默認是根據組件名的字母順序來排序的,但也可以顯式地覆寫爲:
1 |
control.conn
ConnectivityObserver |
4 |
order.observer
myClass conn 1 |
如果不是所有的名字都出現在這個列表中,那些缺失的對象會按字母順序執行,例如:
會導致下面的運行順序:
observer.myClass, observer.1, observer.conn.
另外一個特性是告知仿真器哪些項是允許執行的:
1 |
include.control
conn myClass |
這樣可以讓control.conn和control.myClass以這種順序執行,但如果這個列表爲空,則什麼都不會執行。