很多公司业务都需要进行第三方接口的对接工作,特别是那种大部分数据都来自第三方的项目。比如亚马逊商家服务的saas系统,基本上所有的数据都来自亚马逊平台。
背景
像这种需要定期获取亚马逊接口数据,然后存储到本地数据库中的项目,一般就会涉及到数据转换过程。这边我将会给大家介绍一个实际项目案例,平台从亚马逊获取数据,进行解析的过程中,因为数据过大,导致内存溢出的场景。
接口说明
亚马逊平台返回的是json格式数据,然后通过jackson进行json解析,将最后的解析结果保存到我们自己的数据库中。但是json反序列也是需要技巧的,否则会因为使用不当导致内存溢出。
json解析
一开始我们通过jackson工具类,将json流解析为JsonNode,如下所示:
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree((GZIPInputStream) reportMap.get("data"));
但是当请求量过大的时候,内存中就会出现大量的LinkHashMap、Entry、HashMap、Node之类的集合对象,时间一长就OOM内存溢出了。
解析改造
集合对象如果没有管理好,会很难被GC回收,所以一开始我们想到的办法是将JsonNode设置为null,后面发现这样做没起到任何作用,所以只能进行彻底的改造,直接用jackson解析成我们需要的对象,如下所示:
private List<ReportAdvertisementDto> parseJsonIO(GZIPInputStream gzipInputStream) {
ObjectMapper mapper = new ObjectMapper();
List<ReportAdvertisementDto> advertisementDtoList =new ArrayList<>();
try {
advertisementDtoList = mapper.readValue(gzipInputStream, new TypeReference<List<ReportAdvertisementDto>>() {
});
} catch (IOException e) {
logger.error("parseJsonIO转化异常,错误信息为:{}", ExceptionUtil.formatException(e));
}
return advertisementDtoList;
}
修改完之后可以很明显的看到,LinkHashMap、Entry、HashMap、Node之类的对象数量大量减少,内存的占用率明显降低,大大的减少了内存OOM的风险。
深入改造
写到这一步是不是就优化完毕了呢?不!!!一开始的时候我是将所有的字段全部设置为String,然后通过Long.value()和Integer.value()转换为数据库需要的字段类型,如下所示:
campaignSearchTermReport.setImpressions(Integer.valueOf(impressions));
campaignSearchTermReport.setTotalspend(Long.valueOf(cost));
campaignSearchTermReport.setSales(BigDecimal.valueOf(sales));
所以为了避免这种情况发生,我们需要将转换的对象字段提前设置好,数据库需要什么类型,我们就设置为同样的类型,这样的好处就是我们不需要进行转型,直接就可以set进去,如下所示:
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ReportAdvertisementDto {
private double cost;
private String attributedSales1d;
private int impressions;
private int clicks;
private long campaignId;
private String campaignName;
private String adGroupName;
private long adId;
private long adGroupId;
private String sku;
private String asin;
private long keywordId;
private String matchType;
private String query;
private String keywordText;
private int attributedConversions7d;
private double attributedSales7d;
private int attributedUnitsOrdered7d;
private int attributedConversions7dSameSKU;
private double attributedSales7dSameSKU;
}
总结
在数据量不大的情况下,代码就算效率不高,也不会出现什么问题,但是当数据量达到一定级别,代码问题就会被凸显出来。比如我们平时用Map来存储临时数据,但是map集合的大小要比对象更加占用内存,如果服务器硬件不高,很容易就发生内存溢出。 所以我们在处理接口数据的时候,一定要本着简单、适用。尽量不要将json数据解析为map等集合,对象字段尽量设置为和入库的的表字段类型一致,减少转型的发生。禁止出现大量对象和对象之间数据流转,尽量做到一个解析后的对象直接入库,不需要进行任何转型操作。