memcached-session-manager是一个开源的高可用的Tomcat session共享解决方案,它支持Sticky模式和Non-Sticky模式。Sticky模式表示每次请求都会被映射到同一台后端Web服务器,知道该Web服务器宕机,这样session可先存放在服务器本地,等到请求处理完成再同步到后端memcached服务器;而当Web服务器宕机时,请求被映射到其他Web服务器,这时候,其他Web服务器可以从后端memcache中恢复session。对于Non-Sticky模式来说,请求每次映射的后端Web服务器是不确定的,当请求到来时,从memcached中加载session;当请求处理完成时,将session再协会到memcached。
以Non-Sticky模式为例,首先需要安装memcached的服务器,这个在上一篇中已经讲述过了。然后在Tomcat的$CATALINA_HOME/conf/context.xml文件配置SessionManager,具体配置如下:
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:10.10.195.112:11211"
sticky="false"
sessionBackupAsync="false"
sessionBackupTimeout="1000"
lockingMode="auto"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
其中,memcachedNodes指定了memcached的节点;sticky表示是否采用sticky模式;sessionBackuoAsync表示是否采用异步方式备份session;lockingMode表示session的锁定模式;auto表示对于只读请求,session将不会被锁定,如果包含写入请求,则session会被锁定;requestUriIgnorePattern表示忽略的url; transcoderFactoryClass用来指定序列化的方式,这里采用的是Kryo序列化,也是memcached-session-manager比较推荐的一种序列化方式。也可以采用其他序列化方式,譬如:javolution-serializer, xstream-serializer, flexjson-serializer。
memcached-session-manager依赖于memcached-session-manager-{version}.jar,如果使用的是tomcat6,则还需要下载memcached-session-manager-tc6-{version}.jar,如果是tomcat7则采用memcached-session-manager-tc7-{version}.jar的包(博主采用的是tomcat7+jdk7)。还需要spymemcached-2.7.3.jar,在启动tomcat之前需要将这些jar包放到tomcat的lib目录下。如果采用Kryo方式序列化,还需要加入其他一些包,所有包如下:
注意这些包的版本,搞错一下就不能实现其功能。这里博主已经整理好了,可以在这里下载。
常见错误
1 启动tomcat出现错误案例1
严重: Error manager.start()
org.apache.catalina.LifecycleException: Failed to start component [de.javakaffee.web.msm.MemcachedBackupSessionManager[/Architecture]]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:154)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5501)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:649)
at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:672)
at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1859)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NoClassDefFoundError: org/objenesis/strategy/InstantiatorStrategy
at de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory.getTranscoder(KryoTranscoderFactory.java:64)
at de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory.createTranscoder(KryoTranscoderFactory.java:47)
at de.javakaffee.web.msm.MemcachedSessionService.createTranscoderService(MemcachedSessionService.java:493)
at de.javakaffee.web.msm.MemcachedSessionService.startInternal(MemcachedSessionService.java:448)
at de.javakaffee.web.msm.MemcachedBackupSessionManager.startInternal(MemcachedBackupSessionManager.java:535)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
... 12 more
Caused by: java.lang.ClassNotFoundException: org.objenesis.strategy.InstantiatorStrategy
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 18 more
这个是包版本问题,memcached-session-manager以及msm-kryo-serializer版本不对。
2 启动tomcat出现错误案例2
严重: Error manager.start()
org.apache.catalina.LifecycleException: Failed to start component [de.javakaffee.web.msm.MemcachedBackupSessionManager[]]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:154)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5501)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:649)
at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1245)
at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1895)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NoSuchMethodError: de.javakaffee.web.msm.MemcachedSessionService$SessionManager.getContainerClassLoader()Ljava/lang/ClassLoader;
at de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory.createTranscoder(KryoTranscoderFactory.java:47)
at de.javakaffee.web.msm.MemcachedSessionService.createTranscoderService(MemcachedSessionService.java:449)
at de.javakaffee.web.msm.MemcachedSessionService.startInternal(MemcachedSessionService.java:425)
at de.javakaffee.web.msm.MemcachedBackupSessionManager.startInternal(MemcachedBackupSessionManager.java:509)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
... 12 more
这个也是包版本问题,msm-kryo-serializer版本不对
3 在context.xml中配置failoverNodes节点,如下
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:10.10.195.112:11211"
sticky="false"
failoverNodes="n1"
sessionBackupAsync="false"
lockingMode="auto"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
启动tomcat报错:
严重: Error manager.start()
org.apache.catalina.LifecycleException: Failed to start component [de.javakaffee.web.msm.MemcachedBackupSessionManager[/Architecture]]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:154)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5501)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:649)
at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:672)
at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1859)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalArgumentException: For a single memcached node there should/must no failoverNodes be specified.
at de.javakaffee.web.msm.MemcachedNodesManager.createFor(MemcachedNodesManager.java:224)
at de.javakaffee.web.msm.MemcachedSessionService.createMemcachedNodesManager(MemcachedSessionService.java:445)
at de.javakaffee.web.msm.MemcachedSessionService.startInternal(MemcachedSessionService.java:410)
at de.javakaffee.web.msm.MemcachedBackupSessionManager.startInternal(MemcachedBackupSessionManager.java:509)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
... 12 more
可以看到报错中“Caused by: java.lang.IllegalArgumentException: For a single memcached node there should/must no failoverNodes be specified.”这句,需要把failoverNodes去掉。
应用举例
这里博主采用keepalived+lvs进行负载均衡搭建。详细可以参考LVS+Keepalived实现负载均衡和双机热备,不了解也没关系,这个不是本篇的重点。
主机ip:10.10.195.107
备机ip:10.10.195.187
虚拟ip:10.10.195.188
(配的是双机热备+负载均衡,主备机会虚拟出一个访问的ip,当主机宕机时,会自动切换到备机,对于用户而言完全透明。了解即可)
在主备机web路径的根目录下放入test.jsp,代码如下(备机的就把 10.10.195.107换成10.10.195.187):
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<%@ page session="true" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Memcached-session-manager</title>
</head>
<body >
10.10.195.107
<%
out.print("<br> SESSION-ID:"+session.getId()+"<br>");
%>
</body>
</html>
通过浏览器访问:http://10.10.195.188:8080/test.jsp
可以看到页面显示:
10.10.195.107
SESSION-ID:6F9B1255A1C9E3F7D5FC144DD81217CD-n1
在主机中输入: sudo service keepalived stop之后(切换到备机),页面显示:
10.10.195.187
SESSION-ID:6F9B1255A1C9E3F7D5FC144DD81217CD-n1
可以看到访问的页面变了,但是session并没有改变,因为它存在memcached服务器中。
参考资料:
1. LVS+Keepalived实现负载均衡和双机热备
2. MSM–Memcached_Session_Manager介绍及使用
3. memcached-session-manager配置
4. 《大型分布式网站架构设计与实践》陈康贤著