JAVA项目性能调优
由于之前的HTTP优化,使得接口在压测测试下响应时间为4秒。4秒的响应速度不是一个正常项目能接受的。经过几天的代码与设置调整终于将响应时间压缩至0.4秒。
代码层
在性能优化之前首先想到的就是代码方面的优化。由于项目特殊原因每次都要加载很多数据库信息而且这些信息都是固定得,因此频繁的数据库调用首先是性能的瓶颈之一。为了避免频繁得访问数据库加上项目数据量不是很多,我打算使用单例模式将每次得数据记录保存入内存中用于重复得利用。
package com.comtop.dop.api.engine.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.comtop.cap.runtime.base.util.BeanContextUtil;
import com.comtop.dop.api.ApiConstants;
import com.comtop.dop.api.component.model.ComponentVO;
import com.comtop.dop.api.engine.dag.DagExcutor;
import com.comtop.dop.api.engine.dag.IDagExcutor;
import com.comtop.dop.api.engine.dag.exception.ApiBaseException;
import com.comtop.dop.api.engine.model.EngineVO;
import com.comtop.dop.api.workshop.facade.ApiFacade;
import com.comtop.dop.api.workshop.model.ApiComponentParamVO;
import com.comtop.dop.api.workshop.model.ApiComponentVO;
import com.comtop.dop.api.workshop.model.ApiParamPolicyVO;
import com.comtop.dop.api.workshop.model.ApiVO;
import com.comtop.dop.api.workshop.model.ParamVO;
import com.comtop.dop.base.appservice.IDataFrame;
import com.comtop.dop.base.appservice.impl.JavaDataFrame;
import com.comtop.dop.securitymanage.algorithmmanage.model.AlgorithmVO;
import com.comtop.dop.securitymanage.securitypolicymanage.model.PolicyParamVO;
import com.comtop.dop.securitymanage.securitypolicymanage.model.PolicyVO;
import comtop.soa.javax.annotation.PostConstruct;
/**
* Dag数据源数据单例类
* @author jixiang
*
*/
public class DagSuorce {
private volatile static DagSuorce dagSuorce = null;
// Dag数据源
private static Map<String, IDagExcutor> dagSuorces = new ConcurrentHashMap<String, IDagExcutor>();
// API信息
private static Map<String, ApiVO> apiInfos = new ConcurrentHashMap<String, ApiVO>();
private ApiFacade apiFacade;
// 构造方法
private DagSuorce() {
}
/**
* 新增Dag数据源
*
* @param key
* @param dagExcutor
*/
public void putApiInfo(String apiCode, ApiVO apiVO) {
apiInfos.put(apiCode, apiVO);
}
/**
* 获取Dag数据源
*
* @param key
* @return
*/
public ApiVO getApiInfo(String apiCode) {
ApiVO apiVO = apiInfos.get(apiCode);
if (apiVO == null) {
apiVO = loadApiVO(apiCode);
}
return apiVO;
}
public static DagSuorce getDagSuorce() {
if(dagSuorce==null){
synchronized (DagSuorce.class) {
if(dagSuorce==null){
dagSuorce=new DagSuorce();
dagSuorce.apiFacade = BeanContextUtil.getBeanFromSpringContext(ApiFacade.class);
}
}
}
return dagSuorce;
}
/**
* 新增Dag数据源
*
* @param key
* @param dagExcutor
*/
public void putDagSuorce(String apiCode, IDagExcutor dagExcutor) {
dagSuorces.put(apiCode, dagExcutor);
}
/**
* 获取Dag数据源
*
* @param key
* @return
*/
public IDagExcutor getDagSuorce(String apiCode) {
IDagExcutor dagExcutor = dagSuorces.get(apiCode);
if (dagExcutor == null) {
dagExcutor = loadDagExcutor(apiCode);
}
return dagSuorces.get(apiCode);
}
/**
*
* @param apiCode
* @return
*/
public IDagExcutor loadDagExcutor(String apiCode) {
/**
* 判断数据库是否有相关API数据,如果数据库有API相关信息,则初始化为DAG
*/
ApiVO apiVO = apiFacade.getApiByApiCode(apiCode);
// API执行类
IDagExcutor dagExcutor = getDagExcutor(apiVO);
/**
* 更新Dag数据源
*/
putDagSuorce(apiCode, dagExcutor);
return dagExcutor;
}
/**
*
* @param apiCode
* @return
*/
public ApiVO loadApiVO(String apiCode) {
/**
* 判断数据库是否有相关API数据,如果数据库有API相关信息,则初始化为DAG
*/
ApiVO apiVO = apiFacade.getApiByApiCode(apiCode);
/**
* 更新Dag数据源
*/
putApiInfo(apiCode, apiVO);
return apiVO;
}
/**
* 根据engine信息更新DAG
*
* @param engine
* API引擎
*/
public void updateDag(EngineVO engine) {
// API编号
String apiCcode = engine.getApiCode();
// API执行类
IDagExcutor dag = getDagExcutor(engine.getApi());
/**
* 更新Dag数据源
*/
putDagSuorce(apiCcode, dag);
}
/**
* 根据engine信息更新DAG
*
* @param engine
* API引擎
*/
public void updateDag(ApiVO apiVO) {
/**
* 判断数据库是否有相关API数据,如果数据库有API相关信息,则初始化为DAG
*/
// API执行类
IDagExcutor dagExcutor = getDagExcutor(apiVO);
/**
* 更新Dag数据源
*/
putDagSuorce(apiVO.getApiCode(), dagExcutor);
}
/**
* 描述:通过API对象转换DAG执行类
*
* @param apiVO
* @return IDagExcutor
*/
public IDagExcutor getDagExcutor(ApiVO apiVO) {
/**
* APIVO转换统一数据结构
*/
IDataFrame dataFrame = getDataFrame(apiVO);
/**
* DAG执行实例类
*
*/
IDagExcutor dag = new DagExcutor(dataFrame);
return dag;
}
/**
* 描述:APIVO转换统一数据结构 1、转换出入参数据 2、转换组件元数据 3、转换策略元数据:待处理
*
* @param apiVO
* @return IDataFrame
*/
private IDataFrame getDataFrame(ApiVO apiVO) {
if (apiVO == null) {
// 直接报错
throw new ApiBaseException(ResultEnum.PARAM_ERROR);
}
// API编号
String apiCode = apiVO.getApiCode();
/**
* 统一数据结构
*/
IDataFrame dataFrame = new JavaDataFrame(apiCode);
// API出入参信息
List<ParamVO> lstParam = apiVO.getRelationApiAndParam();
/**
* 1、转换出入参数据,并返回API参数策略(根据用户ID分类)
*/
List<ApiParamPolicyVO> lstApiParamPolicyVO = changeInOutMeta(lstParam,
dataFrame);
// API组件
List<ApiComponentVO> lstApiComponent = apiVO
.getRelationApiToApiComponent();
/**
* 2、转换组件元数据
*
*/
changeComponentMeta(lstApiComponent, dataFrame);
/**
* 3、转换策略元数据
*
*/
changePolicy(lstApiParamPolicyVO, dataFrame);
/**
* 设置申请人
*/
dataFrame.setUserId(apiVO.getApplyUser());
return dataFrame;
}
/**
* 1、转换出入参数据
*
* @param lstParam
* @param dataFrame
*/
private List<ApiParamPolicyVO> changeInOutMeta(
List<ParamVO> lstParam, IDataFrame dataFrame) {
List<ApiParamPolicyVO> lstApiParamPolicyAll = new ArrayList<ApiParamPolicyVO>(
lstParam.size());
/**
* 循环API出入参信息
*/
for (ParamVO param : lstParam) {
/**
* 根据参数类型分类出、入参数
*/
switch (param.getInoutType()) {
case ApiConstants.DOP_WORKSHOP_PARAM_INOUT_TYPE_IN:
boolean isRequired = ApiConstants.TRUE;
if (param.getIsRequired() == ApiConstants.DOP_WORKSHOP_PARAM_IS_REQUIRED_FALSE) {
isRequired = ApiConstants.FALSE;
}
dataFrame.addInColumnMeta(param.getParamCode(),
param.getParamType(), isRequired);
break;
case ApiConstants.DOP_WORKSHOP_PARAM_INOUT_TYPE_OUT:
// API参数策略
List<ApiParamPolicyVO> lstApiParamPolicy = param
.getRelationApiParamPolicy();
lstApiParamPolicyAll.addAll(lstApiParamPolicy);
dataFrame.addOutColumnMeta(param.getParamCode(),
param.getParamType());
break;
default:
break;
}
}
return lstApiParamPolicyAll;
}
/**
* 2、转换组件元数据
*
* @param lstApiComponent
* @param dataFrame
*/
private void changeComponentMeta(
List<ApiComponentVO> lstApiComponent, IDataFrame dataFrame) {
// 循环API组件
for (ApiComponentVO apiComponent : lstApiComponent) {
// 组件实例类
// API组件参数
List<ApiComponentParamVO> lstApiComponentParam = apiComponent
.getRelationApiCommToApiCommParam();
// API组件参数对象
Map<String, Object> apiCompoentMap = new HashMap<String, Object>();
// 循环组件参数
for (ApiComponentParamVO apiComponentParam : lstApiComponentParam) {
// 参数编码
String colName = apiComponentParam.getParamCode();
// 参数值
String colValue = apiComponentParam.getParamValue();
apiCompoentMap.put(colName, colValue);
}
// 组件信息
ComponentVO component = apiComponent.getRelationApiCommToComm();
// 组件对应实体类
String commClass = component.getCommClass();
// 组件序号
int iIndex = apiComponent.getOrderNum();
// 数据统一结构新增API组件参数
dataFrame.addProcessorConfig(commClass, iIndex, apiCompoentMap);
}
}
/**
* 3、转换策略元数据
*
* @param lstApiComponent
* @param dataFrame
*/
private void changePolicy(
List<ApiParamPolicyVO> lstApiParamPolicyVO, IDataFrame dataFrame) {
// 判断策略集合是否为空,为空则返回,不处理
if (lstApiParamPolicyVO == null || lstApiParamPolicyVO.isEmpty()) {
return;
}
for (ApiParamPolicyVO apiPolicy : lstApiParamPolicyVO) {
// 用户ID
String userId = apiPolicy.getUserId();
// 参数编号
String strColumn = apiPolicy.getParamCode();
PolicyVO policyVO = apiPolicy.getRelationPolicy();
if (policyVO == null) {
continue;
}
AlgorithmVO algorithmVO = policyVO.getRelationAlgorithm();
// 算法实例名称
String algorithmClass = algorithmVO.getAlgorithmClass();
String algorithmId = algorithmVO.getAlgorithmId();
List<PolicyParamVO> lsrPolicyParam = policyVO
.getRelationPolicyParamList();
// API策略参数对象
Map<String, Object> apiPolicyMap = new HashMap<String, Object>();
// API策略参数对象
Map<String, Object> logicParamsMap = new HashMap<String, Object>();
logicParamsMap.put("id", algorithmId);
// 循环组件参数
for (PolicyParamVO apiPolicyParam : lsrPolicyParam) {
// 参数编码
String colName = apiPolicyParam.getParamCode();
// 参数值
String colValue = apiPolicyParam.getParamValue();
apiPolicyMap.put(colName, colValue);
if ("cipherType".equalsIgnoreCase(colName)) {
logicParamsMap.put("secretType", colValue);
}
}
// 数据统一结构新增API组件参数
dataFrame.addApiParamPolicyConfig(userId, strColumn,
algorithmClass, apiPolicyMap, logicParamsMap);
}
}
}
经过再次压测发现性能有所提升,但是并不是很明显。再次调试发现具体原因是因为通过HTTP请求调用了外部系统的方法导致。考虑到每次请求都比较固定的特点,因此考虑再次考虑使用单例来记录HttpClient信息并使用连接池来对访问连接进行管理。(具体代码点击在这里)经过再次压测发现由之前的3秒多再次压缩至2秒左右。
集群
经过长时间的代码调试发现HTTP请求的响应速度一直无法有效提升。为此只能通过将外部依赖服务经行集群的形式来增大请求的吞吐量来加快响应时间。
nginx负载均衡
为了是集群访问流量得到合理的分配。我们采用nginx经行负载均衡。
负载形式通过采用weight 来控制集群中不同实例的流量
upstream bpcluster{
server XX.XX.XX.XX:9090 weight=1;//weight 指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
server XX.XX.XX.XX:9091 weight=2;
server XX.XX.XX.XX:9090 weight=1;
}
反向代理设置
location /bp/api {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://**bpcluster**/api;
proxy_redirect off;
}
经过负载之后发现性能有了再次提升,但是并没有预想中的那么好。这明显是不合理的,因此我又继续排查了Tomcat容器
Tomcat层
在tomcat中每一个用户请求都是一个线程,所以可以使用线程池提高性能。
修改server.xml文件:
<!--将注释打开-->
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="300" minSpareThreads="50" prestartminSpareThreads="true" maxQueueSize="100"/>
<!--
参数说明:
maxThreads:最大并发数,默认设置 200,根据硬件设施和业务来判断
minSpareThreads:Tomcat 初始化时创建的线程数,默认设置 25
prestartminSpareThreads: 在 Tomcat 初始化的时候就初始化 minSpareThreads 的参数值,如果不等于 true,minSpareThreads 的值就没啥效果了
maxQueueSize,最大的等待队列数,超过则拒绝请求
-->
<!--在Connector中设置executor属性指向上面的执行器-->
<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
经过再次压测终于响应时间进入了1秒内。但是通过查看运行日志发现居然出现了MySQL Too many connections问题。
MySQL层
没错通过上面的配置之前的MySQL配置已经满足不了现在的访问需求,因此我们需要对Mysql连接数进星修改。
修改形式主要有两种:
1.进入MySQL安装目录,打开MySQL配置文件 my.ini 或 my.cnf查找 max_connections=100,修改为max_connections=1000,重启MySQL服务即可。
2.
(1)mysql -u root -p //控制台登录
(2)show variables like ‘%max_connections%’;
(3)set GLOBAL max_connections=5000; (注:这种配置再重启之后就会失效,推荐采用方案1)
总结
项目的性能不是单方面问题来决定的,代码质量、硬件设备、合理的参数配置缺一不可。当然也需要多次的调试总结才能找到最优解。