一、運行環境
以下所有的描述都是基於Activiti的5.20.0.1版本
1 public interface ProcessEngine extends EngineServices { 2 3 /** the version of the activiti library */ 4 public static String VERSION = "5.20.0.1"; 5 6 /** The name as specified in 'process-engine-name' in 7 * the activiti.cfg.xml configuration file. 8 * The default name for a process engine is 'default */ 9 String getName(); 10 11 void close(); 12 }
二、Activiti不支持分布的原因分析
- 在Activiti工作流的act_ge_property表中通常情況下有3條記錄:
- next.dbid
- schema.history
- schema.version
其中next.dbid對應的值為數據庫中當前最近一次增長后的最大記錄id,每次增長的步長為2500,
1 protected int idBlockSize = 2500; (在ProcessEngineConfiguration類中)
- Activiti中所有的id(如:Task的id,Execution的id,ProcessInstance的id等)都是通過IdGenerator來生成的
1 /** 2 * generates {@link IdBlock}s that are used to assign ids to new objects. 3 * 4 * The scope of an instance of this class is process engine, 5 * which means that there is only one instance in one process engine instance. 6 * 7 * @author Tom Baeyens 8 * @author Joram Barrez 9 */ 10 public interface IdGenerator { 11 12 String getNextId(); 13 14 }
- IdGenerator的默認實現是
1 /** 2 * @author Tom Baeyens 3 */ 4 public class DbIdGenerator implements IdGenerator { 5 6 protected int idBlockSize; 7 protected long nextId = 0; 8 protected long lastId = -1; 9 10 protected CommandExecutor commandExecutor; 11 protected CommandConfig commandConfig; 12 13 public synchronized String getNextId() { 14 if (lastId<nextId) { 15 getNewBlock(); 16 } 17 long _nextId = nextId++; 18 return Long.toString(_nextId); 19 } 20 21 protected synchronized void getNewBlock() { 22 IdBlock idBlock = commandExecutor.execute(commandConfig, new GetNextIdBlockCmd(idBlockSize)); 23 this.nextId = idBlock.getNextId(); 24 this.lastId = idBlock.getLastId(); 25 }
從上面的代碼可以看出,獲取下一個id的方法是加鎖的,也就是在一台服務器上id的增長是沒有問題的,但是如果將Activiti部署在多台服務器上就會有兩個問題
- 從代碼的第17,18行可以看出id是本地自增,如果有多台服務器就會出現id相同的情況(由並發寫造成的);
- 獲取lastId的方法是操作同一個數據庫的,會有問題,代碼22中通過執行GetNextIdBlockCmd來獲取數據庫中的next.dbid的值,如果在多台服務器上由於一台服務器修改后,其他服務器無法知道
1 /** 2 * @author Tom Baeyens 3 */ 4 public class GetNextIdBlockCmd implements Command<IdBlock> { 5 6 private static final long serialVersionUID = 1L; 7 protected int idBlockSize; 8 9 public GetNextIdBlockCmd(int idBlockSize) { 10 this.idBlockSize = idBlockSize; 11 } 12 13 public IdBlock execute(CommandContext commandContext) { 14 PropertyEntity property = (PropertyEntity) commandContext 15 .getPropertyEntityManager() 16 .findPropertyById("next.dbid"); 17 long oldValue = Long.parseLong(property.getValue()); 18 long newValue = oldValue+idBlockSize; 19 property.setValue(Long.toString(newValue)); 20 return new IdBlock(oldValue, newValue-1); 21 } 22 }
三、解決方案
要想解決Activiti分布式的問題,就需要解決id生成的問題,也就是要自己實現IdGenerator接口,因此要有一個地方來生成一個全局唯一的id才行。
我在實際工作中是通過redis來實現的,redis也可以做集群,因此不需要考慮redis單點的問題,具體方案如下:
1 /** 2 * 分布式id生成器 3 * 4 * @version 1.0 5 * @author Pin Xiong 6 * @date 創建時間:2016年8月12日 下午3:22:09 7 */ 8 public class DistributedIdGenerator implements IdGenerator { 9 10 public DistributedIdGenerator(RedisService redisService) { 11 this.redisService = redisService; 12 } 13 14 private RedisService redisService; 15 16 @Override 17 public String getNextId() { 18 return String.format("%sX%s", D.formatDate(Constants.ACTIVITI_ENGINE_DISTRIBUTED_ID_PREFIX),this.redisService.incrby(Constants.ACTIVITI_ENGINE_DISTRIBUTED_ID_KEY, 1L)); 20 } 21 }
其中,D.formatDate(Constants.ACTIVITI_ENGINE_DISTRIBUTED_ID_PREFIX)是通過服務器時間來生成id的前綴,
重點是后面的this.redisService.incrby(MainRK.ACTIVITI_ENGINE_DISTRIBUTED_ID_KEY, 1L)
該方法是在redis中獲取key (也就是代碼中Constants.ACTIVITI_ENGINE_DISTRIBUTED_ID_KEY)對應的值,並自增1
在實際工作中通過該方案可以解決Activiti分布式問題。
如果其他同學有更好的方案,也希望可以一起分享,謝謝!
