最近遇到一个很诡异的问题,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
本文完分布式