基于SEDA的异步框架设计与实现

基于SEDA的异步框架设计与实现

一、SEDA国内外研究现状

1、SEDA架构的起源和特点    当前最流行的互联网服务器模型基本都是基于多线程/进程并发服务模型。然而当线程数目增加到一定值后,系统性能会下降,这就限制了系统支持高并发请求的能力。这种缺陷是多线程/进程本身的架构造成的。加州大学伯克利分校的Matt Welsh提出了一种新的服务器软件架构SEDA。SEDA是Staged Event-Driven Architecture的 缩略语,其基本原理是:应用程序被构造为阶段网络,阶段之间靠事件队列联系,应用程序设计者只负责每个阶段的服务逻辑和阶段间的连接逻辑,而由各阶段自身 负责资源管理和负载适应功能。使用这种分解使系统达到高并发性、高效率性、对负载变化的良好适应性以及良好的系统缩放性。

2、SEDA架构应用情况

    目前SEDA架构在国外已经受到了很多开发人员的青睐。在国外,SEDA架构已经在很多科研机构中被用以进行成熟的学术研究以及实践应用。加州大学伯克利分校、哈佛大学、普林斯顿大学以及微软研究所等闻名于世的大学和科研机构。Matt Welsh博士及其团队已经基于JAVA实现了SEDA架构——Standstorm,并基于此开发了几个应用服务器系统。劳伦斯伯克利国家实验室同样提供了依照SEDA架构开发了开源项目SEA。spring也推出了基于SEDA架构的spring-batch。与此同时,SEDA也已经被用以进行企业级的应用开发。著名的开源企业服务总线(ESB)——Mule即实现了SEDA架构。与国外欣欣向荣的SEDA架构发展现状相比,国内目前使用SEDA架构的企业仍然不多,大多的企业仍然由于技术成本以及其他原因,更倾向于使用主流的多线程、事件驱动服务器模型进行开发。这就导致了在遭遇高并发环境的时候,相当多的企业只能被动地进行硬件升级,而未能很好地利用服务器资源。虽然SEDA在国内多仍停留在起步阶段,但是仍然可以看到包括百度、58同城在内的少数互联网企业已经在内部推广SEDA架构

3、目前仍存在的问题

    采用SEDA架构的系统一般都会面临两个值得关注的问题,其一: 即如何合理地进行资源的初始化分配。如果未能进行合理地线程以及队列资源分配,导致出现资源需求较大的阶段被分配了数量不足的线程数,或者资源需求较小的 阶段被分配了数量过多的线程数,则会造成系统吞吐量的下滑,甚至会导致系统性能在高并发下远低于多线程模型系统。其二,如何合理地对系统进行阶段划分,不 合理的阶段划分将导致系统性能的下滑。要解决这两个问题就必须在很大程度上依赖于架构搭建者对于系统的熟悉程度以及数据量的评估。SEDA架构的提出者 Matt Welsh博士就曾收到非常多开发者对于SEDA架构性能的质疑信件,他也对此做出了相应的解释。所以如何合理使用SEDA架构,是所有架构搭建者在搭建 SEDA时必须仔细考虑的。

4、今后发展的趋势

    随着互联网信息爆炸式的增长,越来越多的互联网服务器将遭遇高并发、海量数据的环境。在这种环境下,多线程以及事件驱动模型服务器已经无法适应。SEDA 架构在高并发下优异的性能表现,已经引起了越来越多企业的关注,而且该架构也体现了SOA的思想,符合以后企业进行分布式SOA系统开发的需求。所以可以 预见,接下来的时间里,SEDA将会得到广泛地应用,在互联网SOA系统中扮演不可或缺的角色。而在这之中,互联网消息提醒系统由于消息处理和消息发送之 间可能具有巨大的资源消耗差异性,已经首当其冲地成为了SEDA的试验田。




二、为什么使用SEDA

 

       目前,面对并发环境,主流互联网服务器编程模型有两种:多线程模型以及事件驱动模型。但是这两个模型都不足以解决这个问题。我们来首先看一下这两种编程模型。

 

1、多线程并发模型   

       多线程并发模型是目前最普遍的服务器编程模型,该模型的架构如下图所示

        该模型针对每一个请求,会为其创建并分配一个线程。该线程负责这个请求的处理。该模型的优点:执行粒度是整个完整的处理流程。处理逻辑清晰,容易开发。但与此同时缺点也很明显:如果处理过程中某一步骤出现长时间调用(例如网络通信、I/O磁盘读写等),则会阻塞后续的请求,造成资源的浪费。并且,当随着处理请求不断增加,导致并发执行的线程数量太多。过多的线程数量导致系统在线程调度和资源争用上的开销过大。引起系统性能急剧下降。导致系统处理能力下降。

 

2、事件驱动模型   

       同时,我们也能看到,很多系统也倾向于使用事件驱动模型,该模型的模型图如下图所示:

        该模型的思想为:将处理流程分割成多个步骤,每一个步骤都实现为一个有限状态机(FSM),所有的处理请求会作为事件进入系统,由调度器负责传递给相应的状态机,状态机的处理结果也以事件的形式传给调度器,新的事件将再次被调度器转发给下一个状态机进行处理,直至处理完成。该模型的优点在于,由于将各个处理步骤独立实现,可以很容易的进行系统监测和调整。但是其缺点也不容忽视,即:由于Scheduler的设计和实现过于复杂,针对于不同的应用和系统的逻辑变更需要不同的实现,导致采用这一模型构建出的系统将十分庞大和难以控制。
       综上所述,主流的多线程编程模型以及事件驱动编程模型虽然都各有其优势,但同时也都有其不可避免的缺陷,并不适合在高并发的环境下进行成熟系统的开发。所以这就是为什么我们要选择其他的模式来进行系统开发。以下介绍SEDA架构思想。

3、阶段性事件驱动(SEDA)模型   

       针对上述现有主流并发架构之缺陷,若能够实现一种良好支持高并发,并且能够对性能需求自适应的架构,则将在很大程度上优化和提升服务器性能,既能够提高运行速度,又可以减少服务器压力,实现了公司利益与用户体验的双赢。因此,为汲取多线程与事件驱动模型之优点,最大程度规避这两者之缺点,以实现较好的高并发服务器架构,SEDA便应运而生。
       SEDA(Staged Event Driven Architecture)是一种阶段性事件驱动的服务器应用程序架构。它是Matt Welsh博士于加州大学伯克利分校提出的一个高性能应用服务器模型。SEDA架构整合了多线程的服务器模型和事件驱动的服务器模型的优势,它可以高效地管理和控制服务器资源,良好地适应高并发环境,SEDA被设计成一个可伸缩的高可用服务器架构。 
应用服务器之请求处理步骤通常是复杂的、基于事件驱动处理的有穷状态机(FSM),所以实现服务器程序过程就类似用程序实现有穷状态机过程。下图所示的即是一基于SEDA模型http服务器有穷机状态图:


        SEDA架构能对有穷状态机进行分析,尔后将相关状态聚集在同一Stage中,Stage间采用队列的方式来进行通信。每一个Stage皆完全独立,均拥有自己的线程池,以及为了专门处理到达这一步骤所必须进行的工作。所有的Stage均通过自身事件队列连接在一起,构成完整的请求处理网络。性能控制器和动态线程池依请求的繁忙程度动态来调整线程池的大小,以达到系统资源的最有分配。每一个Stage由下述四部分组成:
       (1)    事件队列:用以维持Stage间之通信。
       (2)    事件处理器:用以执行请求到这一个Stage中所应执行的工作。
       (3)    线程池:用以提供事件处理器且可以并发执行事件处理之环境。
       (4)    性能控制器:用以对该Stage资源(线程数、队列长度等等)进行调整。
       通过这四部分的协作配合,每一个Stage都可以很好地运行,并且可以控制资源的使用。已经过Stage处理完,若没有后续工作,即可以回收线程池中的线程,来供给其他Stage使用。Stage结构如下图所示。
       在SEDA架构中,基本的处理单元称为阶段(Stage),一个阶段由事件队列、动态线程池、事件处理器和一个性能控制器四个组件构成。SEDA将一个请求的处理过程分解为一系列的阶段,阶段之间通过事件队列联系,开发人员只负责每个阶段的服务逻辑以及阶段间的连接逻辑,而由各个阶段自身负责资源管理以及负载适应功能。使用这种解耦拆分可以使系统达到高并发性、对负载变化的良好适应性以及高度的可缩放性。


 




三、异步框架技术选型

       在这次实现的SEDA异步框架中,采用的基础架构原型如下:

        采用了spring+quartz+fastjson+rabbitmq来实现。和传统SEDA架构区别比较大的地方在于:

        1、采用分布式mq(使用了rabbitmq)而不是blockingqueue。如此既可以支持以后可能进行的分布式化扩展,也可以使得框架具有高可用性,在大数据处理的时候仍可具有较为客观的性能。同时,消息的传递过程中,采用了高性能的fastjson进行数据序列化和反序列化。使得数据在stage之间的传递速度更快。

        2、除了分布式mq之外,还提供了本地动态线程池所需要的队列。避免了consumer由于长时间处理导致数据在队列中积压。

       3、stage利用quartz来提供定时功能,使得stage中的work可以选择定时/实时进行数据处理。从而迎合更多不同类型的需求。(比如定时报警,定时分析监控数据)

 

1、spring

       spring无需再赘述,使用其IOC、AOP等功能,并同时使用spring的其他组件比如spring-rabbit、spring-quartz等。保证spring各包兼容即可。

 

2、quartz

       quartz 的介绍文档网上很多,quartz作为一款优秀的定时器框架可以和spring无缝结合,同时还具有java自带的定时器timer所不具备的定时启动的 功能。其类crontab风格的定时任务声明也更符合我们企业级应用过程中的书写风格。之前文章中介绍过使用quartz过程中需要关注的几个点,复述如 下:
  1)Job不能为内部类,否则无法初始化
  2)保证spring升级到新版本。如果使用老版本比如3.0.5,则会出现如下异常:
Java代码   收藏代码
  1. java.lang.IncompatibleClassChangeError: Found interface org.quartz.JobExecutionContext, but class was expected    
   该case在http://code.google.com/p/wisematches/issues/detail?id=156上有记录。
  3)引入spring-context-support包来使用quartz相关内容,并保证与其他包的兼容性。
  4)其定时语法和crontab有些许差别。语法见:
http://www.blogjava.net/javainthink/archive/2006/10/19/76077.html
       在异步框架中的使用场景:辅助实现定时功能,从而使得异步框架可以更加灵活的支持各种需求。
 

3、fastjson

       fastjson官网:http://code.alibabatech.com/wiki/display/FastJSON/Home-zh。如其描 述,"fastjson是最快的json库。特别在parser方面,fastjson的性能令人惊奇,甚至超越了二进制协议的protobuf"。虽然 只是fastjson一家之辞,但是从protobuf官网上对各种序列化/反序列化工具进行的性能测试中,也可以看出fastjson优异的性能已经将 传统的jackson工具抛在身后。

 

 

  除了速度这一最大优势之外,其还具有如下优势:      
  1)遵循http://json.org标准,为其官方网站收录的参考实现之一。
  2)功能强大,支持JDK的各种类型,包括基本的JavaBean、Collection、Map、Date、Enum、泛型。
  3)无依赖,不需要例外额外的jar,能够直接跑在JDK上。
       4)开源,使用Apache License 2.0协议开源      
       在异步框架中的使用场景:辅助stage到stage之间的数据通信,负责数据在通信过程中的序列化和反序列化过程。
 

4、rabbitmq

       stage与stage之间需要依靠事件队列来进行通信,如果依赖于SEDA官网推荐的BlockingQueue,则无法满足未来的分布式部署。所以决定采用分布式mq来实现。以下比较几个主流的消息中间件:

       1) activemq:

       被称为消息中间件中的瑞士军刀。支持JMS,性能不错。开源社区活跃。能与java很好结合。Spring有相应lib。其功能较为完备,可支持P2P和代理,但性能逊于rabbitmq。从性能角度出发来考虑,决定不使用activemq。

       

       2)zeromq

       性能比其他中间件优异得多。但无法持久化,不能保证数据的可靠性。由于数据的可靠性不能被保证,所以暂时也不考虑。



 

       3)redis       可以当作轻量级的中间件。当传输数据大小少于10k时入队性能优异,数据较大时性能急剧下降。而其出队性能不论数据量大小都十分优秀。考虑到项目实现过程 中可能出现的大于10K的请求响应事件数据(比如推荐关键词、比如与后端服务交互返回的数据),所以不采用redis。

 
       4)kafka 

       除此之外,kafka也是一款值得注目的,性能优异的分布式消息中间件,通过producer的push和consumer的poll来实现数据的交互。 kafka自带负载均衡、并有高可用、高吞吐等特性。但是考虑到实际项目的适用情况,暂时还不需要zookeeper集群,broker集群来进行如此大 规模的数据处理。加上其依赖于scala环境,所以要部署和运维显得不那么方便。所以暂时不考虑。

       5)rabbitmq

       支持AMQP,性能优于activemq。能与java很好结合。Spring有相应lib。环境部署起来不如activemq便捷,需要erlang环境。



        在http://www.aqee.net/message-queue-shootout/一文中对activemq和rabbitmq进行的性能比较显示出rabbitmq更佳的性能。如下图所示:

 

       综上所述,考虑到该次项目所应用的场景和处理数据量的规模,且需要较为优秀的性能,较为便捷的部署方式,能保证消息可靠性以及可持久化,并且对java友好。最终权衡之下,选择了基于AMPQ的rabbitmq消息中间件。


四、异步框架总体设计与实现

 1、框架中的stage理想结构

       前文提到,基于SEDA的异步框架,一个stage的理想结构描述如下:

      

       在这个框架的设想中,一个stage一般需要有如下几个组件:

       1、D-MQ:分布式消息中间件。用做事件队列,以进行消息的传递。

       2、Local-Queue:本地队列。一般是blockingQueue,用以辅助实现stage内的动态线程池。采用Local-Queue的目的在于避免数据在mq中的堆积导致mq性能下降。

       3、Thread Pool:动态线程池。进行事件的并发处理。

       4、Worker:事件的具体处理器

       5、Stage Controller:stage的性能控制器。用以对stage的队列、资源、调度策略进行控制。

       引此为框架的设计理念,于是有了如下基于SEDA的异步框架的架构设计。

 

2、SEDA异步框架的使用场景  

       该异步框架可以用来处理如下几个场景的问题:

       1、系统资源监控(CPU、内存、线程池、队列)

       2、外围服务交互情况(API被调用、上游服务交互、请求方等)监控

       3、系统报警(服务异常、接口压力过大等)

       4、基于日志和事件的数据挖掘(规则挖掘等)

       5、重要业务数据切片转储(里程碑消息、核心服务交互数据等)

       6、异步触发的操作(表A写完后异步写表B等)

       其使用场景大致可如下图所示:

 

3、SEDA异步框架系统总体架构   

       因而,基于以上所述适用范围的框架实现之后的系统架构,一般可如下所示:

       
       当然,以上结构并非绝对的,如有需要,你完全可以通过自己定制bundle和bundle之间的拓扑关系,来实现各种复杂的事件处理过程。你只需要简单通 过声明bundle相关配置,即可实现任何按照你所希望的有向图去关联的bundle。框架提供给了你一个经过轻量级封装后的平台,后面的业务逻辑,就靠 开发者自己了。

 

4、异步框架原生态架构(Virtual Bundle)

       基于上述的设计理念,最终实现的异步框架的原生态架构如下所示:

 


     

      异步框架在无任何扩展的时候,其主要组件如下:
       1、bundle:消息中心的核心组件。由读、处理、写三部分功能组成。同时整合开关、定时器、动态线程池等元素来支持多样化的输入和需求。bundle可以从多种数据源获取数据,并进行数据的处理。
       1)开关:用以决定该bundle是否被激活。如未被激活,则该bundle将停止读取数据,同时不会在其他服务上产生该bundle对应的数据(比如在mq上生成该bundle的队列、连接、交换机等。)
       2)定时器:用以指定该bundle是否定时运行。如未指定,则实时运行。
       3)动态线程池:用以支持bundle以同步/异步方式调用。如未指定,则同步运行。
       2、bundle decider:用以对bundle的关键指标进行决策(是否激活、时效性、同步类型等)。并同时提供健康检查。
       3、Work carrier:处理数据的最小单元。

 

5、异步框架的AMQP实现(AMQP Bundle)  

       异步框架扩展的AMQP实现,其架构图如下所示:


      

       其主要组件说明如下:
       1、amqp bundle:消息中心的核心组件。由读、处理、写三部分功能组成。在整合开关、定时器、动态线程池之余,提供了配置化的订阅订阅管理以及关键行为的声明。

       要声明一个bundle仅需声明对应的bean,示例如下:

   

Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3. </bean>  

 

       1)开关:用以决定该bundle是否被激活。如未被激活,则该bundle将停止读取数据,同时不会在其他服务上产生该bundle对应的数据(比如在 mq上生成该bundle的队列、连接、交换机等)。加上开关之后,最大的优势在于其可以更方便的支持分布式部署。对不同的部署实例设置不一样的 active设置可以完成不同stage在不同机器的启停。默认激活。我们为bundle加上开关,示例如下:
      
Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.     <property name="active" value="true" />   
  4. </bean>  

      

       2)定时器:用以指定该bundle是否定时运行。默认实时运行。我们为bundle加上定时器,示例如下:

 

Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.     <property name="timer" value="0/30 * * * * ?"/>  
  4. </bean>  

      

       3)动态线程池:用以支持bundle内部的实际数据流处理过程以同步/异步方式调用。默认同步运行。我们为bundle加上动态线程池,示例如下:

 

Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.         <property name="taskExecutor" ref="alarmCollectBundleExecutor"/>         
  4. </bean>  
  5.   
  6. <!-- 报警信息收集器对应的动态线程池 -->  
  7. <bean id="alarmCollectBundleExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">  
  8.     <property name="corePoolSize" value="2" />  
  9.     <property name="maxPoolSize" value="30" />  
  10.     <property name="queueCapacity" value="200" />  
  11. </bean>  

      

       4)订阅发布:用以声明收集和推送信息时所需的交换机和密钥。通过支持逗号分隔的多key组合来支持多对多的上下游bundle关系。每个key的配置语 法符合rabbitmq中topic类型的exchange使用规范即可。默认采用“deimos-common”交换机。以下给出几种声明的配置:

       其一:最简易配置。配置要订阅和发布的消息key即可。交换机采用默认配置。

 

Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.         <property name="pubKeys" value="process.alarm.*" />  
  4.         <property name="subKeys" value="collect.alarm.*, collect.log.error" />  
  5. </bean>  

      

       其二:声明特殊的来源和目的地的交换机:

 

Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.         <property name="pubKeys" value="process.alarm.*" />  
  4.         <property name="subKeys" value="collect.alarm.*, collect.log.error" />  
  5.         <property name="pubDest" value="spec1" />  
  6.         <property name="subDest" value="spec2" />  
  7. </bean>  
      
       5)事件队列:每个bundle默认实现一个固定格式的独立队列。可通过配置另外指定。可支持bundle监听多队列的需求。如需要特别指定一个或多个事件队列,则示例如下:
 
Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.         <property name="pubKeys" value="process.alarm.*" />  
  4.         <property name="subKeys" value="collect.alarm.*, collect.log.error" />  
  5.         <property name="subQueues">  
  6.         <list>  
  7.             <ref bean="queueForLogError" />  
  8.         </list>  
  9.     </property>  
  10. </bean>  
  11.   
  12. <!-- error日志消息的订阅 -->  
  13. <!--  
  14.         如队列的声明不采用默认配置,完整声明如下:  
  15.        <property name="exchangeName" value="deimos-common" />  
  16.        <property name="queue" value="logQueue" />  
  17.        <property name="bindingKey" value="collect.log.*" />  
  18. -->  
  19. <bean id="queueForLogError" class="com.cc.deimos.satellite.bo.AmqpQueueConfig">  
  20.     <property name="bindingKey" value="collect.log.error" />  
  21. </bean>  

     

       6)监听容器:按照默认配置实现,并发数可通过配置指定。bundle如需额外设定channel数量,则示例如下:

 

Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.         <property name="pubKeys" value="process.alarm.*" />  
  4.         <property name="subKeys" value="collect.alarm.*, collect.log.error" />  
  5.         <property name="concurrency" value="20" />  
  6. </bean>    

      

       7)关键行为。用以给发布的消息打上bundle的标签。以辅助其他bundle进行数据筛选和处理。默认以发布的key为关键行为。如需额外声明,则示例如下:

 

Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.         <property name="pubKeys" value="process.alarm.*" />  
  4.         <property name="subKeys" value="collect.alarm.*, collect.log.error" />  
  5.         <property name="keyAction" value="ALARM_KEY_INFO" />  
  6. </bean>    

      

       2、bundle decider:用以对bundle的关键指标进行决策(是否激活、时效性、同步类型等)。并同时提供健康检查。默认采用fix strategy decider(定参策略决策器)。可进行配置来指定所需决策器类型,示例如下:

 

Java代码   收藏代码
  1. <!-- 报警消息收集器 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.         <property name="pubKeys" value="process.alarm.*" />  
  4.         <property name="subKeys" value="collect.alarm.*, collect.log.error" />  
  5.         <property name="strategyDecider" value="FIXED_STRATEGY"/>  
  6. </bean>         

      

       3、work carrier:处理数据的最小单元。Bundle依据决策器指示的状态同步/异步、实时/定时调用work carrier进行处理。完全对开发者透明。用户者无需关心该组件。bundle将结合decider进行调度。同时work carrier处理后的数据推送过程也对开发者透明。开发者所需要做的就是实现bundle的doWork方法,并将处理之后的数据直接return即 可。doWork方法如下所示:

 

Java代码   收藏代码
  1. @Override  
  2. public Object doWork(List<DeimosSatelliteRequest> message) throws Exception{  
  3.     // do somethoing with message then retrun the result;  
  4. }  
      
       4、exchange:rabbitmq交换机。默认所有bundle都请求“deimos-common”。可集群化。配置见上。
       5、amq:采用支持amqp协议的rabbitmq。默认单机内存节点。可采用镜像队列或其他方案来进行broker、queue的集群化。
       6、channel:amq信道。可启动多信道并发监听amq队列消息。支持配置化设定。配置见上。
       在web/servlet容器启动之后,框架中的各个组件将被依次加载,以下给出了bundle的大致启动流程,也正是因为这个启动流程,将上述的各个组件进行串联,并开始执行各自负责的工作:
        

 
       以上详细介绍了SEDA框架的AMQP实现中主要组件的作用、声明方式以及实现原理。总结一下,异步框架的AMQP实现中,bundle与bundle之间通过分布式 队列rabbitmq进行数据传递,bundle内部提供包含阻塞队列的动态线程池taskExecutor来进行数据处理,同时提供了定时器timer 来控制bundle的定时/实时调用。workcarrier作为消息处理的最小单元,其调用机制完全对用户透明。消息在bundle中的接收、处理和推 送由bundle decider组件进行管理。用户只需要简单实现doWork方法和声明bundle配置即可实现消息的处理和传递。

 

6、一个简单的bundle安装示例  

      你完全可以只按照如下几步,就可以轻松实现你每个stage:
       1、继承AmqpBundle类,实现doWork方法,完成你的业务逻辑。示例如下(通用收集器demo):
       
Java代码   收藏代码
  1. public class CommonAmqpCollectBundle extends SatAmqpBundle {  
  2.     /** 
  3.      * 采用并发队列。性能比阻塞队列高 
  4.      */  
  5.     public final ConcurrentLinkedQueue<DeimosSatelliteRequest> cacheQueue = new ConcurrentLinkedQueue<DeimosSatelliteRequest>();  
  6.   
  7.     @Override  
  8.     public Object doWork(List<DeimosSatelliteRequest> message) throws Exception {  
  9.         logger.info("i am now in LogWorker:" + Thread.currentThread().getName() + "message is : " + message  
  10.                 + ". now begin to collect!");  
  11.           
  12.         // 此处不采用锁,因为其带来的影响很有限  
  13.         cacheQueue.addAll(message);  
  14.         if (cacheQueue.size() < SatConstant.LOG_BATCH_SIZE) {  
  15.             return null;  
  16.         }  
  17.   
  18.         // 进行遍历导数据  
  19.         List<DeimosSatelliteRequest> list = new ArrayList<DeimosSatelliteRequest>();  
  20.         for (int i = 0; i < SatConstant.LOG_BATCH_SIZE && !cacheQueue.isEmpty(); i++) {  
  21.             DeimosSatelliteRequest meta = cacheQueue.poll();  
  22.               
  23.             // 此时队列也已经为空了  
  24.             if(meta == null){  
  25.                 break;  
  26.             }  
  27.               
  28.             // 校验  
  29.             if(meta.getTimestamp() == null || meta.getRealData() == null || meta.getData() == null){  
  30.                 logger.error("[deimos-satellite]meta param is error! meta request: " + meta);  
  31.                 continue;  
  32.             }  
  33.               
  34.             // 记录关键行为  
  35.             if(meta.getData().get(SatApiConstant.KEY_ACTION) == null){  
  36.                 meta.getData().put(SatApiConstant.KEY_ACTION, keyAction);  
  37.             }  
  38.             list.add(meta);  
  39.         }  
  40.           
  41.         // 排序 ,以时间戳为key。考虑到可能出现时间戳一致的情况,所以不能用map。考虑到如果log要push到其他平台或者服务上,  
  42.         // 该切片应该先保证自身有序而不能完全依赖于下一个bundle来处理  
  43.         Collections.sort(list, new Comparator<DeimosSatelliteRequest>() {  
  44.             @Override  
  45.             public int compare(DeimosSatelliteRequest o1, DeimosSatelliteRequest o2) {  
  46.                 if (o1.getTimestamp().longValue() >= o2.getTimestamp()) {  
  47.                     return -1;  
  48.                 } else {  
  49.                     return 1;  
  50.                 }  
  51.             }  
  52.         });   
  53.         logger.info("[deimos-satellite]common amqp collctor prepare to push to next bundle............ key action: " + keyAction);        
  54.         return list;  
  55.     }     
  56. }  
       
       2、对新写的bundle类加上配置声明。以下为最轻便的写法。如果需要额外定制其他bundle参数,参照上面的相关说明,进行定制即可。
 
Java代码   收藏代码
  1. <!-- 报警消息收集器,最简易参数声明。在切分成不同stage之后没有什么需要特别关注的潜在性能瓶颈时使用 -->  
  2. <bean id="alarmCollector" class="com.cc.deimos.satellite.core.collector.CommonAmqpCollectBundle">  
  3.         <property name="pubKeys" value="process.alarm.*" />  
  4.         <property name="subKeys" value="collect.alarm.*, collect.log.error" />  
  5. </bean>  

     

       3、启动服务。至此,你的bundle也就随着服务的启动就自动启动并开始工作了。

       以上描述的一个bundle启动的过程,当所需要处理的业务被合理拆解成数个bundle(也就是所谓的stage)之后,就可以形成一个完整的基于工作流的系统实现。以下为基于对来源A进行事件收集的简易报警系统,在被拆解为三个bundle之后的数据流:

       

 

6、框架改进空间

       目前框架仅仅提供了一个非常轻量的解决方案,并仅对AMQP进行了实现。后续可有如下几个改进升级的空间:

       1)构成bundle strategy center。各个bundle的decider(决策器)在决策过程中,可以依赖strategy center进行bundle的相关决策。

       2)支持bundle集群化、broker集群化,并引入相关策略(比如一致性哈希)来保证基于该框架的系统的高可用。可纳入bundle strategy center。

       3)框架进一步基于IOC思想,进行面向接口编程的改造和升级。


   文章出处:http://surlymo.iteye.com/blog/2001523


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值