在Jboss集群中jBPM工作流引擎的可伸缩性及性能
作者:Szymon Zeslawski
翻译:snowfox
原文地址:http://www.theserverside.com/tt/articles/article.tss?l=WorkflowEngineJBossCluster
3/2009
任务/范围
这篇文章的目的是展示怎样通过调整jBPM的配置并且利用分布式的TreeCache将它安装到一个JBoss的集群中而取得它的近乎线性的伸缩性能。读者将被按照有效地集群jBPM所需要的所有步骤所指引-从集群的安装到精确地调整jBPM配置-并且提供性能测试报告和可以取得最好性能的各种不同的小技巧和诀窍。
摘要
jBPM是一个很强大的工作流引擎-健壮的,可扩展的并且快速的。然而,如果我们需要比一个服务器能够提供的更好的性能有什么样的可能性?集群是立即涌到我们头脑中的的方案。但是它可以很快地并且很容易地切实可行么,并且更重要的是,它可以产生期望的结果么?这篇文章将带你浏览将一个分布式的TreeCache与JBoss集群一起安装,并且调整jBPM来交付它的完整的性能的整个过程。你也可以获知通过标准的配置可以获得你期望的多大程度的改进。
jBPM介绍
jBPM是一个工作流引擎,可以使它的用户轻松地管理各种不同的业务流程。这些业务流程以一个包含活动的流程定义为基础。相似或相关的活动可以被组合成scopes。活动可以在后边的执行中被手动或者定时触发。在这里JobExecutors发挥作用。它轮询数据库检查是否有工作准备好可以被执行。无论一个工作是否准备好,它的执行都是约定好的,JobExecutor在锁住当前工作然后执行任何与这个工作相关的动作。例如一个定时执行被自动升级地调用,这是本篇文章的主要焦点。
业务背景
我们已经基于一堆尖端技术包括jBPM开发了一个业务流程管理的应用。在我们系统中主要的业务单元承担名称呼叫。我们的客户将主要的关注点放在工作流引擎的伸缩性和性能上,因此我们在测试和调优集群化的jBPM配置上做了很多工作。我的目标是通过集群和调整自动化的呼叫升级来满足客户的关于安装大小和使用特性的需求。
测试环境概述
测试环境包括4台独立运行应用的机器,都连接到一个Oracle 10g的数据库。服务器上运行Ubuntu 8.10 Linux和JBoss 4.2.3GA,每一个都有一个4核的2.5GHz的Q9300 Intel处理器和4GB 的RAM内存。JBoss互相之间使用JGroups协议通信。Library的版本中文章的最后被给出。
系统准备和集群安装
为了避免jBPM的工作所有权和将来某个时候的执行发生严重的问题,必须采取以下步骤:
./etc/host需要有一个同Jboss服务器的IP地址一致的实体绑定到(这应用中集群的每个节点上)使用工作区,此处描述了示例1:
例如,InetAddress.getLocalHost() 方法返回一个非回路的地址
.所有的集群节点通过使用ntpd有它们自己的同步锁机制
JBoss集群是容易安装的,使用“all”配置(用来开发和测试目标)或者用来自“all”的下述文件来丰富你的配置都是足够了:
Log4j的配置
我们已经确认了同我们新的安装相关的所看到的消息。下边线框内的jboss-log4j.xml将允许我们来验证集群是否正确地工作而且不会错漏我们控制台上冗长的输出:
<logger name="org.jboss.cache"> <level value="INFO"/> </logger> <!-- Keep chatty support libraries as silent as possible --> <logger name="org.jgroups.protocols.UDP"> <level value="ERROR"/> </logger> <!-- Limit the org.jgroups logger to WARN as its INFO is verbose --> <logger name="org.jgroups"> <level value="WARN"/> </logger> <!-- Clustering logging --> <logger name="org.jgroups"> <level value="INFO" /> </logger> <logger name="org.jboss.ha"> <level value="INFO" /> </logger>
定时启动JBoss
现在我们可以利用./run.sh –c<config_name> -b<bind_address>命令来启动JBoss了。在控制台上你应该可以看到与这些相似的信息:
12:48:23,783 INFO jgroups.JChannel| JGroups version: 2.4.1 SP-4 12:48:24,098 INFO protocols.FRAG2| frag_size=60000, overhead=200, new frag_size=59800 12:48:24,102 INFO protocols.FRAG2| received CONFIG event: {bind_addr=/10.10.1.43} 12:48:24,187 INFO HAPartition.DefaultPartition| Initializing 12:48:24,232 INFO STDOUT| ------------------------------------------------------- GMS: address is 10.10.1.43:46941 ------------------------------------------------------- 12:48:26,335 INFO HAPartition.DefaultPartition| Number of cluster members: 4 12:48:26,335 INFO HAPartition.DefaultPartition| Other members: 3 12:48:26,335 INFO HAPartition.DefaultPartition| Fetching state (will wait for 30000 milliseconds): 12:48:26,393 INFO HAPartition.DefaultPartition| state was retrieved successfully (in 58 milliseconds) 12:48:26,449 INFO jndi.HANamingService| Started ha-jndi bootstrap jnpPort=1100, backlog=50, bindAddress=/10.10.1.43 12:48:26,457 INFO mingService$AutomaticDiscovery| Listening on /10.10.1.43:1102, group=230.0.0.4, HA-JNDI address=10.10.1.43:1100
你可以发现,一个具有4个节点的集群已经建立了,现在我们可以部署应用程序了。JBoss提供了一个农场化的机制,用来在一步中部署一个应用程序到所有的集群成员上。仅仅需要将你的EAR或者WAR扔到server/<config_name>/farm目录下,然后它会自动地被部署到所有的节点上。移除应用程序文件就会在各节点上卸载应用。
集群中的jBPM
jBPM3.2被设计为可以无缝地址集群中工作。所有的应用程序都是独立的,互相之间并不知晓。工作的分布通过数据库级别的锁和包含线程名称和主机信息的工作所有权来实现。
当一个工作准备执行并且没有被锁住,JobExecutors将会在其上放置一把锁。由于使用乐观锁,仅有他们当中的一个被完成,获得一个乐观锁异常的JobExecutors在获得新的工作之前将被暂停一段已经定义好的时间。
在多服务器环境中缓存jBPM
“二级缓存是JBoss jBPM实现的一个重要的方面。如果这个二级缓存不生效,JBoss jBPM与用其它技术实现的BPM引擎相比可能有一个验证的缺陷。”
--jBPM jPDL 用户手册
jBPM的实体类默认的是被缓存的。然而他们的缓存配置仅仅是适用本地部署的 – 他们使用一个非严格的读写策略,这是JBoss Cache所不支持的。你应该怎样将这个策略改为事务型呢?你不得不覆盖jBPM实体的Hibernate映射。通过在你的SessionFactory Bean中定义一个映射资源的列表,你可以手动实现这个功能。
<bean id="myAppSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource"> <ref bean="..."/> </property> <property name="mappingResources"> <list> <value>classpath*:com/yourcompany/jbpm/**/*.hbm.xml</value> <!--<value>classpath*:org/jbpm/**/*.hbm.xml</value>--> <value>classpath*:org/jbpm/bytes/ByteArray.hbm.xml</value> <value>classpath*:org/jbpm/context/def/ContextDefinition.hbm.xml</value> <!--<value>classpath*:org/jbpm/context/def/VariableAccess.hbm.xml</value>--> <value>classpath*:org/jbpm/context/exe/ContextInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/TokenVariableMap.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/VariableInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/variableinstance/ByteArrayInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/variableinstance/DateInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/variableinstance/DoubleInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/variableinstance/HibernateLongInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/variableinstance/HibernateStringInstance.hbm.xml </value> <value>classpath*:org/jbpm/context/exe/variableinstance/JcrNodeInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/variableinstance/LongInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/variableinstance/NullInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/exe/variableinstance/StringInstance.hbm.xml</value> <value>classpath*:org/jbpm/context/log/VariableLog.hbm.xml</value> <value>classpath*:org/jbpm/context/log/VariableCreateLog.hbm.xml</value> <value>classpath*:org/jbpm/context/log/VariableDeleteLog.hbm.xml</value> <value>classpath*:org/jbpm/context/log/VariableUpdateLog.hbm.xml</value> <value>classpath*:org/jbpm/context/log/variableinstance/ByteArrayUpdateLog.hbm.xml</value> <value>classpath*:org/jbpm/context/log/variableinstance/DateUpdateLog.hbm.xml</value> <value>classpath*:org/jbpm/context/log/variableinstance/DoubleUpdateLog.hbm.xml</value> <value>classpath*:org/jbpm/context/log/variableinstance/HibernateLongUpdateLog.hbm.xml </value> <value>classpath*:org/jbpm/context/log/variableinstance/HibernateStringUpdateLog.hbm.xml </value> <value>classpath*:org/jbpm/context/log/variableinstance/LongUpdateLog.hbm.xml</value> <value>classpath*:org/jbpm/context/log/variableinstance/StringUpdateLog.hbm.xml</value> <value>classpath*:org/jbpm/db/hibernate.queries.hbm.xml</value> <!--<value>classpath*:org/jbpm/file/def/FileDefinition.hbm.xml</value>--> <value>classpath*:org/jbpm/graph/action/MailAction.hbm.xml</value> <!--<value>classpath*:org/jbpm/graph/action/Script.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/graph/def/ProcessDefinition.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/graph/def/Node.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/graph/def/Transition.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/graph/def/Event.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/graph/def/Action.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/graph/def/SuperState.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/graph/def/ExceptionHandler.hbm.xml</value>--> <value>classpath*:org/jbpm/graph/exe/Comment.hbm.xml</value> <value>classpath*:org/jbpm/graph/exe/ProcessInstance.hbm.xml</value> <value>classpath*:org/jbpm/graph/exe/Token.hbm.xml</value> <value>classpath*:org/jbpm/graph/exe/RuntimeAction.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/ActionLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/NodeLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/ProcessInstanceCreateLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/ProcessInstanceEndLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/ProcessStateLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/SignalLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/TokenCreateLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/TokenEndLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/log/TransitionLog.hbm.xml</value> <value>classpath*:org/jbpm/graph/node/StartState.hbm.xml</value> <value>classpath*:org/jbpm/graph/node/EndState.hbm.xml</value> <!--<value>classpath*:org/jbpm/graph/node/ProcessState.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/graph/node/Decision.hbm.xml</value>--> <value>classpath*:org/jbpm/graph/node/Fork.hbm.xml</value> <value>classpath*:org/jbpm/graph/node/Join.hbm.xml</value> <value>classpath*:org/jbpm/graph/node/State.hbm.xml</value> <!--<value>classpath*:org/jbpm/graph/node/TaskNode.hbm.xml</value>--> <value>classpath*:org/jbpm/graph/node/MailNode.hbm.xml</value> <!--<value>classpath*:org/jbpm/instantiation/Delegation.hbm.xml</value>--> <value>classpath*:org/jbpm/job/ExecuteActionJob.hbm.xml</value> <value>classpath*:org/jbpm/job/ExecuteNodeJob.hbm.xml</value> <value>classpath*:org/jbpm/job/Job.hbm.xml</value> <value>classpath*:org/jbpm/job/Timer.hbm.xml</value> <value>classpath*:org/jbpm/logging/log/ProcessLog.hbm.xml</value> <value>classpath*:org/jbpm/logging/log/MessageLog.hbm.xml</value> <value>classpath*:org/jbpm/logging/log/CompositeLog.hbm.xml</value> <!--<value>classpath*:org/jbpm/module/def/ModuleDefinition.hbm.xml</value>--> <value>classpath*:org/jbpm/module/exe/ModuleInstance.hbm.xml</value> <value>classpath*:org/jbpm/scheduler/def/CreateTimerAction.hbm.xml</value> <value>classpath*:org/jbpm/scheduler/def/CancelTimerAction.hbm.xml</value> <!--<value>classpath*:org/jbpm/taskmgmt/def/TaskMgmtDefinition.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/taskmgmt/def/Swimlane.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/taskmgmt/def/Task.hbm.xml</value>--> <!--<value>classpath*:org/jbpm/taskmgmt/def/TaskController.hbm.xml</value>--> <value>classpath*:org/jbpm/taskmgmt/exe/TaskMgmtInstance.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/exe/TaskInstance.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/exe/PooledActor.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/exe/SwimlaneInstance.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/log/TaskLog.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/log/TaskCreateLog.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/log/TaskAssignLog.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/log/TaskEndLog.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/log/SwimlaneLog.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/log/SwimlaneCreateLog.hbm.xml</value> <value>classpath*:org/jbpm/taskmgmt/log/SwimlaneAssignLog.hbm.xml</value> </list> </property> </bean>
这个列表除了被缓存的以外(他们被注释掉了)包含了所有的被匹配的jBPM实体。为了保持映射的完整性,你需要拷贝这些被注释掉的hbm.xml文件到你的项目中并且改变它们的缓存策略定义,从:
<cache usage="nonstrict-read-write"/>
到:
<cache usage="transactional"/>
在一个包中保证这些文件被定义在mappingResources列表的第一行,它们会被自动地读取。
请注意,事务型的缓存策略仅能在你的应用中与一个JTA事物管理器一起工作!你不得不定义Hibernate属性hibernate.transaction.manager_lookup_class,使它指向你的事物管理器的查找类。
使用JBoss TreeCache作为hibernate的二级缓存
对于在你的应用中准备使用TreeCache需要一些步骤但不是很长的步骤。但是不要害怕,毕竟它不是非常复杂的。首先,你不得不创建一个缓存的MBean并且在一个名称为jboss-service.xml的文件保存它的定义:
<mbean code="org.jboss.cache.TreeCache" name="jboss.cache:service=TreeCache"> <depends>jboss:service=Naming</depends> <depends>jboss:service=TransactionManager</depends> <!-- Configure the TransactionManager --> <attribute name="TransactionManagerLookupClass">org.jboss.cache.JBossTransactionManagerLookup</attribute> <!-- Node locking scheme : PESSIMISTIC (default) OPTIMISTIC --> <attribute name="NodeLockingScheme">OPTIMISTIC</attribute> <!-- Node locking isolation level : SERIALIZABLE REPEATABLE_READ (default) READ_COMMITTED READ_UNCOMMITTED NONE (ignored if NodeLockingScheme is OPTIMISTIC) --> <attribute name="IsolationLevel">REPEATABLE_READ</attribute> <!-- Valid modes are LOCAL REPL_ASYNC REPL_SYNC INVALIDATION_ASYNC INVALIDATION_SYNC --> <attribute name="CacheMode">REPL_ASYNC</attribute> <!-- Whether each interceptor should have an mbean registered to capture and display its statistics. --> <attribute name="UseInterceptorMbeans">true</attribute> <!-- Name of cluster. Needs to be the same for all clusters, in order to find each other --> <attribute name="ClusterName">Cache-Cluster</attribute> <attribute name="ClusterConfig"> <config> <!-- UDP: if you have a multihomed machine, set the bind_addr attribute to the appropriate NIC IP address --> <!-- UDP: On Windows machines, because of the media sense feature being broken with multicast (even after disabling media sense) set the loopback attribute to true --> <UDP mcast_addr="228.1.2.3" mcast_port="45566" ip_ttl="64" ip_mcast="true" mcast_send_buf_size="150000" mcast_recv_buf_size="80000" ucast_send_buf_size="150000" ucast_recv_buf_size="80000" loopback="false"/> <PING timeout="2000" num_initial_members="3" up_thread="false" down_thread="false"/> <MERGE2 min_interval="10000" max_interval="20000"/> <FD shun="true" up_thread="true" down_thread="true"/> <VERIFY_SUSPECT timeout="1500" up_thread="false" down_thread="false"/> <pbcast.NAKACK gc_lag="50" max_xmit_size="8192" retransmit_timeout="600,1200,2400,4800" up_thread="false" down_thread="false"/> <UNICAST timeout="600,1200,2400" window_size="100" min_threshold="10" down_thread="false"/> <pbcast.STABLE desired_avg_gossip="20000" up_thread="false" down_thread="false"/> <FRAG frag_size="8192" down_thread="false" up_thread="false"/> <pbcast.GMS join_timeout="5000" join_retry_timeout="2000" shun="true" print_local_addr="true"/> <pbcast.STATE_TRANSFER up_thread="false" down_thread="false"/> </config> </attribute> <!-- The max amount of time (in milliseconds) we wait until the initial state (ie. the contents of the cache) are retrieved from existing members in a clustered environment --> <attribute name="InitialStateRetrievalTimeout">5000</attribute> <!-- Number of milliseconds to wait until all responses for a synchronous call have been received. --> <attribute name="SyncReplTimeout">10000</attribute> <!-- Max number of milliseconds to wait for a lock acquisition --> <attribute name="LockAcquisitionTimeout">15000</attribute> <!-- Name of the eviction policy class. --> <attribute name="EvictionPolicyClass">org.jboss.cache.eviction.LRUPolicy</attribute> <!-- Specific eviction policy configurations. This is LRU --> <attribute name="EvictionPolicyConfig"> <config> <attribute name="wakeUpIntervalSeconds">5</attribute> <!-- Cache wide default --> <region name="/_default_"> <attribute name="maxNodes">5000</attribute> <attribute name="timeToLiveSeconds">1000</attribute> <!-- Maximum time an object is kept in cache regardless of idle time --> <attribute name="maxAgeSeconds">120</attribute> </region> <region name="/org/jboss/data"> <attribute name="maxNodes">5000</attribute> <attribute name="timeToLiveSeconds">1000</attribute> </region> <region name="/org/jboss/test/data"> <attribute name="maxNodes">5</attribute> <attribute name="timeToLiveSeconds">4</attribute> </region> </config> </attribute> <!-- New 1.3.x cache loader config block --> <attribute name="CacheLoaderConfiguration"> <config> <!-- if passivation is true, only the first cache loader is used; the rest are ignored --> <passivation>false</passivation> <!-- <preload>/a/b, /allTempObjects, /some/specific/fqn</preload> --> <shared>false</shared> <!-- we can now have multiple cache loaders, which get chained <cacheloader> <class>org.jboss.cache.loader.FileCacheLoader</class> <properties> location=/tmp/cacheFileStore </properties> <async>false</async> <fetchPersistentState>true</fetchPersistentState> <ignoreModifications>false</ignoreModifications> <purgeOnStartup>false</purgeOnStartup> </cacheloader> --> <cacheloader> <class>org.jboss.cache.loader.JDBCCacheLoader</class> <properties> cache.jdbc.table.name=jbosscache cache.jdbc.table.create=true cache.jdbc.table.drop=true cache.jdbc.table.primarykey=jbosscache_pk cache.jdbc.fqn.column=fqn cache.jdbc.fqn.type=varchar(255) cache.jdbc.node.column=node cache.jdbc.node.type=blob cache.jdbc.parent.column=parent cache.jdbc.driver=oracle.jdbc.driver.OracleDriver cache.jdbc.url=jdbc:oracle:thin:@[hostname]:1521:orcl cache.jdbc.user=[username] cache.jdbc.password=[password] </properties> <async>true</async> <fetchPersistentState>false</fetchPersistentState> <ignoreModifications>false</ignoreModifications> <purgeOnStartup>false</purgeOnStartup> </cacheloader> </config> </attribute> </mbean>
然后将这个文件按照下边的结构打包到一个SAR文档中(本质上就是一个仅仅有不同名字的ZIP):
jbosscache.sar/
- META-INF/
- jboss-service.xml
你还要在你的EAR的根目录中打包这个SAR,并且在你的META-INF/jboss-app.xml中增加下面的行:
<module> <service>jbosscache.sar</service> </module>
在启动或后边使用的过程中,你可能会遭遇到任何序列化的问题,你可以通过增加下边的java虚拟机选项来将JBoss的序列化改为标准的Java序列化:
-Dserialization.jboss=false
为了能够使用JBoss Cache作为它的缓存提供者(假设你使用Maven来构建)你的应用程序应该依赖于下边的配置:
<dependencies> <dependency> <groupId>org.jboss.cluster</groupId> <artifactId>hibernate-jbc-cacheprovider</artifactId> <version>1.0.1.GA</version> <exclusions> <exclusion> <groupId>hibernate</groupId> <artifactId>hibernate3</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-common</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-jmx</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-system</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-j2ee</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-transaction</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jbosscache</artifactId> <version>3.3.1.GA</version> <exclusions> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-cache</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-system</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-common</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-minimal</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>jboss-j2se</artifactId> </exclusion> <exclusion> <groupId>concurrent</groupId> <artifactId>concurrent</artifactId> </exclusion> <exclusion> <groupId>jgroups</groupId> <artifactId>jgroups-all</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
注意:其中被排除的项目用来阻止与我们项目中已经包含或者JBoss自己提供的的libraries的发生版本不匹配。你可能必须为你的应用来手动调整他们。
如果没有使用Maven,你不得不手动地去下载这些提到的libraries并且把它们包含进的类路径。
现在是时候配置Hibernate的缓存了。有几个选项就足够了:
hibernate.cache.provider_class=org.jboss.hibernate.jbc.cacheprovider.JmxBoundTreeCacheProvider hibernate.treecache.mbean.object_name=jboss.cache:service=TreeCache hibernate.cache.use_second_level_cache=true hibernate.cache.use_query_cache=false hibernate.transaction.manager_lookup_class=<your_transaction_manager_class>
做完了所有这些事情,你就可以通过@Cache标记它们来缓存你的实体类了。记住集群的TreeCache仅仅支持只读型的和事务型的缓存策略。
你最新创建的缓存能够通过两种方式来监控 – 通过我们已经创建的Hibernate统计模块或者通过TreeCache JMX MBean。
使用Hibernate统计,需要在你的POM中增加一个附加的依赖:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jmx</artifactId> <version>3.3.1.GA</version> </dependency>
为了使能统计收集和输出,下述配置必须放进你的Spring Context文件中:
<bean id="jmxExporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="Hibernate:name=statistics"> <ref local="statisticsBean"/> </entry> </map> </property> </bean> <bean id="statisticsBean" class="org.hibernate.jmx.StatisticsService"> <property name="statisticsEnabled"> <value>true</value> </property> <property name="sessionFactory"> <ref local="hibernateSessionFactory"/> </property> </bean>
此处的“hibernateSessionFactory”是session factory Spring bean的ID。通过这个改变,Hibernate统计模块就可以通过JMX使用了。
你可以通过完全合格的名称(被称为二级缓存区域),输入比率,命中和失效次数来监控缓存实体,从而验证缓存是否按照期望的工作了。经过正确地缓存jBPM的片刻操作之后,应该会取得一个很高的命中/失效比率,就像从JConsole屏幕上看到的:
调整jBPM的性能
jBPM按照默认的配置是可以运行的,但是这样仅提供了它潜在性能的一小部分。下边这张图演示了呼叫升级时间是怎样随着后继节点的增加而下降的。在我测试的包含1000个呼叫的场景中,每一个都自动地升级2次 – 这导致总共2000个升级。每个升级都导致数据库更新。所有的结果都可以作为例证,在不同的测试条件下会有一些波动。
我们一直在寻找取得近乎线性的伸缩性能。线性伸缩性,是与服务器资源相关的,这意味着对于一个稳定的负载,随着资源的增加,性能是以一个稳定的比率来提高的。
左边的图表基于线性加速度显示了真实的呼叫升级时间与理论时间之间的比较。右边的图表比较了真实的加速度与理论的线性加速度。
这些结果通过标准的jBPM配置来收集的 – 1个JobExecutor线程,10秒的idleInterval ,1个小时的maxIdleInterval ,这意味着仅仅显示在默认的安装情况下jBPM是怎样来衡量的。这并不坏,但加速的因素会更高。
现在,让我们来对配置文件做点小改动。jBPM有两个名称为idleInterval 和maxIdleInterval 的选项,都我们是有意义的。当一个异常被JobExecutor扔出时,它暂停一段在idleInterval 定义的时间,然后按照双倍时间来增加直到它到达maxIdleInterval。对我们来说不幸的是,每次一个乐观锁冲突被探测到后都会扔出一个StaleObjectStateException ,这种情况在伴随着很多的并发JobExecutors试图获取一个工作而发生的很频繁。为了取得一个更高的并发比率降低idleInterval 和maxIdleInterval两个值是很重要的。下边是将idleInterval 降低到500毫秒和将andmaxIdleInterval 降低到1000毫秒的结果:
左边的图表显示了基于线性加速度的真实的呼叫升级时间与理论的时间之间的比较。右边的图表比较了真实的加速度与理论的线性加速度。
你可以看到现在加速曲线已经很接近于最佳了。让我们看看,如果我们将整个时间曲线向下转换。为了增加工作流引擎的吞吐量,你可以改变每个机器的JobExecutor线程的数量。我已经执行了与以前一样的性能测试,但是这次仅在4个节点上增加线程的数量并且尝试打开或关闭缓存。缓存增加了15-23%的吞吐量,但是最重要的获得是来自于增加线程的数量:
这终于使我们获得一些真正的高性能。随着使能缓存将线程数量增加到20个,吞吐量与标准的配置相比突飞猛进到了340%。
我们可以看到这每个机器上跑20个线程,我们已经达到了饱和点,进一步增加这个值不会产生更好的结果了。如果数据库处在来自于应用程序的其它部分的稳定负载下,缓存可能会带来更光明的前景。
结论
你已经看到了一个完整的对jBPM集群和调优的研究。最终的判断是jBPM是一个非常有效率的工作流引擎,为了获得最好的效果它仅需要调整正确的配置。通过增加3个服务器和调校jBPM的配置,与一个服务器的默认安装相比我们能够增加16倍的吞吐量。如果你需要增加你的工作流的吞吐量,我建议你按照下边的顺序进行修改:
一个必须被记住的事情是 – 数据通常迟早在某些点上是一个性能瓶颈。毕竟,jBPM是基于Hibernate的。因此,如果你的努力没有带来期望的效果,考虑调整/集群你的数据库。
链接
Library versions
jBPM-jPDL: 3.2.2
Hibernate: 3.3.1
JBoss: 4.2.3GA
TreeCache: 1.4.1SP9
Hibernate JBossCache provider: 1.0.1GA
关于作者
Szymon Zeslawski目前作为Consol 咨询及解决方案的高级开发人员,开发尖端的业务解决方案。毕业于在波兰克拉科夫的AGH科技大学,他从2002年开始就从事java技术。他擅长的专业领域集中于三年的专业经验,覆盖了不同的web,手机和人机工程学和性能方面的企业技术。When not at computer, he stretches his mind and body training Chuo Jiao or chases ghosts from behind the steering wheel.(这一句,偶实在不会翻译了)。