前边的文字介绍了基于.net的计算网格(Computing Grid)的实现Alchemi,并对其使用做了简单的介绍。个人一直对其不同于其他Grid框架的基于Thread的实现很感兴趣,通过细粒度的Thread,应用Alchemi可以比较容易的实现分布算法。今天比较闲暇,因此对其源码做了简单的浏览,结合其文档,大概得出以下认识。
Alchemi的基本编程模式如下:
第一,从GThread继承自己的GThread类,实现具体的计算代码;
第二,使用GApplication实现主程序:
(1)通过GApplication.Threads.Add添加线程;
(2)通过GApplication.Connection初始化连接和Grid平台;
(3)GApplication.Start启动计算;
(4)必须订阅事件以获知线程结束和计算结束。
一个Alchemi必须包含以下部分:User, Manager, Executor。User通过继承GTread类创建自己的执行代码,通过GApplication的实例连接到Manager来执行代码。在初始化时,需要指定Thread运行需要的任何数据、Dll等信息,Alchemi负责把这些数据或第三方代码部署到Excutor所在的计算机,在运行结束后删除之。对于一般的.net程序,则简单的使用
App.Manifest.Add(new ModuleDependency(typeof(PrimeNumberChecker).Module));
类似的语句将本程序集添加即可。
Manager提供透明的计算服务,一个Manager可以有数个User来使用,同时,Manager将任务分配给自己的Executor来完成任务。这些任务在Manager上存储于SQL Server或者MSDE。Manager同时验证User是否合法用户。
Excutor提供计算服务,在计算之前,任务通过先进先服务的原则依次获得计算。
Alchemi通过.net的Remoting机制来实现。上边提到的User, Manager, Executor分别在GManager, GExecutor, GApplication中实现,其继承自GNode,其中实现了基本的通信机制。一个Grid程序的执行过程如下:
首先,用户创建继承自GTread的类,创建GApplication的实例,所有执行代码需要的数据被加载到DependencyCollection中,FileDependencyCollection是一个继承自ReadOnlyCollectionBase的一个集合类。而继承自GTread的类的实例线程则加载到ThreadCollection中。ThreadCollection是继承自CollectionBase的一个自定义Collection,作为保存GTread实例的容器。
接着,GApplication序列化执行Thread需要的代码和数据,并将其发送到Manager所在计算机,并持久化到硬盘,线程状态等信息则保存到SQL Server数据库中。以下是GApplication的Start的代码:
public void Start()
{
...
Init();
lock(_Threads)
{
foreach (GThread thread in _Threads)
{
if (thread.Id == -1)
{
SetThreadOnManager(thread);
}
}
}
...
}
其中首先通过Init初始化系统,然后遍历Threads集合,通过SetThreadOnManager方法将其发送到Manager,代码如下,关键在于Manager.Owner_SetThread方法,其中最后一个参数将需要的数据、代码等序列化。
private void SetThreadOnManager(GThread thread)
{
thread.SetId(++_LastThreadId);
thread.SetApplication(this);
Manager.Owner_SetThread(
Credentials,
new ThreadIdentifier(_Id, thread.Id, thread.Priority),
Utils.SerializeToByteArray(thread));
}
在Manager的Owner_SetThread方法中,Thread被添加到MApplicationCollection集合中。
将线程发送给Manager后,GApplication实例通过订阅GThreadFinish等事件返回执行结果。Manager通过MApplicationCollection和MExecutorCollection集合保存其用户和Thread信息。通过Owner_CreateApplication和Owner_SetApplicationManifest来完成用户需求的任务的创建,在Owner_SetThread方法中完成Thread的创建。对于有空闲的dedicated的Executor,可以直接连接并执行之,对于没有的情况下,则保存在集合中,由Executor反过来调用来执行Thread。
在Excutor中,执行Thread的方法在ExecuteThreadInAppDomain中,其中首先创建应用程序域,然后通过以下语句执行之:
首先从Manager获取Thread:
byte[] rawThread = Manager.Executor_GetThread(Credentials, _CurTi);
GridAppDomain gad = (GridAppDomain) _GridAppDomains[_CurTi.ApplicationId];
然后执行之,并设置完成状态:
byte[] finishedThread = gad.Executor.ExecuteThread(rawThread);
Manager.Executor_SetFinishedThread(Credentials, _CurTi, finishedThread, null);
其中的gad.Executor定义在AppDomainExecutor中,ExecuteThread方法如下:
GThread gridThread = (GThread) Utils.DeserializeFromByteArray(thread);
gridThread.SetWorkingDirectory(AppDomain.CurrentDomain.SetupInformation.PrivateBinPath);
gridThread.Start();
return Utils.SerializeToByteArray(gridThread);
首先从数据中重建GThread类,然后设置目录,运行,最后序列化回为数组流。其中的序列化使用的是.net的BinaryFormatter。
对于non-dedicated的Thread,通过StartNonDedicatedExecuting(i),设置一个i参数来完成间隔一定时间从Manager获取Thread来调用。
这样,我们对Alchemi的运行机制做了一个简单的剖析,文中肯定存在不少问题和错误,欢迎指正和讨论。了解一个系统的内部实现,一方面可以很好的应用之,另一方面也可以学习其设计思想,应用在我们的项目中。对于Alchemi,个人觉得其核心思想很简单,就是通过一定机制,将执行代码和数据序列化,然后临时部署到远程计算机,然后启动之,达到分布式计算的目的。与一般的远程调用机制比较,一般的远程调用是调用远程计算机上存在的程序,而Alchemi是将代码先部署到远程计算机,然后在执行之。