三、Example1 和VmAllocationPolicySimple源码分析
在接着上一篇博客分析之前,我们先来了解一些可能会需要注意的地方。
MIPS是虚拟机的cpu处理速度。假设同一机器下的所有PE具有相同的MIPS评级。Pe(处理单元)表示CPU单元,即可以理解为CPU的核数,按照每秒百万指令(MIPS)评级定义。
mips是虚拟机的cpu处理速度,cloudlet的length / 虚拟机mips = 任务执行所需时间,所有虚拟机的mips之和不能超过datacenter中定义的主机的物理cpu的mips之和,而虚拟cpu的mips的最大值也不能超过物理cpu的最大值,否则虚拟机将创建失败。
CloudSimExample1
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import org.cloudbus.cloudsim.Cloudlet;
import org.cloudbus.cloudsim.CloudletSchedulerTimeShared;
import org.cloudbus.cloudsim.Datacenter;
import org.cloudbus.cloudsim.DatacenterBroker;
import org.cloudbus.cloudsim.DatacenterCharacteristics;
import org.cloudbus.cloudsim.Host;
import org.cloudbus.cloudsim.Log;
import org.cloudbus.cloudsim.Pe;
import org.cloudbus.cloudsim.Storage;
import org.cloudbus.cloudsim.UtilizationModel;
import org.cloudbus.cloudsim.UtilizationModelFull;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.VmAllocationPolicySimple;
import org.cloudbus.cloudsim.examples.VmAllocationPolicyFirstIn;
import org.cloudbus.cloudsim.examples.VmAllocationPolicy_BestFit;
import org.cloudbus.cloudsim.VmSchedulerTimeShared;
import org.cloudbus.cloudsim.core.CloudSim;
import org.cloudbus.cloudsim.provisioners.BwProvisionerSimple;
import org.cloudbus.cloudsim.provisioners.PeProvisionerSimple;
import org.cloudbus.cloudsim.provisioners.RamProvisionerSimple;
/**
* A simple example showing how to create a datacenter with one host and run one
* cloudlet on it.
*/
public class CloudSimExample1 {
/** The cloudlet list. */
private static List<Cloudlet> cloudletList;
/** The vmlist. */
private static List<Vm> vmlist;
int algorithm_flag=0;
static List<Host> hostList = new ArrayList<Host>();
/**
* Creates main() to run this example.
*
* @param args the args
*/
@SuppressWarnings("unused")
public static void main(String[] args) {
Log.printLine("Starting CloudSimExample1...");
try {
// First step: Initialize the CloudSim package. It should be called
// before creating any entities.
int num_user = 1; // number of cloud users
Calendar calendar = Calendar.getInstance();
boolean trace_flag = false; // mean trace events
// Initialize the CloudSim library 初始化CloudSim包
CloudSim.init(num_user, calendar, trace_flag);
// Second step: Create Datacenters
// Datacenters are the resource providers in CloudSim. We need at
// list one of them to run a CloudSim simulation
Datacenter datacenter0 = createDatacenter("Datacenter_0");
// Third step: Create Broker 数据中心代理
//DatacenterBroker模拟SaaS提供商代理,根据QoS的需求协商资源和服务的分配策略
DatacenterBroker broker = createBroker();
int brokerId = broker.getId();
// Fourth step: Create one virtual machine
vmlist = new ArrayList<Vm>();
// VM description
int vmid = 0;
int mips = 3000;
long size = 10000; // image size (MB)
int ram = 1024; // vm memory (MB)
long bw = 1000;
int pesNumber1 = 2; // number of cpus
int pesNumber2 = 3; // number of cpus
int pesNumber3 = 3; // number of cpus
int pesNumber4 = 1; // number of cpus
String vmm = "Xen"; // VMM name
//create two VMs
Vm vm1 = new Vm(vmid, brokerId, mips, pesNumber1, ram, bw, size, vmm, new CloudletSchedulerTimeShared());
vmid++;
Vm vm2 = new Vm(vmid, brokerId, mips, pesNumber2, ram, bw, size, vmm, new CloudletSchedulerTimeShared());
vmid++;
Vm vm3 = new Vm(vmid, brokerId, mips, pesNumber3, ram, bw, size, vmm, new CloudletSchedulerTimeShared());
vmid++;
Vm vm4 = new Vm(vmid, brokerId, mips, pesNumber4, ram, bw, size, vmm, new CloudletSchedulerTimeShared());
// add the VM to the vmList
vmlist.add(vm1);
vmlist.add(vm2);
vmlist.add(vm3);
vmlist.add(vm4);
// submit vm list to the broker 提交虚拟机列表
broker.submitVmList(vmlist);
// Fifth step: Create one Cloudlet 云服务
cloudletList = new ArrayList<Cloudlet>();
// Cloudlet properties
int id = 0;
long length = 400000;
long fileSize = 300;
long outputSize = 300;
int pesNumber = 1; // number of cpus
UtilizationModel utilizationModel = new UtilizationModelFull(); //类:根据时间返回百分比利用率。
Cloudlet cloudlet = new Cloudlet(id, length, pesNumber, fileSize, outputSize, utilizationModel, utilizationModel, utilizationModel);
cloudlet.setUserId(brokerId);
cloudlet.setVmId(vmid);
// add the cloudlet to the list
cloudletList.add(cloudlet);
// submit cloudlet list to the broker 向代理提交任务列表
broker.submitCloudletList(cloudletList);
// Sixth step: Starts the simulation(模拟)
CloudSim.startSimulation();
CloudSim.stopSimulation();
//Final step: Print results when simulation is over
List<Cloudlet> newList = broker.getCloudletReceivedList();
printCloudletList(newList);
Log.printLine("CloudSimExample1 finished!");
} catch (Exception e) {
e.printStackTrace();
Log.printLine("Unwanted errors happen");
}
}
/**
* Creates the datacenter.
*
* @param name the name
*
* @return the datacenter
*/
private static Datacenter createDatacenter(String name) {
// Here are the steps needed to create a PowerDatacenter:
// 1. We need to create a list to store our machine
// 2. A Machine contains one or more PEs or CPUs/Cores.
// In this example, it will have only one core.
List<Pe> peList = new ArrayList<Pe>();
int mips = 5000;
// 3. Create PEs and add these into a list.
//创建处理器,并添加到Pe列表中:为每个主机配置4核CPU 写入Pe id 和 cpu处理速度
peList.add(new Pe(0, new PeProvisionerSimple(mips))); // need to store Pe id and MIPS Rating
peList.add(new Pe(1, new PeProvisionerSimple(mips)));
peList.add(new Pe(2, new PeProvisionerSimple(mips)));
peList.add(new Pe(3, new PeProvisionerSimple(mips)));
// 4. Create Host with its id and list of PEs and add them to the list
// of machines 创建主机,并将其添加至主机列表
int hostId = 0;
int ram = 2048; // host memory (MB)
long storage = 1000000; // host storage
int bw = 10000;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is our machine
hostId++;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is second machine
hostId++;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is second machine
hostId++;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is second machine
//创建数据中心特征,它表示了数据中心的资源的静态属性,
//比如:体系结构,操作系统,主机列表,分配策略,时间或空间共享,时区,价格
String arch = "x86"; // system architecture
String os = "Linux"; // operating system
String vmm = "Xen";
double time_zone = 10.0; // time zone this resource located
double cost = 3.0; // the cost of using processing in this resource
double costPerMem = 0.05; // the cost of using memory in this resource
double costPerStorage = 0.001; // the cost of using storage in this
// resource
double costPerBw = 0.0; // the cost of using bw in this resource
LinkedList<Storage> storageList = new LinkedList<Storage>(); // we are not adding SAN
// devices by now
DatacenterCharacteristics characteristics = new DatacenterCharacteristics(
arch, os, vmm, hostList, time_zone, cost, costPerMem,
costPerStorage, costPerBw);
// 6. Finally, we need to create a PowerDatacenter object.
Datacenter datacenter = null;
try {
//默认算法:VmAllocationPolicySimple 选择最大剩余PEs的主机分配
//首次适应算法:VmAllocationPolicyFirstIn 选择最先适配的主机分配
datacenter = new Datacenter(name, characteristics, new VmAllocationPolicySimple(hostList), storageList, 0);
} catch (Exception e) {
e.printStackTrace();
}
return datacenter;
}
// We strongly encourage users to develop their own broker policies, to
// submit vms and cloudlets according
// to the specific rules of the simulated scenario
/**
* Creates the broker.
*
* @return the datacenter broker
*/
private static DatacenterBroker createBroker() {
DatacenterBroker broker = null;
try {
broker = new DatacenterBroker("Broker");
} catch (Exception e) {
e.printStackTrace();
return null;
}
return broker;
}
/**
* Prints the Cloudlet objects.
*
* @param list list of Cloudlets
*/
private static void printCloudletList(List<Cloudlet> list) {
int size = list.size();
Cloudlet cloudlet;
String indent = " ";
Log.printLine();
Log.printLine("========== OUTPUT ==========");
Log.printLine("Cloudlet ID" + indent + "STATUS" + indent
+ "Data center ID" + indent + "VM ID" + indent + "Time" + indent
+ "Start Time" + indent + "Finish Time");
DecimalFormat dft = new DecimalFormat("###.##");
for (int i = 0; i < size; i++) {
cloudlet = list.get(i);
Log.print(indent + cloudlet.getCloudletId() + indent + indent);
if (cloudlet.getCloudletStatus() == Cloudlet.SUCCESS) {
Log.print("SUCCESS");
Log.printLine(indent + indent + cloudlet.getResourceId()
+ indent + indent + indent + cloudlet.getVmId()
+ indent + indent
+ dft.format(cloudlet.getActualCPUTime()) + indent
+ indent + dft.format(cloudlet.getExecStartTime())
+ indent + indent
+ dft.format(cloudlet.getFinishTime()));
}
}
}
}
开始仿真模拟时,首先需要创建一个数据中心,然后再数据中心中创建CPU、内存等资源,此时只需要向代理中心注册资源信息,用户就可以使用数据中心的资源进行仿真模拟。在仿真资源分配试验中,其步骤及其各个步骤中的代码如下:
(1) 初始化Cloudsim包,代码如下:
Int num_user= 1 ; //定义用户数量
Calendar calendar=Calendar.getInstance();
boolean trace_flag=false;
CloudSim.init(num_user, calendar, trace_flag); //初始化CloudSim包
(2)创建数据中心(Datacenter),如果多次创建那就生成多个数据中心,代码如下所示:
DataCenter datacenter()=createDatacenter("Datacenter_0");
(3) 创建数据中心代理(Broker),代码如下所示。注:任务到虚拟机的映射是由DatacenterBroker类中的bindCloudletsToVms()函数实现。该函数根据不同的策略来实现任务的映射。
DatacenterBroker broker=createBroker();
Int brokerId=broker.get_id();
(4) 创建虚拟机,只有提交了虚拟机列表,broker才会发挥作用。代码如下所示:
vmlist=new VirtualMachineList(); //创建虚拟机列表
Vmvm=new Vm(vmid, brokerld, mips, PesNumber, ram, bw, size,
vmm,new CloudletSchedulerTimeShared()); //创建虚拟机
vmlist.add(vm); //加入虚拟机列表
broker.submitVMList(vmlist);//提交虚拟机列表
(5) 创建云任务,代码如下所示:
cloudletList = new CloudletList();//创建云任务列表
Cloudlet cloudlet=new Cloudlet(id, length, file_size, output_size);
cloudlet.setUserlD(brokerld);
……
cloudletList.add(cloudlet); //将任务加入任务列表
……
broker.submitCloudletList(cloudletList);//向代理提交任务列表
(6) 执行资源调度算法,完成任务到虚拟机的映射,代码如下所示:
broker. bindCloudletsToVms();
(7) 启动仿真程序,代码如下所示:
CloudSim.startSimulation();
(8) 打印仿真结果,代码如下所示:
List<Cloudlet> newList = broker.getCloudletReceivedList();
CloudSim.stopSimulation();
printCloudletList(newList);
在createDatacenter函数中,我设定了4个主机,为每个主机配置4核CPU。首先写入PEs 的id 和 cpu处理速度。
List<Pe> peList = new ArrayList<Pe>();
int mips = 5000;
//为每个主机配置4核CPU 写入Pe id 和 cpu处理速度
peList.add(new Pe(0, new PeProvisionerSimple(mips)));
peList.add(new Pe(1, new PeProvisionerSimple(mips)));
peList.add(new Pe(2, new PeProvisionerSimple(mips)));
peList.add(new Pe(3, new PeProvisionerSimple(mips)));
随后,创建四个主机,写入id和PEs列表到主机中。
int hostId = 0;
int ram = 2048; // host memory (MB)
long storage = 1000000; // host storage
int bw = 10000;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is frist machine
hostId++;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is second machine
hostId++;
...
创建数据中心的的仓库和属性,这部分的源码我没有修改,具体参见源代码。
VmAllocationPolicySimple
package org.cloudbus.cloudsim.examples;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.cloudbus.cloudsim.Host;
import org.cloudbus.cloudsim.Log;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.VmAllocationPolicy;
import org.cloudbus.cloudsim.core.CloudSim;
/**
* VmAllocationPolicySimple is an VmAllocationPolicy that chooses, as the host for a VM, the host
* with less PEs in use.
*/
//VmAllocationPolicy 抽象类 代表了数据中心主机到虚拟机的供应协议
public class VmAllocationPolicyFirstIn extends VmAllocationPolicy {
/** The vm table.记录虚拟机被分配到哪台主机 */
private Map<String, Host> vmTable;
/** The used pes.记录虚拟机占用了几个处理器核心 */
private Map<String, Integer> usedPes;
/** The free pes.记录每台主机可用的处理器核心数 */
private List<Integer> freePes;
/**
* Creates the new VmAllocationPolicySimple object.
* @param list the list
* @pre $none
* @post $none
*/
public VmAllocationPolicyFirstIn(List<? extends Host> list) {
//初始化主机列表hostList(继承自父类的成员)
super(list);
//初始化每台主机可用的处理器核心数freePes
setFreePes(new ArrayList<Integer>());
for (Host host : getHostList()) {
getFreePes().add(host.getNumberOfPes());
}
//初始化vmTable和usedPes
setVmTable(new HashMap<String, Host>());
setUsedPes(new HashMap<String, Integer>());
}
/**
* Allocates a host for a given VM.
* @param vm VM specification
* @return $true if the host could be allocated; $false otherwise
* @pre $none
* @post $none
*/
@Override
public boolean allocateHostForVm(Vm vm) {
int requiredPes = vm.getNumberOfPes();//创建vm所需的处理器核心数
boolean result = false;
int tries = 0; //尝试次数
List<Integer> freePesTmp = new ArrayList<Integer>();
for (Integer freePes : getFreePes()) {
freePesTmp.add(freePes);
}
//如果当前虚拟机还未创建
if (!getVmTable().containsKey(vm.getUid())) { // if this vm was not created
do {// we still trying until we find a host or until we try all of them
//尝试创建虚拟机直到创建成功或所有的主机都已经尝试过
int moreFree = Integer.MIN_VALUE;//当前最大可用核心数
int idx = -1; //当前最大可用核心数对应主机的下标
//默认算法;找到可用处理器核心数最大的第一台主机
//TestData:VM所需核数分别为 2 3 3 1
//正确结果: 剩余资源:2 1 1 3 主机分配ID:0 1 2 3
for (int i = 0; i < freePesTmp.size(); i++) {
if (freePesTmp.get(i) > moreFree) {
moreFree = freePesTmp.get(i);
idx = i;
}
}
Host host = getHostList().get(idx);
result = host.vmCreate(vm);//尝试创建虚拟机
if (result) { // if vm were succesfully created in the host
//更新映射关系及主机可用的处理器核心数
getVmTable().put(vm.getUid(), host);
getUsedPes().put(vm.getUid(), requiredPes);
getFreePes().set(idx, getFreePes().get(idx) - requiredPes);
result = true;
break;
} else {//如果创建失败
//将当前主机的可用处理器核心数暂时设成最小值,从而排除该主机
freePesTmp.set(idx, Integer.MIN_VALUE);
}
tries++;
} while (!result && tries < getFreePes().size());
}
System.out.println("配置完成后PEs的剩余情况");
System.out.println(getFreePes().get(0)+" "+getFreePes().get(1)+" "+getFreePes().get(2)+" "+getFreePes().get(3));
return result;
}
/**
* Releases the host used by a VM.
*
* @param vm the vm
* @pre $none
* @post none
*/
@Override
public void deallocateHostForVm(Vm vm) {
//删除虚拟机相应的映射关系,通过主机销毁虚拟机并更新可用的处理器核心数
Host host = getVmTable().remove(vm.getUid());
int idx = getHostList().indexOf(host);
int pes = getUsedPes().remove(vm.getUid());
if (host != null) {
host.vmDestroy(vm);
getFreePes().set(idx, getFreePes().get(idx) + pes);
}
}
...
//将虚拟机分配给指定的主机
@Override
public boolean allocateHostForVm(Vm vm, Host host) {
if (host.vmCreate(vm)) { //如果虚拟机创建成功,更新vmTable,并返回true
getVmTable().put(vm.getUid(), host);
int requiredPes = vm.getNumberOfPes();
int idx = getHostList().indexOf(host);
getUsedPes().put(vm.getUid(), requiredPes);
getFreePes().set(idx, getFreePes().get(idx) - requiredPes);
Log.formatLine(
"%.2f: VM #" + vm.getId() + " has been allocated to the host #" + host.getId(),
CloudSim.clock());
return true;
}
return false;
}
}
VmAllocationPolicy
类代表了数据中心主机到虚拟机的供应协议。在类刚开始便定义了vm table
(记录虚拟机被分配到哪台主机)、used pes
(记录虚拟机占用了几个处理器核心)、free pes
(记录每台主机可用的处理器核心数),VmAllocationPolicy
就是通过PEs处理器核心的个数进行单资源的调度,所以在这里我们只需关心这几个参数。传入Host列表,初始化这几个参数之后,我们便可以开始调度。
判断当前传入的vm是否已经创建,在逻辑上就是判断VmTable中有没有写入该vm的ID,未创建的话,进行资源分配,并创建虚拟机,以及更新映射关系及主机可用的处理器核心数。如果创建失败,则将当前主机的可用处理器核心数暂时设成最小值,从而排除该主机。具体代码以及详细介绍见上。
四、虚拟机分配
虚拟机分配指的是,选择满足特定条件(内存、软件环境配置等)的主机创建虚拟机的过程,这个过程由Datacenter对象负责。在云数据中心,将特定应用的虚拟机分配给主机由虚拟机分配控制器VmAllocationPolicy
完成,Cloudsim在主机层和虚拟机层都实现了基于时间共享和空间共享的调度策略。用户可以通过继承该类实现自己的分配策略,CloudSim中,作者实现了一种简单的分配策略——VmAllocationPolicySimple
。方法allocateHostForVm(Vm vm)
是该类的核心,它实现了从主机列表中选择一台主机,并在其上创建虚拟机vm。
主要实现过程的描述如下:
-
记录下所有主机可用的处理器核心数。
-
从中选出可用处理器核心数最多的第一台主机,并尝试在其上创建虚拟机。
-
如果 2 失败了且还有主机没有尝试过,就排除当前选择的这台主机,重做 2。
-
根据虚拟机是否创建成功,返回true或false。
我们自己写相关算法,执行调度该如何修改代码呢?下面接着叙述。
虚拟机的初始放置实际上是一个多维向量的装箱问题,装箱问题均是NP-Hard问题,目前针对装箱问题的解法主要为基于贪心算法的启发式算法,例如首次适应算法(First Fit,FF),降序首次适应算法(First Fit Descending,FFD),最佳适应算法(Best Fit,BF)和降序最佳适应启发式算法(Best Fit Descending,BFD)等。
- (1)首次适应算法:为物品寻找目的箱子时,首先从第一个箱子寻找,如果不满足条件(不能放在箱子),则继续寻找直到找到合适的箱子。在目的箱子中为物品分配所需空间,剩余空间可以继续放置新的物品。如果不能找到合适的箱子,则物品放置失败。
- (2)降序首次适应算法:首先将箱子按照某种资源(如CPU等)的大小进行降序排序,然后按照首次适应算法查找合适的箱子。如果能找到合适的箱子,则放置物品,更新箱子大小,放置成功,否则放置失败。
- (3)最佳适应算法:将物品装入箱子时,在所有箱子中查找大于且最接近物品大小的箱子,然后再从该箱子中分配物品所需的空间,余下的空间作为新箱子继续放置其他物品,此时物品放置成功。如果任何箱子都不能放置物品,则放置失败。
- (4)降序最佳适应算法:在最佳适应算法的基础上进行改进,首先将箱子按照某种资源(如CPU等)的大小进行降序排序,然后执行最佳适应算法查找箱子。若找到合适的箱子,则放置物品并更新箱子的大小,放置成功,否则失败。
默认调度方法
int moreFree = Integer.MIN_VALUE;//当前最大可用核心数 int idx = -1; //当前最大可用核心数对应主机的下标 for (int i = 0; i < freePesTmp.size(); i++) { if (freePesTmp.get(i) > moreFree) { moreFree = freePesTmp.get(i); idx = i; } }
上面是
VmAllocationPolicySimple
中的默认算法。即找到可用处理器核心数最大的第一台主机。
在这里,测试数据为:VM所需核数分别为 2 3 3 1,按照在Example1中定义的VM和Host列表的资源规配置,那么运行出来的正确结果应该是:
- 0-3号主机中剩余PEs资源:2 1 1 3
- 0-3号虚拟机的主机分配ID:0 1 2 3
结果截图:
首次适应算法
int moreFree = Integer.MIN_VALUE;//当前最大可用核心数
int idx = -1; //当前最大可用核心数对应主机的下标
for (int i = 0; i < freePesTmp.size(); i++) {
if (freePesTmp.get(i) >= requiredPes) {
idx = i;
break;
}
}
上面是首次适应算法的代码。即按照次序找到符合条件的第一个主机。
在这里,测试数据为:VM所需核数分别为 2 3 3 1,按照在Example1中定义的VM和Host列表的资源规配置,那么运行出来的正确结果应该是:
- 0-3号主机中剩余PEs资源:1 1 1 4
- 0-3号虚拟机的主机分配ID:0 1 2 0
结果截图:
最佳适应算法
int resource_diff = Integer.MAX_VALUE;//所需资源与目前资源的差值
int idx = -1; //当前最大可用核心数对应主机的下标
for (int i = 0; i < freePesTmp.size(); i++) {
if (freePesTmp.get(i) >= requiredPes) {
int r = (freePesTmp.get(i) - requiredPes);//所需资源与目前资源的差值,找出最小的一个
if(r < resource_diff) {
resource_diff = r;
idx = i;
}
}
}
上面是最佳适应算法的代码。即查找出大于且最接近所需PEs大小的主机。 在这里,测试数据为:VM所需核数分别为 2 3 3 1,按照在Example1中定义的VM和Host列表的资源规配置,那么运行出来的正确结果应该是:
- 0-3号主机中剩余PEs资源:2 0 1 4
- 0-3号虚拟机的主机分配ID:0 1 2 1
结果截图: