在结算改造项目和Mysql迁移项目中,结算遇到了一些性能问题。 在性能优化的过程中,根据不同的场景采用了不同的方法来解决遇到的问题。我把处理的过程和采用的方法发出来,大家来看下是否这些方法得当。
- 一、 全量检查
全量检查客户上下线时,要检查数据库表q_customers里120W左右的数据和ad_campaigns里面110W左右的数据。
考虑了以下两种方法:
1. 所有的数据,要读到内存中进行上下线的计算。 --- 所有的数据load到内存中,内存肯定溢出。
2. 将custid取出来,分批取数据。-- 首先是所有的custid,大约有120w的数据,在整个全量运行期间占据内存。第二,oracle有in不超过1000个的限制,取所有的数据,需要对数据库进行1200次操作,数据库交互次数太多,操作时间太长。
所以,要解决全量的问题:要避免一次把所有数据load到内存中,尽量避免使用in来取数据,减少与数据库交互次数
采用的方案是
1. 分批取数据,每次取40W。
2. 此20W数据,只记录起始和结束的custid,减少custid占用内存。
见如下sql:
SELECT Min(custId) minId,Max(custId) maxId FROM
( SELECT custid FROM (SELECT custid FroM q_customers WHERE custid > #startCustId# ORDER BY custid )
WHERE ROWNUM <= #batchSize# )
结果:
现在压出来的结果,在多个表关联,取十个左右的属性的情况下,40W记录取数据时间大约在 30秒左右。
- 二、 日终—内存溢出
在日终结算时,原来的处理方式会将所有的点击记录(目前约120W左右)全部读到内存中进行校验、打折、计算扣费等。(日终文件会根据custid进行排序)
这种方式在性能测试时,对于150w左右的数据进行计算时,勉强可以通过,但是当点击记录到达300W时,就会报内存溢出。
问题就出在,把所有记录加载进行计算时,随着数据量的增加,2G的内存总会有成为瓶颈的时候,比如300w。
对这个问题的处理采取的思路时:
对于校验、打折这块逻辑的处理:将点击记录读到内存中,当读的行数到达一个设定的值时(如10W),则将这些点击记录放入临时文件中,依次循环进行,直到所有记录都被处理完成。
对于计算扣费:因为与某个custid的所有记录有关,而每个客户有多少点击并不固定,这个地方的处理是将一个客户的所有点击记录都累加计算完,将客户的扣费情况写入临时文件。(此处的处理方式是因为我们系统中不会有某个客户的每天的点击超过10W条,不会占用过多内存。如果某天我们有超级客户,每天的点击超过10w的情况出现,可能整个结算都要重新考虑架构方式来解决性能问题了)
- 三、 在mysql中,由于分库导致取数据比较慢
Mysql迁移项目中,结算要读取的q_customers,ad_campaigns,ad_budgets等数据已经迁移到mysql中,而其他的如accounts表中的数据在oracle中。结算需要从双库中读取数据。从mysql中去读40W数据不能使用 between and 这种custid区间的方式,那么在第一条中所使用的方法在读mysql的数据时,就要考虑改变,只能使用in的方式去mysql中去读了。压测的结果表明,在mysql中使用in循环去读数据时,性能很慢。读40W条记录,大约需要20分钟,这在结算上下线中是不可接受的。
So,采用以下的方式。
//1.将某个custid区间的40Wcustid先取出来
//2.对custid根据cobar分区规则进行分区,得到128个数组
//3.将128个数组,分成16组。每组里面分别有8个分区的数据
//4.使用8个线程,分别对16组数据 从mysql中获取(mysql没有in个数的限制)
多线程的代码如下:
private class GetDataThreadMysql<E> extends Thread {
private Vector<E> v1;
private List<List<Long>> custListTeam;
private List<Long> campaignIdList;
private String sql;
private boolean flag = false;
public boolean isFlag() {
return flag;
}
public GetDataThreadMysql(Vector<E> v1, List<List<Long>> custListTeam, List<Long> campaignIdList, String sql){
this.v1 = v1;
this.custListTeam = custListTeam;
this.sql = sql;
this.campaignIdList = campaignIdList;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
if (Checks.isEmpty(campaignIdList)) {
for (List<Long> custIdList : custListTeam) {
v1.addAll(executeQueryForList(sql, custIdList));
}
} else {
Map<String, List<Long>> pMap = new HashMap<String, List<Long>>();
pMap.put("campaignIdList", campaignIdList);
for (List<Long> custIdList : custListTeam) {
pMap.put("custIdList", custIdList);
v1.addAll(executeQueryForList(sql, pMap));
}
}
flag = true;
}
}
- 四、 Cobar分批提交数据慢的问题
解决方式如三,使用多线程解决。
在不使用多线程时,cobar1w 条提交时间为1分钟左右。
现在每1W条数据提交时间,oracle为100ms左右,mysql使用8个线程为1000ms。
- 五、 处理过程中full gc比较多
在多次处理过程中,会经常遇到 full gc比较多的情况。
使用jstat –gcutil <pid> 可以查看这个pid fgc的次数。
Fgc次数多有什么危害,大家可以自己google一下。
在fgc比较多时,可以使用 jmap –histo <pid> 来查看哪些对象占用内存比较多,然后有针对性的进行优化。