技术学习永远都是无休止的,更何况是在互联网大行其道的今天,“不进则退”永远是留给像我这种不是那么好学人的硬伤。庆幸的是,还好存在着价值的驱动和追求使我不得不去学习和总结,即使是赶鸭子上架试的学习,学永远比不学强。
那么技术的学习非常重要的一个环节就是总结,尤其对于像我这种只有到用到时才会好好去学的人。一直想把这段时间工作中所遇到的问题好好记录下来(即使自己是那么的不善于总结,不管生活、学习、还是工作)....
在辛苦、加班加点差不多一个月后的上周,终于看到我们的产品慢慢趋于稳定,让我们每个疲惫的心看到一丝希望。那一刻、内心的欣慰是难以言喻的。现在将这段时间开发过程中遇到最棘手,解决时间最长的三个问题列出来加以分析和总结。
1、spring定时器同一时间调度重复执行两次。
2、内存溢出、数据库链接数成倍增加 达到配置上线。
3、Tcp长连接无故断开,无任何异常断开信息,也未重连。
第一个问题,在开发当时就知道的一个问题,也花了大半天的时间去查找定位原因。最开始的想法以为是spring定时器配置的原因导致,于是到网上搜集各种spring定时器的帖子、仔细的看了spring定时器的几种具体实现方式,然后仔细检测了自己的配置,定位无果.... 然后以“spring定时器同一调度时间点执行两次”的字眼网上疯狂的搜索,然后发现各种解决办法(包括说1、web.xml的配置问题啊。2、tomcat配置文件baseApp属性配置的问题,导致定时类在spring自己加载了一次后tomcat启动时再加载了一次),觉得有必要的都一一试了之后问题还是没能解决。由于当时开发进度紧,最后觉得先选择一种折中的办法暂时规避这个问题以免影响项目正常运行,当时的办法就是在定时类中声明一个静态变量初始值为0,定时器执行到方法时更新为1,执行完毕再更新为0。如果第一次没执行完,第二次执行到此方法时检测该静态变量是否为0,为0则继续,否则不执行。当时以为这个方法能暂时规避这个问题所引发的错误请求问题,但是在所有代码都基本写完准备测试的时候发现,以上的解决方法根本不适用,反而把定时器调度时间点全给打乱了~...
于是又开始集中解决这个问题,包括项目组的一个开发主力也在一起帮忙解决,奋战了一天多最后很幸运的看到一个很隐蔽的帖子、问题也说的很隐蔽~ 不仔细看根本就看不出他说的到底是什么问题。最终这个问题终于得以解决!!
问题原因:在代码中有多处直接加载spring配置文件(包括定时器的配置文件)的代码,项目启动时有一处,如:BeanFactory beanFactory = new ClassPathXmlApplicationContext(“application*.xml”)。
解决办法:代码中直接加载配置文件是为了取得DAO和service层的实体注入,方便数据库操作,所以加载时直接加载这类的配置文件,无需包含定时器的配置(定时器配置单独分开的一个配置文件spring-quartz.xml)。如:BeanFactory beanFactory = new ClassPathXmlApplicationContext(new String[]{"application.xml","application-security.xml"})。
第二个问题,是比较已知的一类问题。一般都是代码、编程习惯问题造成的,比如IO流未正常关闭、http、tcp、socket等网络连接操作不当、或者是代码某处死循环啊等等、、 所以发现这个问题时把该检查的代码都检测了一篇、该优化的代码都优化了,但是问题还是存在。领导提议说用程序性能分析软件来探测,也准备下载 但是网上搜了一些看了下界面,感觉好像并没能细化到分析出是哪一段代码的那个类出现的问题。最后是同事发现了一处代码的可疑点,说改下试试,改完本地测试,果不其然 问题就出在这。
问题原因:由于业务需要,项目中保持了一个tcp长连接,不定时的接收服务端的消息写入,每次接收到写入时会实例化并调用项目中的业务处理类。由于这里接收服务端的写入消息时间频度比较小,而每次都实例化一个业务处理类(每次都是不同的对象),这个处理类中又多次跟数据库打交道,所以导致内存和数据库连接暴增。
解决办法:在调用tcp长连接类开启tcp连接时将业务处理对象作为参数传入。这样不用再每次消息到来都创建不同的对象了。
第三个问题,之前一直觉得也并无大碍,不会影响正常功能,首先它断开的频度也不是很频繁(大不了重启服务重连就好了);其次我客户端是用的一个mina框架,如果是服务端重启、网络断开等原因,mina的一个业务处理类都会抛出一个断开连接的异常信息,然后连接会关闭实现重连。所以有那么几天并没有致力去解决这个问题。但是之后发现问题存在始终不是办法,如果以后部署在正式环境上,根本就不能随便去重启服务器,更别说还是跟多个服务器有关联的。一旦重启可能会影响客户使用,更可能会影响到其他服务器。所以又开始是大工作量的投入测试,想找到问题复现的场景,但是本地环境各种想到的情况都测试了,问题始终没有复现,服务器上使用时也基本不会复现,只有在晚上长连接长时间闲置时问题才会出现,但是第二天过来是啥异常信息都查不到。想来想去 觉得还是加个心跳,定时给服务端发送消息检测连接是否正常,如果异常则关闭连接重连。下面把mina tcp连接相关的代码贴出来,供以后参考
public class Client extends Thread{
private static final Logger logger = Logger.getLogger(Client.class);
private static String serverIp = CmProperties.get("sipip");
//private static String serverIp = "127.0.0.1";
private static int recvPort = Integer.parseInt(CmProperties.get("sipport2"));
NioSocketConnector connector;
DefaultIoFilterChainBuilder chain;
IoSession mainSession;
RecvSipMakeCall recvSipMakeCall;
public Client(RecvSipMakeCall recvSipMakeCall) {
connector = new NioSocketConnector();
chain = connector.getFilterChain();
connector.setConnectTimeoutMillis(10000);
chain.addLast("codec", new ProtocolCodecFilter(
new TextLineCodecFactory()));
this.recvSipMakeCall = recvSipMakeCall;
}
public void registerHandler() {
connector.setHandler(new IoHandlerAdapter() {
@Override
public void messageReceived(IoSession session, Object message)
{
String msg = (String) message;
if (session.isConnected()) {
logger.debug("客户端收到通话状态消息:" + msg);
//RecvSipMakeCall recvSipMakeCall = new RecvSipMakeCall(); //这句是上面第二个问题(内存溢出、数据连接暴增)的关键
recvSipMakeCall.recvEvent(msg);
} else {
logger.info("连接已关闭");
}
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
logger.error("与" + session.getRemoteAddress() + "通信过程中出现错误:[" + cause.getMessage() + "]..连接即将关闭....");
//关闭IoSession,该操作也是异步的...true表示立即关闭,false表示所有写操作都flush后关闭
session.close(false);
}
@Override
public void sessionCreated(IoSession session) throws Exception {
//设置IoSession闲置时间,参数单位是秒
session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 300);
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
// 如果IoSession闲置,则关闭连接
if (status == IdleStatus.BOTH_IDLE) {
session.write(" \n");
}
}
@Override
public void sessionClosed(IoSession session) throws Exception {
logger.info("事件连接已关闭,正在准备重连...");
int count = 0;
while (mainSession == null || !mainSession.isConnected()) {
count++;
logger.info("事件连接创建失败。第"+count+"次重新连接...");
Thread.sleep(30000); //如果没连接上,隔30秒重连一次
connection();
}
}
});
}
public void connection() {
ConnectFuture future = connector
.connect(new InetSocketAddress(serverIp,recvPort));
logger.info("连接IP:" + serverIp + ",端口:" + recvPort);
try {
if (future.await(10000)) {
if (future.isConnected()){
logger.info("已成功建立连接.");
//System.out.println("已成功建立连接.");
mainSession = future.getSession();
}else {
logger.info("建立连接失败,请检查网络是否正常!");
//System.out.println("建立连接失败,请检查网络是否正常!");
}
} else {
logger.info("连接超时!");
}
} catch (InterruptedException e) {
logger.error("get iosession exception.msg:"+e.getMessage());
return;
}
}
public void startConn()
{
connection();
int count = 0;
while (mainSession == null || !mainSession.isConnected()) {
count++;
logger.info("事件连接创建失败。第"+count+"次重新连接...");
try {
Thread.sleep(30000);//如果没连接上,隔30秒重连一次
} catch (InterruptedException e) {
logger.error("The exception occurred in startConn!!"+e.getMessage());
}
connection();
}
}
@Override
public void run() {
registerHandler();
startConn();
}
}