很多公司業務都需要進行第三方接口的對接工作,特別是那種大部分數據都來自第三方的項目。比如亞馬遜商家服務的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等集合,對象字段儘量設置爲和入庫的的表字段類型一致,減少轉型的發生。禁止出現大量對象和對象之間數據流轉,儘量做到一個解析後的對象直接入庫,不需要進行任何轉型操作。
想學習分佈式、微服務、JVM、多線程、架構、java、python的童鞋,千萬不要掃碼,否則後果自負~
林老師帶你學編程:https://wolzq.com