其他
干货 | 携程中转交通方案拼接性能优化
作者简介
简言,携程后端开发经理 ,关注技术架构、性能优化、交通规划等领域。// 计算方案评分(computeTripScore) 中调用的StringUtils.format代码示例
StringUtils.format("AAAA-{0},BBBB-{1},CCCC-{2},DDDD-{3},EEEE-{4},FFFF-{5},GGGG-{6},HHHH-{7},IIII-{8},JJJJ-{9}",
aaaa, bbbb, cccc, dddd, eeee, ffff, gggg, hhhh, iiii, jjjj)
// getTripId 中调用StringUtils.format代码示例
StringUtils.format("{0}_{1}_{2}_{3}_{4}_{5}_{6}", aaaa, bbbb, cccc, dddd, eeee, ffff)
// 通过Java replace实现的自定义format函数
public static String format(String template, Object... parameters) {
for (int i = 0; i < parameters.length; i++) {
template = template.replace("{" + i + "}", parameters[i] + "");
}
return template;
}
实现 | 执行1000次平均耗时(us) |
使用Java8的replace实现的StringUtils.format | 1988.982 |
使用Apache replace实现的StringUtils.format | 656.537 |
Java8自带String.format | 1417.474 |
Apache的StringUtils.join | 116.812 |
// 优化前,通过Java replace实现的format函数
public static String format(String template, Object... parameters) {
for (int i = 0; i < parameters.length; i++) {
template = template.replace("{" + i + "}", parameters[i] + "");
}
return template;
}
// 优化后,通过Apache replace实现的format函数
public static String format(String template, Object... parameters) {
for (int i = 0; i < parameters.length; i++) {
String temp = new StringBuilder().append('{').append(i).append('}').toString();
template = org.apache.commons.lang3.StringUtils.replace(template, temp, String.valueOf(parameters[i]));
}
return template;
}
// 优化前
StringUtils.format("{0}_{1}_{2}_{3}_{4}_{5}_{6}", aaaa, bbbb, cccc, dddd, eeee, ffff)
// 优化后
StringUtils.join("_", aaaa, bbbb, cccc, dddd, eeee, ffff)
if (Config.getBoolean("enable.score.detail", false)) {
scoreDetail = StringUtils.format("AAAA-{0},BBBB-{1},CCCC-{2},DDDD-{3},EEEE-{4},FFFF-{5},GGGG-{6},HHHH-{7},IIII-{8},JJJJ-{9}",
aaaa, bbbb, cccc, dddd, eeee, ffff, gggg, hhhh, iiii, jjjj);
}
缓存容量有限,需要仔细斟酌数据的加载、更新、失效和替换策略; 缓存架构的设计:通常来说内存缓存(如HashMap、Caffeine等)性能最高,而Redis等分布式缓存次之,RocksDB相对较慢,容量上限则正好相反,需要仔细选型并搭配使用; 缓存不一致问题如何解决,能接受多久的不一致。
基础数据(如车站、行政区域等),因数据量小,变化频率低,全量保存到HashMap中,周期全量更新; 部分火车、飞机、汽车、船舶的班次数据缓存到Redis中,以提高访问效率和稳定性。不同产线采取的缓存策略稍有不同,但总的来说是定时更新与搜索触发更新相结合的方式; 一次拼接过程中可能查询数百次产线数据,Redis毫秒级的延迟累加起来也是非常大的。因此,希望在Redis之上再构建一层内存缓存以提高性能。通过分析发现拼接过程中存在非常明显的热点数据,热门日期和线路的查询占比非常高且数量相对有限。因此可以将这部分热点数据保存到内存缓存中,使用LFU(Least Frequently Used)替换,最终产线数据内存缓存命中率达到45%以上,相当于降低近一半的IO开销。 因为可以接受分钟级的数据不一致,所以将拼接结果缓存起来,在有效期内,如果下一个用户查询同一出发日期的相同线路,直接使用缓存数据即可。因为拼接的中转方案数据相对较大,所以将拼接结果保存到RocksDB中,虽然性能不如Redis,但是对于单次查询影响还可以接受。
子任务的执行需要相互独立、互不影响。如果存在依赖关系,则需要等待前一个任务执行完才能开始下一个任务,这样会使多线程失去意义; CPU核数决定了并发能力的上限,过多的线程会因频繁切换上下文而降低性能,需要特别关注线程数、CPU使用率、CPU Throttled time等指标。
“携程技术”公众号
分享,交流,成长