pushlet一个很坑爹的地方。。。

来源:互联网 发布:矩阵微分方程 编辑:程序博客网 时间:2024/05/21 09:16

上回书说道pushlet的集群解决方案,但是节点是全部起的,没有什么问题。

这礼拜线上节点先起了两个,之后起得节点全pushlet全失效了,觉得很不可思议,又再次探究了一下。

HTTP ERROR 503Problem accessing /pushlet.srv. Reason:    javax.servlet.ServletException: Failed to initialize Pushlet framework java.lang.NullPointerException

这是当时爆的一个异常信息,是前端请求/pushlet.srv时的异常。

/pushlet.srv就像配置servlet一样是配置在web.xml里面的,于是就去找pushlet的处理类

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. **//*     */ package nl.justobjects.pushlet.servlet;/*     *//*     */ import java.io.IOException;/*     */ import java.io.InputStreamReader;/*     */ import java.util.Enumeration;/*     */ import javax.servlet.ServletContext;/*     */ import javax.servlet.ServletException;/*     */ import javax.servlet.http.HttpServlet;/*     */ import javax.servlet.http.HttpServletRequest;/*     */ import javax.servlet.http.HttpServletResponse;/*     */ import nl.justobjects.pushlet.Version;/*     */ import nl.justobjects.pushlet.core.Command;/*     */ import nl.justobjects.pushlet.core.Config;/*     */ import nl.justobjects.pushlet.core.Controller;/*     */ import nl.justobjects.pushlet.core.Dispatcher;/*     */ import nl.justobjects.pushlet.core.Event;/*     */ import nl.justobjects.pushlet.core.EventParser;/*     */ import nl.justobjects.pushlet.core.EventSourceManager;/*     */ import nl.justobjects.pushlet.core.Protocol;/*     */ import nl.justobjects.pushlet.core.Session;/*     */ import nl.justobjects.pushlet.core.SessionManager;/*     */ import nl.justobjects.pushlet.util.Log;/*     */ import nl.justobjects.pushlet.util.Servlets;/*     *//*     */ public class Pushlet extends HttpServlet/*     */ implements Protocol/*     */ {/*     */ public void init()/*     */ throws ServletException/*     */ {/*     */ try/*     */ {/*  31 */ String webInfPath = getServletContext().getRealPath("/") + "/WEB-INF";/*  32 */ Config.load(webInfPath);/*     *//*  34 */ Log.init();/*     *//*  37 */ Log.info("init() Pushlet Webapp - version=" + Version.SOFTWARE_VERSION + " built=" + Version.BUILD_DATE);/*     *//*  40 */ SessionManager.getInstance().start();/*     *//*  43 */ Dispatcher.getInstance().start();/*     *//*  46 */ if (Config.getBoolProperty("sources.activate"))/*  47 */ EventSourceManager.start(webInfPath);/*     */ else/*  49 */ Log.info("Not starting local event sources");/*     */ }/*     */ catch (Throwable t) {/*  52 */ throw new ServletException("Failed to initialize Pushlet framework " + t, t);/*     */ }/*     */ }/*     *//*     */ public void destroy() {/*  57 */ Log.info("destroy(): Exit Pushlet webapp");/*     *//*  59 */ if (Config.getBoolProperty("sources.activate"))/*     */ {/*  61 */ EventSourceManager.stop();/*     */ }/*     */ elseLog.info("No local event sources to stop");/*     *//*  67 */ Dispatcher.getInstance().stop();/*     *//*  70 */ SessionManager.getInstance().stop();/*     */ }/*     *//*     */ public void doGet(HttpServletRequest request, HttpServletResponse response)/*     */ throws ServletException, IOException/*     */ {/*  77 */ Event event = null;/*     */ Enumeration e;/*     */ try/*     */ {/*  81 */ String eventType = Servlets.getParameter(request, "p_event");/*     *//*  84 */ if (eventType == null) {/*  85 */ Log.warn("Pushlet.doGet(): bad request, no event specified");/*  86 */ response.sendError(400, "No eventType specified");/*  87 */ return;/*     */ }/*     *//*  91 */ event = new Event(eventType);/*  92 */ for (e = request.getParameterNames(); e.hasMoreElements();) {/*  93 */ String nextAttribute = (String) e.nextElement();/*  94 */ event.setField(nextAttribute, request.getParameter(nextAttribute));/*     */ }/*     *//*     */ }/*     */ catch (Throwable t)/*     */ {/* 100 */ Log.warn("Pushlet: Error creating event in doGet(): ", t);/* 101 */ response.setStatus(400);/* 102 */ return;/*     */ }/*     *//* 106 */ doRequest(event, request, response);/*     */ }/*     *//*     */ public void doPost(HttpServletRequest request, HttpServletResponse response)/*     */ throws ServletException, IOException/*     */ {/* 114 */ Event event = null;/*     */ try/*     */ {/* 117 */ event = EventParser.parse(new InputStreamReader(request.getInputStream()));/*     *//* 120 */ if (event.getEventType() == null) {/* 121 */ Log.warn("Pushlet.doPost(): bad request, no event specified");/* 122 */ response.sendError(400, "No eventType specified");/* 123 */ return;/*     */ }/*     *//*     */ }/*     */ catch (Throwable t)/*     */ {/* 129 */ Log.warn("Pushlet:  Error creating event in doPost(): ", t);/* 130 */ response.setStatus(400);/* 131 */ return;/*     */ }/*     *//* 135 */ doRequest(event, request, response);/*     */ }/*     *//*     */ protected void doRequest(Event anEvent, HttpServletRequest request, HttpServletResponse response)/*     */ {/* 144 */ String eventType = anEvent.getEventType();/*     */ try/*     */ {/* 149 */ Session session = null;/* 150 */ if (eventType.startsWith("join"))/*     */ {/* 152 */ session = SessionManager.getInstance().createSession(anEvent);/*     *//* 154 */ String userAgent = request.getHeader("User-Agent");/* 155 */ if (userAgent != null)/* 156 */ userAgent = userAgent.toLowerCase();/*     */ else {/* 158 */ userAgent = "unknown";/*     */ }/* 160 */ session.setUserAgent(userAgent);/*     */ }/*     */ else/*     */ {/* 166 */ String id = anEvent.getField("p_id");/*     *//* 169 */ if (id == null) {/* 170 */ response.sendError(400, "No id specified");/* 171 */ Log.warn("Pushlet: bad request, no id specified event=" + eventType);/* 172 */ return;/*     */ }/*     *//* 176 */ session = SessionManager.getInstance().getSession(id);/*     *//* 179 */ if (session == null) {/* 180 */ response.sendError(400, "Invalid or expired id: " + id);/* 181 */ Log.warn("Pushlet:  bad request, no session found id=" + id + " event=" + eventType);/* 182 */ return;/*     */ }/*     *//*     */ }/*     *//* 190 */ Command command = Command.create(session, anEvent, request, response);/* 191 */ session.getController().doCommand(command);/*     */ }/*     */ catch (Throwable t) {/* 194 */ Log.warn("Pushlet:  Exception in doRequest() event=" + eventType, t);/* 195 */ t.printStackTrace();/* 196 */ response.setStatus(500);/*     */ }/*     */ }/*     */ }

反编译的代码,凑活着看吧。

那个异常是在pushlet的init方法里面爆的,很显然上面一个地方爆空指针了。

一个最直接的疑点就是为啥会报异常。

再仔细想想,我web.xml中的配置

<servlet>          <servlet-name>pushlet</servlet-name>          <servlet-class>nl.justobjects.pushlet.servlet.Pushlet</servlet-class>          <load-on-startup>3</load-on-startup>      </servlet> 


pushlet的初始化已改发生在应用初始化的时候,并且init方法只会被调用一遍,为什么只会被调用一遍的init方法中会持续报错呢?

答案显然只有一个,初始化失败了。

也就是说第一次调用init方法的时候就报错了

去看线上日志也没有我想要的答案,但至少确定,是在初始化的时候发生的异常。

通过断点调试(class文件调试的话需要插件)我们发现,后面请求中爆的空指针异常来自

SessionManager.getInstance().start();
getInstance返回了一个null

让我们来看看这个getInstance方法究竟是个啥

public static SessionManager getInstance() { return instance; }
一开始我看到这里的时候我是惊呆了的。。。我还以为是一个复杂的静态工厂方法
既然getInstance只是返回一个实例,那真正的初始化是在哪儿的呢

我们来到SessionManager的静态块

try/*     */ {/*  33 */ instance = (SessionManager) Config.getClass("sessionmanager.class", "nl.justobjects.pushlet.core.SessionManager").newInstance();/*  34 */ Log.info("SessionManager created className=" + instance.getClass());/*     */ } catch (Throwable t) {/*  36 */ Log.fatal("Cannot instantiate SessionManager from config", t);/*     */ }/*     */ }

原来实例化发生在静态块里面。

很明确,应该是执行静态块的时候发什么了什么异常,导致session无法正常实例化,因为静态块只在类加载的时候执行一次,我们的instance就永远只能是null了

那究竟又是发生么什么导致静态块执行失败了呢?

我们到Config类里继续看

public static Class getClass(String aClassNameProp, String aDefault)/*     */ throws PushletException/*     */ {/*  50 */ String clazz = (aDefault == null)? getProperty(aClassNameProp): getProperty(aClassNameProp, aDefault);/*     */ try/*     */ {/*  53 */ return Class.forName(clazz);/*     */ }/*     */ catch (ClassNotFoundException t) {/*  56 */ throw new PushletException("Cannot find class for " + aClassNameProp + "=" + clazz, t);/*     */ }/*     */ }public static String getProperty(String aName, String aDefault) {/*  88 */ return properties.getProperty(aName, aDefault);/*     */ }/*     */public static String getProperty(String aName) {/*  92 */ String value = properties.getProperty(aName);/*  93 */ if (value == null) {/*  94 */ throw new IllegalArgumentException("Unknown property: " + aName);/*     */ }/*  96 */ return value;/*     */ }
public static void load(String aDirPath)/*     */ {/*     */ try/*     */ {/*  67 */ Log.info("Config: loading pushlet.properties from classpath");/*  68 */ properties = Sys.loadPropertiesResource("pushlet.properties");/*     */ }/*     */ catch (Throwable t) {/*  71 */ String filePath = aDirPath + File.separator + "pushlet.properties";/*  72 */ Log.info("Config: cannot load pushlet.properties from classpath, will try from " + filePath);/*     */ try/*     */ {/*  75 */ properties = Sys.loadPropertiesFile(filePath);/*     */ } catch (Throwable t2) {/*  77 */ Log.fatal("Config: cannot load properties file from " + filePath, t);/*     *//*  80 */ return;/*     */ }/*     */ }/*     *//*  84 */ Log.info("Config: loaded values=" + properties);/*     */ }
我们发现getClass方法进去会调用一个getProperty方法,我们遇到了一个可能会爆空指针的点
properties.getProperty(aName, aDefault)
如果properties为空的话就会报错,

而这个properties的赋值是在load方法里面的,只要保证load方法在getProperty方法之前被调用就不会爆空指针了。

那load方法又是什么时候被调用的呢,让我们回到pushlet类的init方法。

先调load在初始化session,没毛病啊,这流程下来没有任何问题啊!

怎么可能执行session静态块的时候load方法还没执行啊!

一开始我也想不通,后来看了点基础知识。。web容器的初始化过程


图片网上copy的。。。

spring容器的初始化在servlet初始化之前。也就是说这个加载顺序完完全全具有发生调用session静态块在load之前,即在load之前调用到了session的条件

那么问题来了,到底是啥调用了session呢。。。

然后一想就想到了。。。没错,就是那个mq的消费者。

spring容器初始化完成,mq监听容器也完成了初始化,mq消费者开始消费消息。

有人可能会问了,应用刚起来,哪儿来的消息啊。。关键是为了做到集群,我确实用到了mq订阅模式。

因为第二次发布的时候,是先起了两个节点,中间可能有人发了mq,到后面节点启动的时候,每个节点在spring容器一初始化好就开始了消费。。。。

后面节点启动时会较pushlet初始化前开始消费,这一点都认同的吧。。

那么问题来了,我消费者里写了啥呢。。。说来惭愧,就是利用pushlet发送消息的方法,也就是说,确确实实在pushlet初始化前调用了session。。。。




问题知道出在哪儿解决就简单了。我的做法是在调用pushlet发消息的工具类里写个静态块,初始化手动pushlet。。。。



原创粉丝点击