EdgeCloudSim这个工具包的使用细节,不论是在论文还是在readme中,作者并没有给我们详细描述,大概是觉得源码都给你们了,自己看不就好了吗。所以,在这篇文章里想对这个工具包的各个模块的源码进行一个分析,深入了解它的工作原理,并记录自己的学习过程,看看它到底是不是真的能实现我们所需的移动边缘计算的仿真效果。
软件架构
EdgeCloudSim的架构如下所示。
主要包括了几个模块,在作者的论文中也有解释:
- 核心仿真模块
- 网络模块
- 边缘协调器模块
- 负载生成器模块
- 核心仿真模块
- 移动模块
在src文件夹下,存放了所有源码,这里每个文件夹都是一个模块。
然后,对应这些模块的源码,进行分析。
模块分析
移动模块
移动模块的作用是,在移动边缘计算场景下,边缘用户是处于移动的状态的,所以,需要对他们位置信息的变化进行模拟。在作者的论文中,提到了使用游牧流动模型,对移动用户的运动轨迹进行模拟,在模型中,存在三种具有不同吸引力级别的位置类型。如果该位置的吸引力高,则该用户可能会在该位置花费更多的时间。例如,学生们在图书馆或咖啡馆里花费的时间比在售货亭上花费的时间多。
代码量不多,就全部贴上来。
抽象类:
public abstract class MobilityModel {
protected int numberOfMobileDevices;
protected double simulationTime;
public MobilityModel(int _numberOfMobileDevices, double _simulationTime){
numberOfMobileDevices=_numberOfMobileDevices;
simulationTime=_simulationTime;
};
// Default Constructor: Creates an empty MobilityModel
public MobilityModel() {}
// calculate location of the devices according to related mobility model
public abstract void initialize();
// returns location of a device at a certain time
public abstract Location getLocation(int deviceId, double time);
}
实现类:
public class NomadicMobility extends MobilityModel {
private List<TreeMap<Double, Location>> treeMapArray;
public NomadicMobility(int _numberOfMobileDevices, double _simulationTime) {
super(_numberOfMobileDevices, _simulationTime);
// TODO Auto-generated constructor stub
}
@Override
public void initialize() {
treeMapArray = new ArrayList<TreeMap<Double, Location>>();
ExponentialDistribution[] expRngList = new ExponentialDistribution[SimSettings.getInstance().getNumOfEdgeDatacenters()];
//create random number generator for each place
Document doc = SimSettings.getInstance().getEdgeDevicesDocument();
NodeList datacenterList = doc.getElementsByTagName("datacenter");
for (int i = 0; i < datacenterList.getLength(); i++) {
Node datacenterNode = datacenterList.item(i);
Element datacenterElement = (Element) datacenterNode;
Element location = (Element)datacenterElement.getElementsByTagName("location").item(0);
String attractiveness = location.getElementsByTagName("attractiveness").item(0).getTextContent();
int placeTypeIndex = Integer.parseInt(attractiveness);
expRngList[i] = new ExponentialDistribution(SimSettings.getInstance().getMobilityLookUpTable()[placeTypeIndex]);
}
//initialize tree maps and position of mobile devices
for(int i=0; i<numberOfMobileDevices; i++) {
treeMapArray.add(i, new TreeMap<Double, Location>());
int randDatacenterId = SimUtils.getRandomNumber(0, SimSettings.getInstance().getNumOfEdgeDatacenters()-1);
Node datacenterNode = datacenterList.item(randDatacenterId);
Element datacenterElement = (Element) datacenterNode;
Element location = (Element)datacenterElement.getElementsByTagName("location").item(0);
String attractiveness = location.getElementsByTagName("attractiveness").item(0).getTextContent();
int placeTypeIndex = Integer.parseInt(attractiveness);
int wlan_id = Integer.parseInt(location.getElementsByTagName("wlan_id").item(0).getTextContent());
int x_pos = Integer.parseInt(location.getElementsByTagName("x_pos").item(0).getTextContent());
int y_pos = Integer.parseInt(location.getElementsByTagName("y_pos").item(0).getTextContent());
//start locating user shortly after the simulation started (e.g. 10 seconds)
treeMapArray.get(i).put(SimSettings.CLIENT_ACTIVITY_START_TIME, new Location(placeTypeIndex, wlan_id, x_pos, y_pos));
}
for(int i=0; i<numberOfMobileDevices; i++) {
TreeMap<Double, Location> treeMap = treeMapArray.get(i);
while(treeMap.lastKey() < SimSettings.getInstance().getSimulationTime()) {
boolean placeFound = false;
int currentLocationId = treeMap.lastEntry().getValue().getServingWlanId();
double waitingTime = expRngList[currentLocationId].sample();
while(placeFound == false){
int newDatacenterId = SimUtils.getRandomNumber(0,SimSettings.getInstance().getNumOfEdgeDatacenters()-1);
if(newDatacenterId != currentLocationId){
placeFound = true;
Node datacenterNode = datacenterList.item(newDatacenterId);
Element datacenterElement = (Element) datacenterNode;
Element location = (Element)datacenterElement.getElementsByTagName("location").item(0);
String attractiveness = location.getElementsByTagName("attractiveness").item(0).getTextContent();
int placeTypeIndex = Integer.parseInt(attractiveness);
int wlan_id = Integer.parseInt(location.getElementsByTagName("wlan_id").item(0).getTextContent());
int x_pos = Integer.parseInt(location.getElementsByTagName("x_pos").item(0).getTextContent());
int y_pos = Integer.parseInt(location.getElementsByTagName("y_pos").item(0).getTextContent());
treeMap.put(treeMap.lastKey()+waitingTime, new Location(placeTypeIndex, wlan_id, x_pos, y_pos));
}
}
if(!placeFound){
SimLogger.printLine("impossible is occurred! location cannot be assigned to the device!");
System.exit(1);
}
}
}
}
@Override
public Location getLocation(int deviceId, double time) {
// 获取对应设备的路径表
TreeMap<Double, Location> treeMap = treeMapArray.get(deviceId);
// 从treeMap中获取当前时间向下取最近的时间戳的位置信息
Entry<Double, Location> e = treeMap.floorEntry(time);
if(e == null){
SimLogger.printLine("impossible is occurred! no location is found for the device '" + deviceId + "' at " + time);
System.exit(1);
}
return e.getValue();
}
当我们想要实现自己的对边缘用户的移动策略,继承MobilityModel这个抽象类就好了。它主要包含了三个功能函数:构造函数、初始化函数、位置获取函数。
那它是如何实现,在初始化之后,通过调用getLocation就能获取某一个设备在指定时间的位置的呢?
我对源码的理解是,它在初始化的时候,就随机生成了许多初始点坐标,然后将这些点的所有时间点的位置存在了一张TreeMap中,然后在仿真运行的时候,只需要从这个映射中获取对应时间的位置即可。
网络模块
对于边缘计算,网络的仿真非常重要,因为对于任务卸载,网络因素对其效率产生直接的作用。而EdgeCouldSIm对于移动端的网络仿真时如何实现的呢?
源码也不多,就直接贴上来。
抽象类:
public abstract class NetworkModel {
protected int numberOfMobileDevices;
protected String simScenario;
public NetworkModel(int _numberOfMobileDevices, String _simScenario){
numberOfMobileDevices=_numberOfMobileDevices;
simScenario = _simScenario;
};
// 初始化网络模块
public abstract void initialize();
// 获取两个设备之间的对某个任务的上传延迟
public abstract double getUploadDelay(int sourceDeviceId, int destDeviceId, Task task);
// 获取两个设备之间对某个任务的下载延迟
public abstract double getDownloadDelay(int sourceDeviceId, int destDeviceId, Task task);
// 可能会用到的方法,用于设备告知网络管理器相关网络的操作
public abstract void uploadStarted(Location accessPointLocation, int destDeviceId);
public abstract void uploadFinished(Location accessPointLocation, int destDeviceId);
public abstract void downloadStarted(Location accessPointLocation, int sourceDeviceId);
public abstract void downloadFinished(Location accessPointLocation, int sourceDeviceId);
}
实现类:
public class MM1Queue extends NetworkModel {
// WLAN的平均泊松分布
private double WlanPoissonMean; //seconds
private double WanPoissonMean; //seconds
// 平均输入任务大小
private double avgTaskInputSize; //bytes
private double avgTaskOutputSize; //bytes
private int maxNumOfClientsInPlace;
public MM1Queue(int _numberOfMobileDevices, String _simScenario) {
super(_numberOfMobileDevices, _simScenario);
}
@Override
public void initialize() {
WlanPoissonMean=0;
WanPoissonMean=0;
avgTaskInputSize=0;
avgTaskOutputSize=0;
maxNumOfClientsInPlace=0;
// 计算网络的泊松分布和任务大小
double numOfTaskType = 0;
SimSettings SS = SimSettings.getInstance();
for (int i=0; i<SimSettings.getInstance().getTaskLookUpTable().length; i++) {
double weight = SS.getTaskLookUpTable()[i][0]/(double)100;
if(weight != 0) {
WlanPoissonMean += (SS.getTaskLookUpTable()[i][2])*weight;
double percentageOfCloudCommunication = SS.getTaskLookUpTable()[i][1];
WanPoissonMean += (WlanPoissonMean)*((double)100/percentageOfCloudCommunication)*weight;
avgTaskInputSize += SS.getTaskLookUpTable()[i][5]*weight;
avgTaskOutputSize += SS.getTaskLookUpTable()[i][6]*weight;
numOfTaskType++;
}
}
WlanPoissonMean = WlanPoissonMean/numOfTaskType;
avgTaskInputSize = avgTaskInputSize/numOfTaskType;
avgTaskOutputSize = avgTaskOutputSize/numOfTaskType;
}
@Override
public double getUploadDelay(int sourceDeviceId, int destDeviceId, Task task) {
double delay = 0;
Location accessPointLocation = SimManager.getInstance().getMobilityModel().getLocation(sourceDeviceId,CloudSim.clock());
// 判断不同目标设备的ID从而计算上传的延迟
if(destDeviceId == SimSettings.CLOUD_DATACENTER_ID){
double wlanDelay = getWlanUploadDelay(accessPointLocation, CloudSim.clock());
double wanDelay = getWanUploadDelay(accessPointLocation, CloudSim.clock() + wlanDelay);
if(wlanDelay > 0 && wanDelay >0)
delay = wlanDelay + wanDelay;
}
//mobile device to edge orchestrator
else if(destDeviceId == SimSettings.EDGE_ORCHESTRATOR_ID){
delay = getWlanUploadDelay(accessPointLocation, CloudSim.clock()) +
SimSettings.getInstance().getInternalLanDelay();
}
//mobile device to edge device (wifi access point)
else if (destDeviceId == SimSettings.GENERIC_EDGE_DEVICE_ID) {
delay = getWlanUploadDelay(accessPointLocation, CloudSim.clock());
}
return delay;
}
@Override
public double getDownloadDelay(int sourceDeviceId, int destDeviceId, Task task) {
//Special Case -> edge orchestrator to edge device
if(sourceDeviceId == SimSettings.EDGE_ORCHESTRATOR_ID &&
destDeviceId == SimSettings.GENERIC_EDGE_DEVICE_ID){
return SimSettings.getInstance().getInternalLanDelay();
}
double delay = 0;
Location accessPointLocation = SimManager.getInstance().getMobilityModel().getLocation(destDeviceId,CloudSim.clock());
//cloud server to mobile device
if(sourceDeviceId == SimSettings.CLOUD_DATACENTER_ID){
double wlanDelay = getWlanDownloadDelay(accessPointLocation, CloudSim.clock());
double wanDelay = getWanDownloadDelay(accessPointLocation, CloudSim.clock() + wlanDelay);
if(wlanDelay > 0 && wanDelay >0)
delay = wlanDelay + wanDelay;
}
//edge device (wifi access point) to mobile device
else{
delay = getWlanDownloadDelay(accessPointLocation, CloudSim.clock());
EdgeHost host = (EdgeHost)(SimManager.
getInstance().
getEdgeServerManager().
getDatacenterList().get(sourceDeviceId).
getHostList().get(0));
//if source device id is the edge server which is located in another location, add internal lan delay
//in our scenario, serving wlan ID is equal to the host id, because there is only one host in one place
if(host.getLocation().getServingWlanId() != accessPointLocation.getServingWlanId())
delay += (SimSettings.getInstance().getInternalLanDelay() * 2);
}
return delay;
}
// 返回区域内最大移动设备数
public int getMaxNumOfClientsInPlace(){
return maxNumOfClientsInPlace;
}
// 计算区域内最大移动设备数
private int getDeviceCount(Location deviceLocation, double time){
int deviceCount = 0;
for(int i=0; i<numberOfMobileDevices; i++) {
Location location = SimManager.getInstance().getMobilityModel().getLocation(i,time);
if(location.equals(deviceLocation))
deviceCount++;
}
//record max number of client just for debugging
if(maxNumOfClientsInPlace<deviceCount)
maxNumOfClientsInPlace = deviceCount;
return deviceCount;
}
// 核心数据传输延迟计算函数
private double calculateMM1(double propagationDelay, int bandwidth /*Kbps*/, double PoissonMean, double avgTaskSize /*KB*/, int deviceCount){
double Bps=0, mu=0, lamda=0;
avgTaskSize = avgTaskSize * (double)1000; //convert from KB to Byte
Bps = bandwidth * (double)1000 / (double)8; //convert from Kbps to Byte per seconds
lamda = ((double)1/(double)PoissonMean); //task per seconds
mu = Bps / avgTaskSize ; //task per seconds
double result = (double)1 / (mu-lamda*(double)deviceCount);
result += propagationDelay;
return (result > 5) ? -1 : result;
}
private double getWlanDownloadDelay(Location accessPointLocation, double time) {
return calculateMM1(0,
SimSettings.getInstance().getWlanBandwidth(),
WlanPoissonMean,
avgTaskOutputSize,
getDeviceCount(accessPointLocation, time));
}
private double getWlanUploadDelay(Location accessPointLocation, double time) {
return calculateMM1(0,
SimSettings.getInstance().getWlanBandwidth(),
WlanPoissonMean,
avgTaskInputSize,
getDeviceCount(accessPointLocation, time));
}
private double getWanDownloadDelay(Location accessPointLocation, double time) {
return calculateMM1(SimSettings.getInstance().getWanPropagationDelay(),
SimSettings.getInstance().getWanBandwidth(),
WanPoissonMean,
avgTaskOutputSize,
getDeviceCount(accessPointLocation, time));
}
private double getWanUploadDelay(Location accessPointLocation, double time) {
return calculateMM1(SimSettings.getInstance().getWanPropagationDelay(),
SimSettings.getInstance().getWanBandwidth(),
WanPoissonMean,
avgTaskInputSize,
getDeviceCount(accessPointLocation, time));
}
}
观察抽象类,我们可以发现,网络模块需要实现功能主要是获取任务卸载时上传和下载的网络延迟。
网络模块的默认实现基于单个服务器队列模型。就是基于数学的方法,用带宽和任务等因素,得出数据传输所需的耗时,核心代码就在calculateMM1函数中。
看了网络模块的源码实现,其实还是有点失望的,只是通过简单的数学计算获取延迟,如果能接入专业的网络仿真器,例如ns3,就好了。
负载生成模块
负载生成器模块负责为给定配置生成任务。负载生成器模块和移动模块是向其他组件提供输入的主要组件。默认情况下,根据泊松分布生成任务,并且移动设备根据移动性移动模型移动。
抽象类:
public abstract class LoadGeneratorModel {
protected List<TaskProperty> taskList;
protected int numberOfMobileDevices;
protected double simulationTime;
protected String simScenario;
// 构造函数,需要填入移动设备的数量,仿真时间,仿真场景名字
public LoadGeneratorModel(int _numberOfMobileDevices, double _simulationTime, String _simScenario){
numberOfMobileDevices=_numberOfMobileDevices;
simulationTime=_simulationTime;
simScenario=_simScenario;
};
public LoadGeneratorModel() {}
// 获取任务列表
public List<TaskProperty> getTaskList() {
return taskList;
}
// 初始化任务列表
public abstract void initializeModel();
// 获取移动设备上的任务种类
public abstract int getTaskTypeOfDevice(int deviceId);
}
实现类:
public class IdleActiveLoadGenerator extends LoadGeneratorModel{
// 存储每个移动设备的任务类别
int taskTypeOfDevices[];
public IdleActiveLoadGenerator(int _numberOfMobileDevices, double _simulationTime, String _simScenario) {
super(_numberOfMobileDevices, _simulationTime, _simScenario);
}
@Override
// 初始化任务列表
public void initializeModel() {
taskList = new ArrayList<TaskProperty>();
//exponential number generator for file input size, file output size and task length
ExponentialDistribution[][] expRngList = new ExponentialDistribution[SimSettings.getInstance().getTaskLookUpTable().length][3];
//create random number generator for each place
for(int i=0; i<SimSettings.getInstance().getTaskLookUpTable().length; i++) {
if(SimSettings.getInstance().getTaskLookUpTable()[i][0] ==0)
continue;
expRngList[i][0] = new ExponentialDistribution(SimSettings.getInstance().getTaskLookUpTable()[i][5]);
expRngList[i][1] = new ExponentialDistribution(SimSettings.getInstance().getTaskLookUpTable()[i][6]);
expRngList[i][2] = new ExponentialDistribution(SimSettings.getInstance().getTaskLookUpTable()[i][7]);
}
// 给每个设备都随机分配一个种任务
taskTypeOfDevices = new int[numberOfMobileDevices];
for(int i=0; i<numberOfMobileDevices; i++) {
int randomTaskType = -1;
double taskTypeSelector = SimUtils.getRandomDoubleNumber(0,100);
double taskTypePercentage = 0;
for (int j=0; j<SimSettings.getInstance().getTaskLookUpTable().length; j++) {
taskTypePercentage += SimSettings.getInstance().getTaskLookUpTable()[j][0];
if(taskTypeSelector <= taskTypePercentage){
randomTaskType = j;
break;
}
}
if(randomTaskType == -1){
SimLogger.printLine("Impossible is occurred! no random task type!");
continue;
}
taskTypeOfDevices[i] = randomTaskType;
double poissonMean = SimSettings.getInstance().getTaskLookUpTable()[randomTaskType][2];
double activePeriod = SimSettings.getInstance().getTaskLookUpTable()[randomTaskType][3];
double idlePeriod = SimSettings.getInstance().getTaskLookUpTable()[randomTaskType][4];
double activePeriodStartTime = SimUtils.getRandomDoubleNumber(
SimSettings.CLIENT_ACTIVITY_START_TIME,
SimSettings.CLIENT_ACTIVITY_START_TIME + activePeriod); //active period starts shortly after the simulation started (e.g. 10 seconds)
double virtualTime = activePeriodStartTime;
ExponentialDistribution rng = new ExponentialDistribution(poissonMean);
while(virtualTime < simulationTime) {
double interval = rng.sample();
if(interval <= 0){
SimLogger.printLine("Impossible is occurred! interval is " + interval + " for device " + i + " time " + virtualTime);
continue;
}
//SimLogger.printLine(virtualTime + " -> " + interval + " for device " + i + " time ");
virtualTime += interval;
if(virtualTime > activePeriodStartTime + activePeriod){
activePeriodStartTime = activePeriodStartTime + activePeriod + idlePeriod;
virtualTime = activePeriodStartTime;
continue;
}
taskList.add(new TaskProperty(i,randomTaskType, virtualTime, expRngList));
}
}
}
@Override
public int getTaskTypeOfDevice(int deviceId) {
// 从设备任务列中获取对应任务种类
return taskTypeOfDevices[deviceId];
}
}
负载生成模块,主要的实现逻辑就是在仿真开始,给所有移动设备随机分配一种任务,这样,在仿真过程中,边缘端就可以获取需要计算的任务。
边缘协调器模块
该模块是系统的决策者,它使用从其他模块收集的信息来决定如何以及在何处处理传入的客户端请求。
这个模块还是对我们最重要的,我们搞学术的,当然需要弄点新的算法什么,而不是仅仅修改一下仿真规模,看看效果而已,所以,我们需要将自己的边缘卸载算法切入仿真系统当中,而切入点,就是在这里了。
抽象类:
public abstract class EdgeOrchestrator extends SimEntity{
protected String policy;
protected String simScenario;
// 构造器,需要传入卸载策略和场景名称
public EdgeOrchestrator(String _policy, String _simScenario){
super("EdgeOrchestrator");
policy = _policy;
simScenario = _simScenario;
}
public EdgeOrchestrator() {
super("EdgeOrchestrator");
}
public abstract void initialize();
// 获取任务的设备id
public abstract int getDeviceToOffload(Task task);
// 获取卸载的边缘服务器虚拟机
public abstract Vm getVmToOffload(Task task, int deviceId);
}
实现类:
public class BasicEdgeOrchestrator extends EdgeOrchestrator {
private int numberOfHost; //used by load balancer
private int lastSelectedHostIndex; //used by load balancer
private int[] lastSelectedVmIndexes; //used by each host individually
public BasicEdgeOrchestrator(String _policy, String _simScenario) {
super(_policy, _simScenario);
}
@Override
public void initialize() {
numberOfHost=SimSettings.getInstance().getNumOfEdgeHosts();
lastSelectedHostIndex = -1;
lastSelectedVmIndexes = new int[numberOfHost];
for(int i=0; i<numberOfHost; i++)
lastSelectedVmIndexes[i] = -1;
}
// 获取设备应该被卸载的目的地
@Override
public int getDeviceToOffload(Task task) {
int result = SimSettings.GENERIC_EDGE_DEVICE_ID;
if(!simScenario.equals("SINGLE_TIER")){
// 如果不是单层的仿真,那么就要判断是否要卸载到云端
int CloudVmPicker = SimUtils.getRandomNumber(0, 100);
if(CloudVmPicker <= SimSettings.getInstance().getTaskLookUpTable()[task.getTaskType()][1])
result = SimSettings.CLOUD_DATACENTER_ID;
else
result = SimSettings.GENERIC_EDGE_DEVICE_ID;
}
return result;
}
@Override
// 判断需要被卸载到哪个服务器虚拟机上
public Vm getVmToOffload(Task task, int deviceId) {
Vm selectedVM = null;
if(deviceId == SimSettings.CLOUD_DATACENTER_ID){
// 通过最小加载算法在云设备上选择虚拟机
double selectedVmCapacity = 0; //start with min value
List<Host> list = SimManager.getInstance().getCloudServerManager().getDatacenter().getHostList();
for (int hostIndex=0; hostIndex < list.size(); hostIndex++) {
List<CloudVM> vmArray = SimManager.getInstance().getCloudServerManager().getVmList(hostIndex);
for(int vmIndex=0; vmIndex<vmArray.size(); vmIndex++){
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(vmIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(vmIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity && targetVmCapacity > selectedVmCapacity){
selectedVM = vmArray.get(vmIndex);
selectedVmCapacity = targetVmCapacity;
}
}
}
}
else if(simScenario.equals("TWO_TIER_WITH_EO"))
// 两层场景需要边缘调度,那么需要进行边缘端的均衡负载
selectedVM = selectVmOnLoadBalancer(task);
else
// 两层场景,不需要边缘调度
selectedVM = selectVmOnHost(task);
return selectedVM;
}
// 两层场景,不需要边缘调度,获取任务的卸载目标虚拟机
public EdgeVM selectVmOnHost(Task task){
EdgeVM selectedVM = null;
// 获取产生任务的设备坐标
Location deviceLocation = SimManager.getInstance().getMobilityModel().getLocation(task.getMobileDeviceId(), CloudSim.clock());
// 获取坐标所在的网络
int relatedHostId=deviceLocation.getServingWlanId();
// 获取该网络中的虚拟机列表,仅仅只单一的覆盖范围内的网络
List<EdgeVM> vmArray = SimManager.getInstance().getEdgeServerManager().getVmList(relatedHostId);
// 根据不同卸策略获取虚拟机
if(policy.equalsIgnoreCase("RANDOM_FIT")){
int randomIndex = SimUtils.getRandomNumber(0, vmArray.size()-1);
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(randomIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(randomIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity)
selectedVM = vmArray.get(randomIndex);
}
else if(policy.equalsIgnoreCase("WORST_FIT")){
double selectedVmCapacity = 0; //start with min value
for(int vmIndex=0; vmIndex<vmArray.size(); vmIndex++){
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(vmIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(vmIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity && targetVmCapacity > selectedVmCapacity){
selectedVM = vmArray.get(vmIndex);
selectedVmCapacity = targetVmCapacity;
}
}
}
else if(policy.equalsIgnoreCase("BEST_FIT")){
double selectedVmCapacity = 101; //start with max value
for(int vmIndex=0; vmIndex<vmArray.size(); vmIndex++){
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(vmIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(vmIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity && targetVmCapacity < selectedVmCapacity){
selectedVM = vmArray.get(vmIndex);
selectedVmCapacity = targetVmCapacity;
}
}
}
else if(policy.equalsIgnoreCase("FIRST_FIT")){
for(int vmIndex=0; vmIndex<vmArray.size(); vmIndex++){
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(vmIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(vmIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity){
selectedVM = vmArray.get(vmIndex);
break;
}
}
}
else if(policy.equalsIgnoreCase("NEXT_FIT")){
int tries = 0;
while(tries < vmArray.size()){
lastSelectedVmIndexes[relatedHostId] = (lastSelectedVmIndexes[relatedHostId]+1) % vmArray.size();
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(lastSelectedVmIndexes[relatedHostId]).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(lastSelectedVmIndexes[relatedHostId]).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity){
selectedVM = vmArray.get(lastSelectedVmIndexes[relatedHostId]);
break;
}
tries++;
}
}
return selectedVM;
}
// 通过边缘调度器来选择卸载目标服务器
public EdgeVM selectVmOnLoadBalancer(Task task){
EdgeVM selectedVM = null;
if(policy.equalsIgnoreCase("RANDOM_FIT")){
int randomHostIndex = SimUtils.getRandomNumber(0, numberOfHost-1);
List<EdgeVM> vmArray = SimManager.getInstance().getEdgeServerManager().getVmList(randomHostIndex);
int randomIndex = SimUtils.getRandomNumber(0, vmArray.size()-1);
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(randomIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(randomIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity)
selectedVM = vmArray.get(randomIndex);
}
else if(policy.equalsIgnoreCase("WORST_FIT")){
double selectedVmCapacity = 0; //start with min value
for(int hostIndex=0; hostIndex<numberOfHost; hostIndex++){
List<EdgeVM> vmArray = SimManager.getInstance().getEdgeServerManager().getVmList(hostIndex);
for(int vmIndex=0; vmIndex<vmArray.size(); vmIndex++){
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(vmIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(vmIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity && targetVmCapacity > selectedVmCapacity){
selectedVM = vmArray.get(vmIndex);
selectedVmCapacity = targetVmCapacity;
}
}
}
}
else if(policy.equalsIgnoreCase("BEST_FIT")){
double selectedVmCapacity = 101; //start with max value
for(int hostIndex=0; hostIndex<numberOfHost; hostIndex++){
List<EdgeVM> vmArray = SimManager.getInstance().getEdgeServerManager().getVmList(hostIndex);
for(int vmIndex=0; vmIndex<vmArray.size(); vmIndex++){
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(vmIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(vmIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity && targetVmCapacity < selectedVmCapacity){
selectedVM = vmArray.get(vmIndex);
selectedVmCapacity = targetVmCapacity;
}
}
}
}
else if(policy.equalsIgnoreCase("FIRST_FIT")){
for(int hostIndex=0; hostIndex<numberOfHost; hostIndex++){
List<EdgeVM> vmArray = SimManager.getInstance().getEdgeServerManager().getVmList(hostIndex);
for(int vmIndex=0; vmIndex<vmArray.size(); vmIndex++){
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(vmIndex).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(vmIndex).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity){
selectedVM = vmArray.get(vmIndex);
break;
}
}
}
}
else if(policy.equalsIgnoreCase("NEXT_FIT")){
int hostCheckCounter = 0;
while(selectedVM == null && hostCheckCounter < numberOfHost){
int tries = 0;
lastSelectedHostIndex = (lastSelectedHostIndex+1) % numberOfHost;
List<EdgeVM> vmArray = SimManager.getInstance().getEdgeServerManager().getVmList(lastSelectedHostIndex);
while(tries < vmArray.size()){
lastSelectedVmIndexes[lastSelectedHostIndex] = (lastSelectedVmIndexes[lastSelectedHostIndex]+1) % vmArray.size();
double requiredCapacity = ((CpuUtilizationModel_Custom)task.getUtilizationModelCpu()).predictUtilization(vmArray.get(lastSelectedVmIndexes[lastSelectedHostIndex]).getVmType());
double targetVmCapacity = (double)100 - vmArray.get(lastSelectedVmIndexes[lastSelectedHostIndex]).getCloudletScheduler().getTotalUtilizationOfCpu(CloudSim.clock());
if(requiredCapacity <= targetVmCapacity){
selectedVM = vmArray.get(lastSelectedVmIndexes[lastSelectedHostIndex]);
break;
}
tries++;
}
hostCheckCounter++;
}
}
return selectedVM;
}
}
从源码中可以看出,工具包将边缘卸载场景分为了三种情况,单层的卸载,双层和附加了调度器双层结构,这三者有什么区别呢?如果是单层的场景,那么任务只会在单一覆盖范围的边缘服务器进行调度,两层架构中,移动设备可以使用连接的接入点提供的WAN连接将其任务发送到全局云。具有EO架构的两层结构具有相当大的优势,因为对于在第一层执行的任务,只有具有EO架构的两层结构才能将任务卸载到位于不同建筑物中的任何边缘服务器。
在源码中,已经写了几种卸载策略,分别是RANDOM_FIT、NEXT_FIT、WORST_FIT、BEST_FIT,当我们想要写入自己的卸载算法时,只需要重写这个方法,或者直接添加匹配算法即可,然后再外部配置文件中,配置卸载算法的名称,就能进行匹配。
具有EO架构的两层结构在源码中是如何体现的呢?也就是说,如何将任务卸载到不同范围内的服务器上的?
可以看出,如果是没有EO,那么,调度时服务器虚拟机列表仅仅是从任务所在移动设备的位置区域内的服务器,而如果加上EO,那么选取的服务器列表就会从其他的网络范围中获取,例如NEXT_FIT策略下,就会从下一个网络中获取,查看是否合适,不适合就混继续向下轮询,直到发现合适的网络的服务器。所以,选取的网络是卸载算法较位核心的输出。
核心仿真模块
该模块作为EdgeCloudSim全局的管理器,用于实例化其他模块,负责从配置文件加载和运行Edge Computing方案。此外,它提供了一种日志记录机制,可以将模拟结果保存到文件中。默认情况下,结果以逗号分隔值(CSV)数据格式保存,但是可以将其更改为任何格式。
看看接口类就明白了:
public interface ScenarioFactory {
/**
* provides abstract Load Generator Model
*/
public LoadGeneratorModel getLoadGeneratorModel();
/**
* provides abstract Edge Orchestrator
*/
public EdgeOrchestrator getEdgeOrchestrator();
/**
* provides abstract Mobility Model
*/
public MobilityModel getMobilityModel();
/**
* provides abstract Network Model
*/
public NetworkModel getNetworkModel();
/**
* provides abstract Edge Server Model
*/
public EdgeServerManager getEdgeServerManager();
/**
* provides abstract Cloud Server Model
*/
public CloudServerManager getCloudServerManager();
/**
* provides abstract Mobile Server Model
*/
public MobileServerManager getMobileServerManager();
/**
* provides abstract Mobile Device Manager Model
*/
public MobileDeviceManager getMobileDeviceManager() throws Exception;
}
总结
通过对EdgeCloudSim架构下的这几个主要模块源码阅读,大致了解了EdgeCloudSim的工作原理,也明确了下一步的工作内容。
对于移动模块,并没没有什么问题,只是简单对边缘设备的路径进行模拟生成。
对于网络模块,它只是对数据传输的延迟进行计算,没有对其他的很多指标进行模拟,例如什么丢包率,抖动啥的,还是有点简陋了。
对于负载生成模块,我们可以添加不同需求的任务种类。
对于边缘协调器模块,就是我们比较核心的工作内容了,边缘计算卸载算法就需要在其中完成。