一种面向作业流(工作流)的轻量级可复用的异步流水开发框架JobFlow的设计与实现

一种面向作业流(工作流)的轻量级可复用的异步流水开发框架JobFlow的设计与实现

 

       要做这个东东的想法由来以久了的说,一个月以前动手把代码实现了下来,今天觉得如果实在不写成博文记录下来的话,不知又要推到何年何月了。嗯,废话不多说,直入正题了~

       在实际的开发过程中,我们经常会遇到这样的情况:海量的并发用户发送请求要求服务器处理,而服务器集群之间彼此也经常会传递请求,用于完成特定的任务。我们可以把处理每个请求的过程理解为是一种任务的执行,这样的话,就相当于有一堆的作业摆在那时需要执行。而我们所要关心的是作业的数据结构是如何描述的、作业是如何存放、如何取出的以及是如何执行的。

       基于上述的思路,我们可以将任何的操作流程封装在一个Job,并将其丢入到一个Job池中,Job池最好设置成阻塞模式,这样即可唤醒等待的多个处理器从Job池中取出Job并执行。这样一个框架的形成就牵涉到了宏观设计的问题:即JobJob池及处理器这三种最重要的资源是如何定义和组织的。JobFlow整体的框架结果如下图所示:

为了便于简化开发过程,使用的开发语言为Java,但这种设计思路是基于面向对象的,具备普适意义,感兴趣的可以自己实现C++版本。代码的组织结构截图如下:

一、Job作业的设计

       Job即是对处理某个任务/作业执行流程的一种抽象,Job接口提供一个最重要的方法:run(),我们可以定义具体的Job类,实现run()方法,用于填充必要的业务逻辑,在run()执行过程中可能会产生新的Job。按照这样的思路,我们可以在一个大Jobrun()方法定义相关的执行流程,从而派生出若干个小Job,继而实现了对一个大Job的拆分过程,同时每小Job也还可以继续做拆分

       如下图所示,简单地举例:我们定义了加法Job、减法Job、乘法Job及除法Job,每种Job均实现了Job接口,对于run()方法做了对应的填充,分别实现了两个数的相加、相减、相乘和相除。

 

二、作业池JobPool的设计

       既然生成了Job,那么这些Job就需要进行存放。JobPool作为Job作业存储的载体,最为关注的是Job的存入put(),和Job的取出get()。而Job池本身的存储介质可以是多种多样的:内存、数据库或者是文件系统;Job池的取出策略也可以是多种多样的:作业先进先出FIFO、作业先进后出FILO、大作业优先、小作业优先、响应比高者优先、随机等等。

       由于JobPool的存储介质和取出策略两者是独立的部分,使用桥接模式代替类之间的继承关系,使这两个部分独立地变化,从而避免了派生类的膨胀。

为了便于说明问题,本文后面的代码里只关注了取出策略的变化,存储介质只是基于内存实现的,但这并不代表我不知道如何实现这个桥接模式。

三、处理器Executer的设计

       有了Job,也有了存储JobJobPool,那么我们就需要定义一些计算资源从JobPool里出取一个Job,并执行Job.run()。我们将这种计算资源抽象为Executer,即处理器。Executer可以是一个进程或者是一个线程,可以是本地的计算资源,也可以考虑是远程的计算资源,这样我们可以灵活地将单机环境的系统扩展为一个分布式计算系统。为了简单处理,我们Executer的设计先考虑本地线程的情况。

我们在使用计算资源Executer时,并非只有一个Executer,利用ExecuterPoolExecuter进行管理。

为了使计算资源也作业本身对接,我们创建一个作业池管理类JobPoolManager封装JobPoolExecuterPool,用于取出Job并执行。

四、基本的实现BaseXXXX.java

有了上述这些框架基础的接口之后,我们需要对这些接口做一些最初步的实现,称之为BaseXXXX.java,对应源代码分别如下:

五、具体的实现

基于BaseXXXX.java的基本实现,我们需要定义能够在实际运行环境下工作的类,源代码如下:

 

六、基于队列的JobPool实现

我们现在可以自己定义具体的Job存取方式了,如以List队列形式存放Job会遵循FIFO的取出规则:

七、单机环境测试

为了对框架本身进行简单的测试,我们编写了4个简单的Job作业,分别做加减乘除运算,并编写了一个Test类用于框架的执行:

八、将JobFlow的应用扩展到分布式环境

目前而言,我们已经初步实现了单机环境下的异步工作流执行,我们更希望是将其扩展到一个分布式的环境中,利用多物理处理机异步地执行整个工作流序列。设计的思想是将JobPool所封装的Job队列设置为分布式环境下共享的阻塞队列,如果JobPool为空,则所有处理器均处于阻塞状态,如果有Job存入JobPool,则唤醒等待的处理器进行异步地执行。在编码实现上,利用JavaRMI技术实现JobPool的远程调用。

运行的方式:先运行RmiJobPoolInit使JobPool远程对象化,从而适用于分布式环境下并发的访问,再在同一主控节点上运行RmiJobProducer产生若干Job并将这些Job放入到JobPool中。随后,在其他从属节点上运行RmiExecuterServer用于从JobPool中取出Job并执行,从而实现了一个简单的分布式计算框架。

       然而,这里会存在一个问题,即如果你在从属节点上先开启RmiExecuterServer,而主控节点后开启RmiJobProducer,则从属节点还是一直处于阻塞状态,并不会从JobPool中取Job执行,很奇怪呢。这里Job的同步的控制并不完备,神马原因呢,暂时还没想通,以后我得找时间好好去调调~

      

九、总结

很明显可以看出,整个JobFlow的框架的思想精髓的实质即是我们之前所学习的操作系统的两个经典问题:生产者/消费者问题、作业调度问题。JobFlow适用于很多的场合,比如C/S模式的网络收发数据包:网络数据都需要经过打包、发包、解包、执行这四个流程,我们就可以将它们封装成一个一个的Job,丢到一个很大的Job池里面。Client端有多个打包线程不断地将数据打包后丢到Job池中,又有多个发送线程去Job池里取包向Server发送数据;Server端有多个接包线程在接收到Client所传输的数据包后,丢到自己Job池中,再由多个解析线程取Job进行数据包的解析,最后将解析后的Job分类,丢给相应的执行Job池中,再分别由多个执行线程从对应的执行Job池中取Job进行执行。

这里只是举例说明,具体的JobFlow的实现还是会遇到很多技术难点,要考虑各种Executer的同步、内存泄露问题、线程安全问题、各种池化对象的维护与管理。而对于一个框架而言,可扩展性、可复用性和灵活可配置性都是要考虑的问题。

       好了,JobFlow的原理和代码实现大概就是如此了。为了验证想法,这个版本JobFlow的代码实现的还是过于简单了些,在最初设计上的考虑,在本版本的代码里并未。更为完善的版本会在后续的时间里更新,请大家持续关注本博客~

猜你喜欢

转载自blog.csdn.net/MONKEY_D_MENG/article/details/6419246
今日推荐