背景
项目上协调办公、工作流使用的是Oracle Bpm的产品,最近被甲方爸爸投诉审批耗时长,希望能达到平均耗时1秒,甲方爸爸都要求了,没办法,于是就开始了一段痛苦的优化过程。本着普渡众生的想法,记录下本次优化的过程,希望能为减少类似的痛苦。
问题描述
环境描述
- Oracle Bpm 11.1.1.6 (后续简称BPM)
- Oracle ESB 12.1.3(后续简称OSB)、Oracle SOA Suit 11g(后续简称SOA)
代码调用路径
业务代码-->OSB-->SOA-->JAVA审批代码-->调用bpm提供的审批API
本文主要描述如何对JAVA审批代码做优化
相关代码
熟悉Oracle Bpm提供的审批API的同学对下面的代码应该不陌生,若是没接触过可以参考:
Oracle 官方教程
项目上的审批代码:
package cn.com.utility.bpm.utils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import oracle.bpel.services.workflow.StaleObjectException;
import oracle.bpel.services.workflow.WorkflowException;
import oracle.bpel.services.workflow.client.IWorkflowServiceClient;
import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;
import oracle.bpel.services.workflow.query.ITaskQueryService;
import oracle.bpel.services.workflow.task.ITaskService;
import oracle.bpel.services.workflow.task.model.CommentType;
import oracle.bpel.services.workflow.task.model.ObjectFactory;
import oracle.bpel.services.workflow.task.model.Task;
import oracle.bpel.services.workflow.verification.IWorkflowContext;
public class TaskService {
private IWorkflowServiceClient wfSvcClient = null;
private ITaskQueryService taskQueryService = null;
private ITaskService taskService = null;
private IWorkflowContext wfContext = null;
private static final String BPM_MANAGER_UN = "weblogic";
private static final String BPM_MANAGER_PW = "weblogic1";
private static final String SOA_URL = "t3://bpmtest1.wlj.com.cn:8001";
private static final String BPM_CLIENT_TYPE = "EJB";
private static final String BPM_LDAP_DOMAIN = "jazn.com";
private String username;
public TaskService() {
super();
}
public IWorkflowServiceClient getWorkflowServiceClient() {
if (wfSvcClient == null) {
Map properties =
new HashMap();
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,
BPM_CLIENT_TYPE);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL,
SOA_URL);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_PRINCIPAL,
BPM_MANAGER_UN);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_CREDENTIALS,
BPM_MANAGER_PW);
wfSvcClient =
WorkflowServiceClientFactory.getWorkflowServiceClient(WorkflowServiceClientFactory.REMOTE_CLIENT,
properties,
null);
}
return wfSvcClient;
}
public ITaskQueryService getTaskQueryService() {
if (taskQueryService == null) {
taskQueryService =
this.getWorkflowServiceClient().getTaskQueryService();
}
return taskQueryService;
}
public ITaskService getTaskService() {
if (taskService == null) {
taskService = this.getWorkflowServiceClient().getTaskService();
}
return taskService;
}
public IWorkflowContext getWorkflowContext() {
long start = System.currentTimeMillis();
if (wfContext == null) {
try {
wfContext =
getTaskQueryService().authenticate(BPM_MANAGER_UN, BPM_MANAGER_PW.toCharArray(),
BPM_LDAP_DOMAIN);
wfContext =
this.getTaskQueryService().authenticateOnBehalfOf(wfContext,
username);
} catch (WorkflowException e) {
return null;
}
}
return wfContext;
}
/**
* 审批操作
* @param taskId
* @param outcome
* @param comments
* @param params
* @return
* @throws StaleObjectException
* @throws WorkflowException
*/
public Task updateTaskOutcome(String taskId, String outcome,
String comments,
Map params) throws StaleObjectException,
WorkflowException {
Task task = this.getTaskById(taskId);
if (params != null) {
Iterator iter = params.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
if (entry.getKey() != null && entry.getValue() != null) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
this.updatePayloadElement(task, key, value);
}
}
}
ObjectFactory factory = new ObjectFactory();
CommentType commentType = factory.createCommentType();
commentType.setComment(comments);
commentType.setCommentScope("TASK");
commentType.setTaskId(taskId);
commentType.setAction(outcome);
task.addUserComment(commentType);
this.getTaskService().updateTask(this.getWorkflowContext(), task);
task = this.getTaskById(taskId);
this.getTaskService().updateTaskOutcome(this.getWorkflowContext(),
task, outcome);
return task;
}
public void updatePayloadElement(Task task, String name, String value) {
task.getPayloadAsElement().getElementsByTagName(name).item(0).setTextContent(value);
}
public Task getTaskById(String taskId) {
Task task = null;
try {
task = this.getTaskQueryService().getTaskDetailsById(this.getWorkflowContext(),
taskId);
} catch (WorkflowException e) {
e.printStackTrace();
}
return task;
}
}
问题排查
这个过程很痛苦的:......
(PS:不知道有没大佬能提供一套方法能减少这个过程的痛苦?欢迎大佬指点!)
思路
记录下排查的思路:
- 源码检查
- 打印每一段可能耗时长的代码耗时
- 部署、监控
- 导出日志、统计分析
结果
项目上的审批接口主要实现了两件事:
- 审批
- 记录审批历史
从日志上分析,记录审批历史(这一步其实可以不要,但是由于项目上的种种原因,无法使用BPM官方的审批历史表(WFHISTORY),审批历史需要单独写入一张历史表中)暂时可以不考虑。
所以下面的篇幅就主要开始描述如优化审批操作。
优化处理
日志分析
- 结合源码可以看出,获取context的总耗时达到4s左右
- 审批时似乎重复调用了一次BPM 审批的API
【getWorkflowContext】获取context总耗时 cost(ms):918
【getWorkflowContext】获取context总耗时 cost(ms):700
【getWorkflowContext】获取context总耗时 cost(ms):1957
【getWorkflowContext】获取context总耗时 cost(ms):300
【BpmService】taskId :0a28e3e0-8c75-4eac-ae0d-78a29783be83 审批操作 cost(ms):1917
【BpmService】taskId :0a28e3e0-8c75-4eac-ae0d-78a29783be83 记录审批历史 cost(ms):8
【BpmService】taskId :0a28e3e0-8c75-4eac-ae0d-78a29783be83 总耗时 cost(ms):5892【MHM.MHM_RECEIPT_HEADERS_T】
优化
- 缓存context
-
处理重复调用API的代码
- 增加参数(系统/流程标识)让没有设置Outcomes Require Comment的流程跳过。
- 测试后发现,BPM流程建模的时候如果设置了Outcomes Require Comment ,那么就不能直接调用updateTaskOutcome() 方法,需要先调用下述代码:
this.getTaskService().updateTask(this.getWorkflowContext(), task);
Bpm 建模时在humanTask上设置Outcomes Require Comment:
代码举例:
//标识流程模型上有没有设置OUTCOME必须注释
Boolean comment = true;
if (funName != null && funName.startsWith("CRM.")) {
//CRM系统不进行备注
comment = false;
}
//省略部分代码
if (comment) {
this.getTaskService().updateTask(this.getWorkflowContext(), task);
task = this.getTaskById(taskId);
}
相关代码
package cn.com.utility.bpm.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import oracle.bpel.services.workflow.StaleObjectException;
import oracle.bpel.services.workflow.WorkflowException;
import oracle.bpel.services.workflow.client.IWorkflowServiceClient;
import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;
import oracle.bpel.services.workflow.query.ITaskQueryService;
import oracle.bpel.services.workflow.task.ITaskService;
import oracle.bpel.services.workflow.task.model.CommentType;
import oracle.bpel.services.workflow.task.model.ObjectFactory;
import oracle.bpel.services.workflow.task.model.Task;
import oracle.bpel.services.workflow.verification.IWorkflowContext;
public class TaskService2 {
private IWorkflowServiceClient wfSvcClient = null;
private ITaskQueryService taskQueryService = null;
private ITaskService taskService = null;
private IWorkflowContext wfContext = null;
private static final String BPM_MANAGER_UN = "weblogic";
private static final String BPM_MANAGER_PW = "weblogic1";
private static final String SOA_URL = "t3://bpmtest1.wlj.com.cn:8001";
private static final String BPM_CLIENT_TYPE = "EJB";
private static final String BPM_LDAP_DOMAIN = "jazn.com";
private static IWorkflowContext managerWfContext = null;
private static Map tokenCache =
new Hashtable();
private String username;
public TaskService2() {
super();
}
public IWorkflowServiceClient getWorkflowServiceClient() {
if (wfSvcClient == null) {
Map properties =
new HashMap();
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,
BPM_CLIENT_TYPE);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL,
SOA_URL);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_PRINCIPAL,
BPM_MANAGER_UN);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_CREDENTIALS,
BPM_MANAGER_PW);
wfSvcClient =
WorkflowServiceClientFactory.getWorkflowServiceClient(WorkflowServiceClientFactory.REMOTE_CLIENT,
properties,
null);
}
return wfSvcClient;
}
public ITaskQueryService getTaskQueryService() {
if (taskQueryService == null) {
taskQueryService =
this.getWorkflowServiceClient().getTaskQueryService();
}
return taskQueryService;
}
public ITaskService getTaskService() {
if (taskService == null) {
taskService = this.getWorkflowServiceClient().getTaskService();
}
return taskService;
}
private IWorkflowContext getIWorkflowContext() {
String logTag = "【getWorkflowContext】";
long totalStartTime = System.currentTimeMillis();
long totalEndTime = 0;
long startTime = 0;
long endTime = 0;
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
TokenCache token = tokenCache.get(username);
if (token == null || token.isTimeout()) {
System.out.println(logTag + "【" + df.format(new Date()) + "】" +
"create new context cache:" + username);
try {
if (managerWfContext == null) {
startTime = System.currentTimeMillis();
managerWfContext =
getTaskQueryService().authenticate(BPM_MANAGER_UN,
BPM_MANAGER_PW.toCharArray(),
BPM_LDAP_DOMAIN);
endTime = System.currentTimeMillis();
System.out.println(logTag + "【" + df.format(new Date()) +
"】" + " 获取managerWfContext cost(ms):" +
(endTime - startTime));
}
startTime = System.currentTimeMillis();
wfContext =
this.getTaskQueryService().authenticateOnBehalfOf(managerWfContext,
username);
endTime = System.currentTimeMillis();
System.out.println(logTag + "【" + df.format(new Date()) + "】" +
" 获取wfContext cost(ms):" +
(endTime - startTime));
startTime = System.currentTimeMillis();
if (token == null) {
token = new TokenCache();
token.setWorkflowContext(wfContext);
tokenCache.put(username, token);
} else {
token.updateToken(wfContext);
}
endTime = System.currentTimeMillis();
System.out.println(logTag + "【" + df.format(new Date()) + "】" +
" 处理缓存 cost(ms):" + (endTime - startTime));
System.out.println(logTag + "【" + df.format(new Date()) + "】" +
"WorkflowContext Initialization Completed!");
} catch (WorkflowException e) {
System.out.println(logTag + "【" + df.format(new Date()) + "】" +
"获取IWorkflowContext报错");
System.out.println(e.getMessage());
return null;
}
} else {
System.out.println(logTag + "【" + df.format(new Date()) + "】" +
"get context from cache:" + username);
wfContext = token.getWorkflowContext();
}
totalEndTime = System.currentTimeMillis();
System.out.println(logTag + "【" + df.format(new Date()) + "】" +
" 获取context总耗时 cost(ms):" +
(totalEndTime - totalStartTime));
return wfContext;
}
public IWorkflowContext getWorkflowContext() {
String logTag = "【getWorkflowContext】";
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
IWorkflowContext context = getIWorkflowContext();
//报错或者超时后增加一次重试
if (context == null) {
System.out.println(logTag + "【" + df.format(new Date()) + "】" +
"重新获取IWorkflowContext");
managerWfContext = null;
context = getIWorkflowContext();
}
return context;
}
/**
* 审批操作
* @param taskId
* @param outcome
* @param comments
* @param params
* @param funName 系统标识
* @return
* @throws StaleObjectException
* @throws WorkflowException
*/
public Task updateTaskOutcome(String taskId, String outcome,
String comments, Map params,
String funName) throws StaleObjectException,
WorkflowException {
Task task = this.getTaskById(taskId);
if (params != null) {
Iterator iter = params.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
if (entry.getKey() != null && entry.getValue() != null) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
this.updatePayloadElement(task, key, value);
}
}
}
//标识流程模型上有没有设置OUTCOME必须注释
Boolean comment = true;
if (funName != null && funName.startsWith("CRM.")) {
//CRM不进行备注
comment = false;
}
ObjectFactory factory = new ObjectFactory();
CommentType commentType = factory.createCommentType();
commentType.setComment(comments);
commentType.setCommentScope("TASK");
commentType.setTaskId(taskId);
commentType.setAction(outcome);
task.addUserComment(commentType);
if (comment) {
this.getTaskService().updateTask(this.getWorkflowContext(), task);
task = this.getTaskById(taskId);
}
this.getTaskService().updateTaskOutcome(this.getWorkflowContext(),
task, outcome);
return task;
}
// //旧代码
// public IWorkflowContext getWorkflowContext() {
// long start = System.currentTimeMillis();
// if (wfContext == null) {
// try {
// wfContext =
// getTaskQueryService().authenticate(BPM_MANAGER_UN, BPM_MANAGER_PW.toCharArray(),
// BPM_LDAP_DOMAIN);
// wfContext =
// this.getTaskQueryService().authenticateOnBehalfOf(wfContext,
// username);
// } catch (WorkflowException e) {
// return null;
// }
// }
// System.out.println("getWorkflowContext cost:" +
// (System.currentTimeMillis() - start) + "ms");
// return wfContext;
// }
// 旧代码
// /**
// * 审批操作
// * @param taskId
// * @param outcome
// * @param comments
// * @param params
// * @return
// * @throws StaleObjectException
// * @throws WorkflowException
// */
// public Task updateTaskOutcome(String taskId, String outcome,
// String comments,
// Map params) throws StaleObjectException,
// WorkflowException {
// Task task = this.getTaskById(taskId);
// if (params != null) {
// Iterator iter = params.entrySet().iterator();
// while (iter.hasNext()) {
// Map.Entry entry = (Map.Entry)iter.next();
// if (entry.getKey() != null && entry.getValue() != null) {
// String key = entry.getKey().toString();
// String value = entry.getValue().toString();
// this.updatePayloadElement(task, key, value);
// }
// }
// }
//
// ObjectFactory factory = new ObjectFactory();
// CommentType commentType = factory.createCommentType();
// commentType.setComment(comments);
// commentType.setCommentScope("TASK");
// commentType.setTaskId(taskId);
// commentType.setAction(outcome);
// task.addUserComment(commentType);
// this.getTaskService().updateTask(this.getWorkflowContext(), task);
// task = this.getTaskById(taskId);
//
// this.getTaskService().updateTaskOutcome(this.getWorkflowContext(),
// task, outcome);
//
// return task;
// }
public void updatePayloadElement(Task task, String name, String value) {
task.getPayloadAsElement().getElementsByTagName(name).item(0).setTextContent(value);
}
public Task getTaskById(String taskId) {
Task task = null;
try {
task = this.getTaskQueryService().getTaskDetailsById(this.getWorkflowContext(),
taskId);
} catch (WorkflowException e) {
e.printStackTrace();
}
return task;
}
}
结果
- 设置了Outcomes Require Comment的流程由于添加了缓存机制,平均减少了2秒的耗时
- 没有设置Outcomes Require Comment的流程平均耗时1秒
最后
做了缓存和系统标识后,总算是满足了甲方爸爸。
其他优化
- 修改审批方案:可以考虑使用MQ等消息队列做异步处理
- 审批历史的优化:如果需要单独记录审批历史,建议使用MQ等消息队列做异步处理