项目场景:
采集设备每小时将采集的信息已json文件的形式上报到指定位置,项目服务每小时定时读取文件的内容,然后将文件中的信息解析入库,前期由于上报的文件较小,所有采用直接将json字符串转javaBean对象然后分析后入库。等上正式环境后发现每小时上报的文件高达200M~400M,此时jvm直接内存溢出。
问题分析:
200M的数据包含的对象信息有100w之多,所以解析json数据和解析后的数据入库都是一个问题。
之前有写过批量保存100W以上的数据,所以数据入库问题不大,这里主要介绍大文件json数据解析问题。(批量保存百万级数据链接:https://blog.csdn.net/haohao_ding/article/details/102676528)
为了保证服务不占用大量的内存所以我们不能使用传统的将文件读取,解析,入库等这样的步骤方法,只能采用分批读取,分批解析,分批入库,保证整个环节内存使用较为合理的范围。这里使用的JsonReader对象,通过文件流的形式,一个一个读取json文件的对象,读取一批对象后再进行分析、保存操作。等这一批入库后接着读取,再分析、入库,直到整个文件读取完为止。
代码实现:
import com.google.gson.stream.JsonReader;
import lombok.Data;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
@Data
@Slf4j
public abstract class BigSingleJsonParse<T> {
Reader in = null;
int count = 100000;
ArrayList <T> objectList = new ArrayList<>();
public BigSingleJsonParse(Reader in) {
this.in = in;
}
public BigSingleJsonParse() {
}
public void readMessageArrayToDo(){
if (in != null) {
JsonReader reader = new JsonReader(in);
try {
reader.beginArray();
while (reader.hasNext()) {
if (objectList.size() > count) {
toDoSome(objectList);
objectList.clear();
}
objectList.add(readMessage(reader));
}
reader.endArray();
toDoSome(objectList);
objectList.clear();
} catch (IOException e) {
e.printStackTrace();
}
}else{
log.error("文件流传入为null。");
}
}
public void readMessageArrayToDo(Reader in){
this.in = in;
readMessageArrayToDo();
}
public abstract T readMessage(JsonReader reader);
public abstract void toDoSome(ArrayList <T> objectList);
}
上面的代码是一个简单的封装,readMessageArrayToDo()方法就是读取传入的文件流,或者实例对象时set的文件流,逐个读取到count(默认100000)个后,toDoSome()方法对这些数据分析保存,readMessage()方法是具体读取每一个对象。
下面是具体的实现
@Component("netCollectDataJsonParse")
@Data
@Slf4j
public class NetCollectDataJsonParseImpl extends BigSingleJsonParse<NetCollectData> {
@Autowired
@Qualifier("netCollectDataBigDataInsert")
private BigDataInsertOrUpdate netCollectDataBigDataInsertOrUpdate;
@Autowired
@Qualifier("invalidNetCollectDataBigDataInsert")
private BigDataInsertOrUpdate invalidNetCollectDataBigDataInsertOrUpdate;
public NetCollectDataJsonParseImpl() {
}
public NetCollectDataJsonParseImpl(Reader in) {
super(in);
}
@Override
public NetCollectData readMessage(JsonReader reader) {
NetCollectData netCollectData = new NetCollectData();
try {
reader.beginObject();
while(reader.hasNext()){
String name = reader.nextName();
if("COLLECT_TIME".equals(name)){
netCollectData.setCOLLECT_TIME(reader.nextString());
}else if("MAC".equals(name)){
netCollectData.setMAC(reader.nextString());
}else if("IP".equals(name)){
netCollectData.setIP(reader.nextString());
}else if("TIME".equals(name)){
netCollectData.setTIME(reader.nextString());
}else if("TS_UP_4G".equals(name)){
netCollectData.setTS_UP_4G(reader.nextLong());
}else if("TS_UP".equals(name)){
netCollectData.setTS_UP(reader.nextLong());
}else if("TS_DOWN_4G".equals(name)){
netCollectData.setTS_DOWN_4G(reader.nextLong());
}else if("TS_DOWN".equals(name)){
netCollectData.setTS_DOWN(reader.nextLong());
}else if("PKG_UP_4G".equals(name)){
netCollectData.setPKG_UP_4G(reader.nextLong());
}else if("PKG_UP".equals(name)){
netCollectData.setPKG_UP(reader.nextLong());
}else if("PKG_DOWN_4G".equals(name)){
netCollectData.setPKG_DOWN_4G(reader.nextLong());
}else if("PKG_DOWN".equals(name)){
netCollectData.setPKG_DOWN(reader.nextLong());
}else{
reader.skipValue();
}
}
reader.endObject();
} catch (IOException e) {
e.printStackTrace();
}
return netCollectData;
}
@Override
public void toDoSome(ArrayList<NetCollectData> netCollectDataList) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
List<NetCollectData> netCollectDatas = new ArrayList<>();
List<InvalidNetCollectData> invalidNetCollectDatas = new ArrayList<>();
for (int i = 0; i < netCollectDataList.size(); i++) {
NetCollectData netCollectData = netCollectDataList.get(i);
// 时间为1970.01.01 08:00:00代表断开网络
if ("1970.01.01 08:00:00".equals(netCollectData.getTIME())) {
InvalidNetCollectData invalidNetCollectData = netCollectDataToInvalidNetCollectData(netCollectData);
try {
invalidNetCollectData.setDate(dateFormat.parse(netCollectData.getCOLLECT_TIME()));
} catch (ParseException e) {
log.error("读取到的采集时间转换异常");
invalidNetCollectData.setDate(new Date());
}
invalidNetCollectDatas.add(invalidNetCollectData);
} else {
if(MacOfEduIdBucket.isContainsKey(netCollectData.getMAC())){
netCollectData.setEduId(MacOfEduIdBucket.getEduIdByMac(netCollectData.getMAC()));
try {
netCollectData.setDate(dateFormat.parse(netCollectData.getCOLLECT_TIME()));
} catch (ParseException e) {
log.error("读取到的采集时间转换异常");
netCollectData.setDate(new Date());
}
netCollectDatas.add(netCollectData);
} else {
log.warn("该mac[{}],在redis里面找不到对应的学生信息", netCollectData.getMAC());
InvalidNetCollectData invalidNetCollectData = netCollectDataToInvalidNetCollectData(netCollectData);
try {
invalidNetCollectData.setDate(dateFormat.parse(netCollectData.getCOLLECT_TIME()));
} catch (ParseException e) {
log.error("读取到的采集时间转换异常");
invalidNetCollectData.setDate(new Date());
}
invalidNetCollectDatas.add(invalidNetCollectData);
}
}
}
if(CollectionUtils.isNotEmpty(netCollectDatas)){
netCollectDataBigDataInsertOrUpdate.insertOrUpdateBigData(netCollectDatas);
}
if(CollectionUtils.isNotEmpty(invalidNetCollectDatas)){
invalidNetCollectDataBigDataInsertOrUpdate.insertOrUpdateBigData(invalidNetCollectDatas);
}
}
/**
* 由netCollectData对象得到invalidNetCollectData对象
*
* @param netCollectData
* @return
*/
private InvalidNetCollectData netCollectDataToInvalidNetCollectData(NetCollectData netCollectData) {
InvalidNetCollectData invalidNetCollectData = new InvalidNetCollectData();
invalidNetCollectData.setCOLLECT_TIME(netCollectData.getCOLLECT_TIME());
invalidNetCollectData.setIP(netCollectData.getIP());
invalidNetCollectData.setMAC(netCollectData.getMAC());
invalidNetCollectData.setEduId(netCollectData.getEduId());
invalidNetCollectData.setTIME(netCollectData.getTIME());
invalidNetCollectData.setPKG_DOWN(netCollectData.getPKG_DOWN());
invalidNetCollectData.setPKG_DOWN_4G(netCollectData.getPKG_DOWN_4G());
invalidNetCollectData.setPKG_UP(netCollectData.getPKG_UP());
invalidNetCollectData.setPKG_UP_4G(netCollectData.getPKG_UP_4G());
invalidNetCollectData.setTS_DOWN(netCollectData.getTS_DOWN());
invalidNetCollectData.setTS_DOWN_4G(netCollectData.getTS_DOWN_4G());
invalidNetCollectData.setTS_UP(netCollectData.getTS_UP());
invalidNetCollectData.setTS_UP_4G(netCollectData.getTS_UP_4G());
return invalidNetCollectData;
}
}
使用方法:
@Autowired
private BigSingleJsonParse netCollectDataJsonParse;
.
.
.
.
.
.
netCollectDataJsonParse.setIn(new FileReader(filePath));
netCollectDataJsonParse.readMessageArrayToDo();
经过测试:400M的文件轻松解析入库,不存在内存泄漏的情况