Activiti内部实现中,各主要部件关系

image

对外,提供Service服务,它是无状态的。

这些Service包括:

  • protected RepositoryService repositoryService = new RepositoryServiceImpl();
  • protected RuntimeService runtimeService = new RuntimeServiceImpl();
  • protected HistoryService historyService = new HistoryServiceImpl();
  • protected IdentityService identityService = new IdentityServiceImpl();
  • protected TaskService taskService = new TaskServiceImpl();
  • protected FormService formService = new FormServiceImpl();
  • protected ManagementService managementService = new ManagementServiceImpl();

 

对service提供的服务,基本上每一个需要执行具体任务的方法,都有一个对应的Command实现与之对应。

  • 一般来说,一个Command对应一个完整事物;
  • 调用Command之前,都会经过CommandInterceptor,这个在初始化时就确定了,如:LogInterceptor >> CommandContextInterceptor >> CommandInvoker

 

如果Command不涉及节点相关内容,而是直接对数据库进行操作,则直接关联DB,入DbSqlSession的缓存;

否则,一般会通过ExecutionEntity来完成具体的操作,这里,封装了一些基本的原子操作,它们都是AtomicOperation的接口实现:

流程实例类:

  • AtomicOperationProcessStart
  • AtomicOperationProcessStartInitial
  • AtomicOperationProcessEnd

流程活动类:

  • AtomicOperationActivityStart
  • AtomicOperationActivityExecute
  • AtomicOperationActivityEnd

流程活动流转类:

  • AtomicOperationTransitionNotifyListenerEnd
  • AtomicOperationTransitionDestroyScope
  • AtomicOperationTransitionNotifyListenerTake
  • AtomicOperationTransitionCreateScope
  • AtomicOperationTransitionNotifyListenerStart

流程执行树清理类:

  • AtomicOperationDeleteCascade
  • AtomicOperationDeleteCascadeFireActivityEnd

 

其中,活动执行时,具体动作是由ActivityBehavior的实现类来决定的:

image

关于默认ID生成器

Activiti的默认生成器是DbIdGenerator,实际是一次从数据库中取一块数据,然后慢慢用,用完后再取。

/**
 * @author Tom Baeyens
 */
public class DbIdGenerator implements IdGenerator {

  protected int idBlockSize;
  protected long nextId = 0;
  protected long lastId = -1;
  
  protected CommandExecutor commandExecutor;
  protected CommandConfig commandConfig;
  
  public synchronized String getNextId() {
    if (lastId<nextId) {
      getNewBlock();
    }
    long _nextId = nextId++;
    return Long.toString(_nextId);
  }

  protected synchronized void getNewBlock() {
    IdBlock idBlock = commandExecutor.execute(commandConfig, new GetNextIdBlockCmd(idBlockSize));
    this.nextId = idBlock.getNextId();
    this.lastId = idBlock.getLastId();
  }
  • 在ProcessEngineConfiguration中定义的块默认大小100;
  • 在ProcessEngineComfigurationImpl中,完成初始化:
  // id generator /////////////////////////////////////////////////////////////
  
  protected void initIdGenerator() {
    if (idGenerator==null) {
      CommandExecutor idGeneratorCommandExecutor = null;
      if (idGeneratorDataSource!=null) {
        ProcessEngineConfigurationImpl processEngineConfiguration = new StandaloneProcessEngineConfiguration();
        processEngineConfiguration.setDataSource(idGeneratorDataSource);
        processEngineConfiguration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_FALSE);
        processEngineConfiguration.init();
        idGeneratorCommandExecutor = processEngineConfiguration.getCommandExecutor();
      } else if (idGeneratorDataSourceJndiName!=null) {
        ProcessEngineConfigurationImpl processEngineConfiguration = new StandaloneProcessEngineConfiguration();
        processEngineConfiguration.setDataSourceJndiName(idGeneratorDataSourceJndiName);
        processEngineConfiguration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_FALSE);
        processEngineConfiguration.init();
        idGeneratorCommandExecutor = processEngineConfiguration.getCommandExecutor();
      } else {
        idGeneratorCommandExecutor = getCommandExecutor();
      }
      
      DbIdGenerator dbIdGenerator = new DbIdGenerator();
      dbIdGenerator.setIdBlockSize(idBlockSize);
      dbIdGenerator.setCommandExecutor(idGeneratorCommandExecutor);
      dbIdGenerator.setCommandConfig(getDefaultCommandConfig().transactionRequiresNew());
      idGenerator = dbIdGenerator;
    }
  }

注意:此处对getNextId()方法加了synchronize关键字,它在单机部署下,确定不会出现网上分析的什么ID重复问题。

关于task的start_time_字段取值问题

在TaskEntity中:

  /** creates and initializes a new persistent task. */
  public static TaskEntity createAndInsert(ActivityExecution execution) {
    TaskEntity task = create();
    task.insert((ExecutionEntity) execution);
    return task;
  }

  public void insert(ExecutionEntity execution) {
    CommandContext commandContext = Context.getCommandContext();
    DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
    dbSqlSession.insert(this);
    
    if(execution != null) {
      execution.addTask(this);
    }
    
    commandContext.getHistoryManager().recordTaskCreated(this, execution);
  }

/*
* 。。。。
*/

  /**  Creates a new task.  Embedded state and create time will be initialized.
   * But this task still will have to be persisted. See {@link #insert(ExecutionEntity)}. */
  public static TaskEntity create() {
    TaskEntity task = new TaskEntity();
    task.isIdentityLinksInitialized = true;
    task.createTime = ClockUtil.getCurrentTime();
    return task;
  }

由此知道,活动的时间是由ClockUtil.getCurrentTime()决定的。再来看看CockUtil的源码:

/**
 * @author Joram Barrez
 */
public class ClockUtil {
  
  private volatile static Date CURRENT_TIME = null;
  
  public static void setCurrentTime(Date currentTime) {
    ClockUtil.CURRENT_TIME = currentTime;
  }
  
  public static void reset() {
    ClockUtil.CURRENT_TIME = null;
  } 
  
  public static Date getCurrentTime() {
    if (CURRENT_TIME != null) {
      return CURRENT_TIME;
    }
    return new Date();
  }

}

注意:

因为可能多线程情况,而且只有set,不会执行类似++,--这样的操作,所以这里用volatile关键字完全满足需要。

默认实现在分布式中问题

在上面介绍了DbIdGenerator以及ClockUtil之后,可以清楚明白他们的原理,那么在分布式部署中,如果还是使用这种默认的实现而不加以改善,会出现什么问题。

1.DbIdGenerator的getNextId()/getNewBlock()两个方法,在分布式主机中,synchronize不能顺利实现锁控制;

2.ClockUtil严重依赖容器所在服务器时间,但是分布式主机的时间不可能达到完全的同步;

3.在分布式主机中,对同一个任务,可以同时执行,因为他们都是DbSqlSession缓存,不会立马入库;也就是说,可能存在一个任务被同时自行两次的情况。

 

对1,2两点分析,基本上是确定会存在的,至于第三点,限于猜想,不知道实际是否有相关的解决策略,目前对activiti关于此处的设置猜想还没有完全弄清楚。

 

其实之所以那么在乎任务Id以及任务执行时间,主要是在流程跟踪图中,需要根据有序的历史任务结果集模仿重现走过的路径,而做到有序,这两个要素是非常关键的。

对分布式应用,如果同库,那ID的生成问题都会是一个问题,常见的解决方式是把他扔给数据库去解决,比如一个序列、一个类似序列的自定义函数等都是不错的选择。

当然,放到DB中以后,那么频繁访问数据库,activiti中设计的blocksize也就基本失效了,这个也算是衡量的结果吧。

实际问题分析

image

上述数据,是在生产上出现的问题数据,环境是为了负载均衡做了两个应用Server,同时连接一个DB;

从数据可以分析出“活动节点会在双机中运行”;

61011(A) >> 60852(B) >> 60866(B) >> 61022(A) >> 61028(A) >> 60889(B) >> 60893(B)

A机器上的61011执行完毕以后,事件如何转到B机器上的60852,这个还不明白,待解决!!

posted @ 2016-11-06 19:49 寻梦丄天涯 阅读(4640) 评论(0) 推荐(0) 编辑
摘要: 流程跟踪图-推导算法 工作中使用activiti实现流程图相关业务,但是上线后遇到问题,偶尔流程图出不来。查阅了一下画流程图的实现,基本上是参见:activiti-流程图颜色变化之一篇。 核心类,参见:ActivitiHistoryGraphBuilder、Graph; 实际上,算法思路是通过act_hi_actinst来查找到走过的历史任务节点,并按开始时间排序。对这些历史节点遍历,分别查找每个... 阅读全文
posted @ 2016-11-03 10:57 寻梦丄天涯 阅读(2649) 评论(1) 推荐(0) 编辑
摘要: Gradle概述与环境搭建 Gradle介绍 Gradle是一个基于JVM的构建工具,它提供了: 像Ant一样,通用灵活的构建工具 可以切换的,基于约定的构建框架 强大的多工程构建支持 基于Apache Ivy的强大的依赖管理 支持maven, Ivy仓库 支持传递性依赖管理,而不需要远程仓库或者是pom.xml和ivy.xml配置文件。 对Ant的任务做了很好的集成 基于Groo... 阅读全文
posted @ 2016-11-01 07:17 寻梦丄天涯 阅读(222) 评论(0) 推荐(0) 编辑
摘要: org.apache.ibatis.jdbc.AbstractSQL org.apache.ibatis.jdbc.AbstractSQL 抽象泛型类,它主要用于解决书写SQL时经常多了或者少了and、or、where、括号等等内容问题。 实际上,它的原理说起来也简单,就是把SQL拆分成多个部分,然后再根据语句类型来进行拼装。 核心方法: @Override public String to... 阅读全文
posted @ 2016-10-31 16:59 寻梦丄天涯 阅读(6454) 评论(0) 推荐(1) 编辑
摘要: 本系列教程均源于南柯梦,经好库文摘转发,感谢南柯梦的辛苦整理。 深入迁出mybatis系列 深入浅出Mybatis系列(一)---Mybatis入门 深入浅出Mybatis系列(二)---配置简介(mybatis源码篇) 深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇) 深入浅出Mybatis系列(四)---配置详解之type... 阅读全文
posted @ 2016-10-31 08:17 寻梦丄天涯 阅读(205) 评论(0) 推荐(0) 编辑
摘要: 摘要: java 1.5开始引入了注解和反射,正确的来说注解是反射的一部分,没有反射,注解无法正常使用,但离开注解,反射依旧可以使用,因此来说,反射的定义应该包含注解才合理一些。 定义一个注解类 定义一个UserAnnotation注解类@Target(value = { ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) publi... 阅读全文
posted @ 2016-10-31 07:46 寻梦丄天涯 阅读(207) 评论(0) 推荐(0) 编辑
摘要: 内容摘自:springmvc与spring上下文的关系 原理区别 一直不清楚springmvc-servlet.xml配置与spring.xml两个配置文件出现的上下文关系。今天找到一上面的文章,倒是突然间清楚了。 具体来说,spring.xml上下文与springmvc-servlet.xml上下文是父子容器上下文的关系,他们有以下关系特点: spring.xml定义的上下文是父上下文,不能调... 阅读全文
posted @ 2016-10-30 07:55 寻梦丄天涯 阅读(5173) 评论(0) 推荐(0) 编辑
摘要: todo。。。 8.开发流程部署功能 9.开发简单任务待办功能 10.开发简单任务办理功能 11.开发页面activiti流程跟踪图形展现功能 12.集成网页流程设计器 阅读全文
posted @ 2016-10-29 19:14 寻梦丄天涯 阅读(130) 评论(0) 推荐(0) 编辑
摘要: 1.配置DispatcherServlet 在web.xml文件中,添加: springmvc org.springframework.web.servlet.DispatcherServlet 1 springmvc / 上述配置中,默认会在WEB-INF/目录下,查找s... 阅读全文
posted @ 2016-10-29 16:34 寻梦丄天涯 阅读(238) 评论(0) 推荐(0) 编辑
摘要: activiti helloworld activiti的入门实践文章,重点在于动手做,要解决的是怎么做的问题。只有知道了怎么做后,才具有实际动手能力,才算对这门技术有一个初步掌握;至于更深入细化的知道它的设计理念、实现细节等,那是有时间深入时候再进行的工作了。 任务目标: 会用eclipse的ac 阅读全文
posted @ 2016-10-29 11:33 寻梦丄天涯 阅读(1291) 评论(0) 推荐(0) 编辑
点击右上角即可分享
微信分享提示