Nginx + Tomcat + Session 共享解决方案
Contents
注意, 这里使用的是
Linux
+Tomcat 7.x
+JDK 7
+Nginx
+Spring Session
的方式
安装Redis
配置Tomcat
这里为了简单,直接复制两份Tomcat,分别将目录命名为spring_session_master
, spring_session_slave
。
然后还要修改各个Tomcat下的conf/server.xml
配置文件,修改下其中的相关端口。
我这里,将master
的改为了9090
端口,将slave
的改为了9096
端口.并且一起启动它.
配置Nginx
upstream springsession {
server localhost:9090;
server localhost:9096;
}
server {
listen 80 default_server;
server_name localhost;
location / {
proxy_pass http://springsession;
}
}
然后重启下Nginx: sudo nginx -s reload
Spring Session 的使用
Spring中的spring.xml
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<!-- scan package -->
<context:component-scan base-package="test.yang.test" />
<context:annotation-config />
<mvc:annotation-driven />
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:core.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true" />
</bean>
<import resource="session.xml" />
</beans>
Spring中的关于session的配置文件session.xml
的内容
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<context:annotation-config />
<bean
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" />
<bean
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:port="${redis.port}"
p:hostName="${redis.host}"
p:database="${redis.database}"/>
</beans>
SpringMVC中的spring-servlet.xml
文件内容
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<context:property-placeholder location="classpath:core.properties"
ignore-unresolvable="true" order="2" />
<mvc:resources location="/res/" mapping="/res/**" />
<mvc:annotation-driven />
<context:component-scan base-package="test.yang.test" />
<bean id="velocityConfig"
class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/velocity" />
<property name="velocityProperties">
<props>
<prop key="input.encoding">utf-8</prop>
<prop key="output.encoding">utf-8</prop>
<prop key="file.resource.loader.cache">false</prop>
</props>
</property>
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".vm" />
<property name="contentType" value="text/html; charset=utf-8" />
<property name="exposeRequestAttributes" value="true" />
<property name="exposeSessionAttributes" value="true" />
<property name="exposeSpringMacroHelpers" value="true" />
</bean>
</beans>
应用的 web.xml
文件内容
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>uniweibov2</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<session-config>
<session-timeout>45</session-timeout>
</session-config>
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error</location>
</error-page>
<error-page>
<error-code>504</error-code>
<location>/error</location>
</error-page>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/conf/spring.xml
</param-value>
</context-param>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<servlet-name>mvc-dispatcher</servlet-name>
</filter-mapping>
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/conf/spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
项目的pom.xml
文件内容
<properties>
<spring.version>3.2.6.RELEASE</spring.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<version>1.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.8</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>这
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
core.properties
文件内容
#redis server
redis.host = 10.0.0.40
redis.port = 6379
redis.database = 8
Controller示例代码Hello.java
package test.yang.test;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/hello")
public class Hello {
@RequestMapping("/index")
public void name(HttpSession session) {
session.setAttribute("hello", "world");
System.out.println(session.getId());
}
@RequestMapping("/get")
public void get(HttpSession session) {
String world = (String) session.getAttribute("hello");
System.out.println(session.getId());
System.out.println("结果为:" + world);
}
}
Spring Session 原理
为什么按上面这样子直接配置就可以直接实现Session共享了呢?让我们一步一步来解释。
首先是session.xml
文件:
<context:annotation-config />
<bean
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" />
这段代码,就实现了Spring Session中使用redis作为存储session的配置文件。进入源码,可以发现,它使用了RedisTemplate
以及import org.springframework.session.web.http.SessionRepositoryFilter
。
SpringSession实现了HttpSession的接口来处理Session相关的业务操作,它提供了两种解决方案,一种是默认的使用Map
来保存Session,还有一种,是为了在集群中解决Session共享的方案的使用Redis来处理Session数据。
它们分别是接口org.springframework.session.SessionRepository
(即处理Session的接口)的两个实现:
org.springframework.session.MapSessionRepository
和 org.springframework.session.RedisOperationsSessionRepository
。
然后,它们在SessionRepositoryFilter
,被注入进来处理对应的Session实现的策略方式。注意,这个是Filter,为了在Spring中生效,所以在web.xml
中添加了
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
它是一个代理过滤器,它会在Spring Context里,查看实现了Filter的Bean,然后调用doFilter
方法来进代理。因为SessionRepositoryFilter
需要一些Spring Context的处理。所以才要在web.xml
里配置这段过滤器的代理过滤器。
整个流程就是这样子了。再详细的, 可以根据此思想,深入每一个相关的类的源码。或者直接进行单步调试。
测试
全部启动完毕后, 每将访问http://localhost/hello/index
,就可以看到tomcat的控制台里,打印sessionID的内容了,因为默认情况下是轮询的方式,所以master和slave会循环显示。即第一次请求在一个tomcat的控制台显示,下一次请求就会在另一个Tomcat里显示,如此循环。
然后可以关闭一个Tomcat(模拟进程死掉,或重新部署项目时,先让一个运行着,升级另一个Tomcat的应用,完成后再切换),可以看到sessionId没有变。当再次启动这个Tomcat时, 发现负载均衡又正常工作了,而且SessionID没有变。