最近遇到一個很詭異的問題,SAE的javaruntime有一部分用戶反饋session老是丟失,致使至關一部分用戶的代碼部署後登陸功能沒法正常使用。java
先說下sae session的託管方式:web
這裏先說下SAE的session託管方式,採用分佈式session,最終由mc實現session的綜合存儲,從而達到多jvm實例的狀況下,session同步。分佈式session的實現代碼最終調用spymmecached的客戶的對session的後端mc進行增刪改。後端
經過分析用戶運行時產生的日誌分析發現一個以下的錯誤session
java.lang.ClassNotFoundException: com.xxx.xxx.xxxx //這裏通常都是mvc裏的model代碼,一些基本的用戶信息處理類,序列化後存session是通常登陸的處理方式 at java.net.URLClassLoader$1.run(URLClassLoader.java:217) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:205) at java.lang.ClassLoader.loadClass(ClassLoader.java:388) at java.lang.ClassLoader.loadClass(ClassLoader.java:333) at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:419) at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:372) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:279) at inner.spy.memcached.transcoders.SerializingTranscoder$DeserializableObject.resolveClass(SerializingTranscoder.java:323) //調用棧這裏是調用到了spymemecached客戶端改寫的反序列化的代碼 at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1592) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1513) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1749) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1346) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:368) at inner.spy.memcached.transcoders.SerializingTranscoder.deserialize(SerializingTranscoder.java:294) at inner.spy.memcached.transcoders.SerializingTranscoder.decode(SerializingTranscoder.java:204) at inner.spy.memcached.transcoders.TranscodeService$1.call(TranscodeService.java:63) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334) at java.util.concurrent.FutureTask.run(FutureTask.java:166) at inner.spy.memcached.transcoders.TranscodeService$Task.run(TranscodeService.java:110) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) at java.lang.Thread.run(Thread.java:714)
從以上的錯誤能夠看出就是類找不到,照通常的想法應該是看看打包後的war包中是否包含這個類,不幸的是這個類通常都是在的,那麼就是其餘的問題來看看具體錯誤處的代碼mvc
public Object deserialize(byte[] in) { //這個方法是最終拿到session mc的數據後判斷爲object的時候,調用的反序列化 Object rv = null; ByteArrayInputStream bis = null; ObjectInputStream is = null; try { if (in != null) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); //這裏是是拿到當前上下文的classloader DeserializableObject ois = new DeserializableObject( new ByteArrayInputStream(in),loader); return ois.readObject(); } } catch (IOException e) { getLogger().warn("Caught IOException decoding %d bytes of data", in == null ? 0 : in.length, e); } catch (ClassNotFoundException e) { getLogger().warn("Caught CNFE decoding %d bytes of data", in == null ? 0 : in.length, e); } finally { CloseUtil.close(is); CloseUtil.close(bis); } return rv; } /** * 反序列化字節流,使用一個指定的classloader序列化class流。 * @author Administrator * */ private static class DeserializableObject extends ObjectInputStream { //具體處理邏輯 private ClassLoader loader; DeserializableObject(InputStream in, ClassLoader loader) throws IOException { super(in); this.loader = loader; //解析一些用戶類的時候這裏的classloader變成了StartJarClassloader } @Override protected Class<?> resolveClass(ObjectStreamClass desc)throws IOException, ClassNotFoundException { return Class.forName( desc.getName(),true,loader ); } }
經過過debug發現,在解析一些用戶的類的時候,當前上下文的classloader不知道怎麼變成了StarJarClassloader對象實例,StartJarClassloader的對象實例加載的類對象是在jetty/lib下的相關jar包,固然沒法找到用戶本身寫的類對象,查了半天也沒有確認爲何會致使這樣的問題,那麼只能換個思路解決問題了,能不能在jetty啓動的時候將加載用戶類的WebAppClassloader的實例保存起來,而後在反序列化的時候直接拿這個classloader來反射具體的類,因而考慮半天具體這樣實現app
/** * 自定義的反序列化 */ public Object deserialize(byte[] in) { Object rv = null; ByteArrayInputStream bis = null; ObjectInputStream is = null; try { if (in != null) { // ClassLoader loader = Thread.currentThread().getContextClassLoader(); ClassLoader loader = ServiceStatus.loader !=null?ServiceStatus.loader : Thread.currentThread().getContextClassLoader(); //用一個全局的類存下加載用戶代碼的classloader DeserializableObject ois = new DeserializableObject( new ByteArrayInputStream(in),loader); return ois.readObject(); } } catch (IOException e) { getLogger().warn("Caught IOException decoding %d bytes of data", in == null ? 0 : in.length, e); } catch (ClassNotFoundException e) { getLogger().warn("Caught CNFE decoding %d bytes of data", in == null ? 0 : in.length, e); } finally { CloseUtil.close(is); CloseUtil.close(bis); } return rv; } /** * 反序列化字節流,使用一個指定的classloader序列化class流。 * @author Administrator * */ private static class DeserializableObject extends ObjectInputStream { private ClassLoader loader; DeserializableObject(InputStream in, ClassLoader loader) throws IOException { super(in); this.loader = loader; } @Override protected Class<?> resolveClass(ObjectStreamClass desc)throws IOException, ClassNotFoundException { return Class.forName( desc.getName(),true,loader ); }
按照以上的處理方式處理後發現,果真解決了這個問題,真是曲線救國啊~~~eclipse
這裏可能要問了怎麼獲取到到加載用戶代碼的WebAppClassloader呢 仍是貼代碼webapp
public class SaeWebAppContext extends WebAppContext { //這裏的父類是構造用戶代碼的上下文,在這裏會解析用戶的war包,然後建立屬於用戶代碼的WebAppClassloader @Override public void setClassLoader(ClassLoader classLoader) { // TODO Auto-generated method stub super.setClassLoader(classLoader); //在父類建立完後 存下這個對象的引用 ServiceStatus.loader = getClassLoader(); } }
自此 問題解決 目前運行良好,接下來要查查是什麼緣由致使classloader變化,等查到了在繼續分析緣由
jvm
本文完分佈式