最近发布了一个日常,直接导致系统响应时间变慢了两倍,系统load增长了4倍,还好由于PV不是很大,系统没出什么问题,但是如果这个系统是公司的主系统,承担大部分的流量的话,估计系统就挂了,现在想想有点后怕,把这次故障的过程记录下来。
故障的起因很简单,我需要提供一个根据本地ip获取地址的接口,这里我调用了公司统一的一个底层地址接口,代码如下
String clientIp = rundata.getRequest().getRemoteAddr();//用户IP地址
ITbip tbip = new TbipImpl();
tbip.setEncode("gbk"); //设置所采用的编码,参数支持"gbk"或"utf8",默认为gbk。
tbip.init();
IpInfoEntity ipInfo = tbip.getIpInfo(clientIp);
String regin = ipInfo.getRegion();
String city = ipInfo.getCity();
context.put("regin",regin);
context.put("city",city);
这个接口上去以后,系统性能马上就下来了,问题定位倒很容易,就这么几行代码,主要的性能消耗点就在tbip.init()这个方法,里面的代码如下:
public void init() throws IOException {
String resFile = "";
if (this.encode.equals("gbk")) {
resFile = "/ipdata_geo_isp_code.txt.gbk";
} else {
resFile = "/ipdata_geo_isp_code.txt.utf8";
}
InputStream is = this.getClass().getResourceAsStream(resFile);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line = "";
while ((line = br.readLine()) != null) {
int position1 = line.indexOf(",");
int position2 = line.indexOf(",", position1+1);
int position3 = line.indexOf(",", position2+1);
int position4 = line.indexOf(",", position3+1);
IpSegInfoEntity ipSegInfoEntity = new IpSegInfoEntity();
ipSegInfoEntity.setLipStart(Long.parseLong(line.substring(0, position1)));
ipSegInfoEntity.setLipEnd(Long.parseLong(line.substring(position1+1, position2)));
ipSegInfoEntity.setDetail(line.substring(position4+1));
//System.out.println(ipSegInfoEntity);
ipSegEntityList.add(ipSegInfoEntity);
}
br.close();
}
可以看到,这段代码就是读取一个ip地址对应关系的文件,然后把它读入到内存中,可以想像,这个文件可以有多大,大概是25M的样子,就是因为这个init方法,导致每次请求这个接口的时候,内存就会消耗掉25M左右。因此导致系统频繁GC,导致请求响应变慢,请求变慢,导致运行队列过长,从而导致load上升等等问题。解决方法很简单,在系统刚启动的时候,把文件放入到内存中就行了
教训:1:每次调用别人的接口,要看仔细demo,否则容易被误导,这次的错误代码就是参照demo写的,因此就有问题了
2:碰到init方法的时候千万注意,因为有可能里面会有很复杂的逻辑,很有可能调用一次就够了,调用多次的话,就会有性能问题