最近面试的时候遇到了一个关于分布式如何实现session共享的问题,由于以前没有深入了解过,本着虚心好学的态度,花了2天时间研究实现了一下,并记录下来~~~

首先简单说一下什么是session:

客户端通过浏览器向服务器发送一个请求,服务器会根据这个请求创建一个seesion并保存,然后将sessionID以cookie的形式返回给客户端浏览器,那么这个客户端之后的请求都会带上这个sessionId以供服务器识别请求来源于哪个客户端。

因为tomcat session默认是存储在服务器端的,所以如果是分布式应用的话,因为应用是部署在多个服务器上的,然后负载均衡有可能将同一个浏览器的请求分发到不同的服务器上,这样的话如果还是使用默认的seesion管理机制就会出现session不一致的问题,目前解决session不一致的问题,我所了解的有2种方式

(1)使用session集群的方式-复杂、(2)使用第三方应用存储管理session的方式-简单

本文是以形式(2)的方式实现session共享,使用redis存储session实现分布式session共享,要使用redis存储session还需要用到spring session来替换tomcat session

下面简单说一下本文要做哪些事情(自己先准备好redis服务、nginx服务、2个tomcat服务)

1.环境介绍

2.创建一个spring session+redis管理session的maven web项目

3.将第2步创建的web项目部署到2个不同服务器的tomcat上,并且配置nginx负载均衡到2个tomcat服务器(1:1权重)

4.测试结果

一、环境介绍

首先说明一下我这边使用的是的3个VM虚拟机+1个远程redis服务

nginx:192.168.1.140

tomcat1:192.168.1.141

tomcat2:192.168.1.142

redis:个人远程服务


二、创建一个spring session+redis管理session的maven web项目

(1)pom.xml

因为使用到了spring session,所以为了方便起见,控制层我也使用spring mvc,当然,你们也可以使用自定义servlet。下面是pom依赖

<!-- spring session -->
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
  <version>2.0.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>io.lettuce</groupId>
  <artifactId>lettuce-core</artifactId>
  <version>5.0.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.0.7.RELEASE</version>
</dependency>
<!--spring mvc-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.0.7.RELEASE</version>
</dependency>
<!--jedis-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.9.0</version>
</dependency>
<!--servlet-->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>4.0.0</version>
</dependency>

(2)spring mvc配置文件(我存放的路径:src/main/resources/spring/springmvc.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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- 指定Controller所在的包 ,根据自己的controller包名替换-->
    <context:component-scan base-package="com.springsession.controller"/>

    <!-- Redis hostName填写你们自己的redis服务地址-->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="***" />
        <property name="port" value="6379" />
    </bean>
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
    </bean>

    <!--redis非集群管理session需要添加ConfigureRedisAction.NO_OP配置,
    若redis是集群管理session,Redis需要开启Keyspace Notifications功能的,默认是关闭的
    使用:redis-cli config set notify-keyspace-events Egx 命令开启,然后重启redis-->
    <util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>

    <!-- Session -->
    <bean id="redisHttpSessionConfiguration"  class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" >
        <property name="maxInactiveIntervalInSeconds" value="120" />
    </bean>
</beans>

(3)写一个Controller

@Controller
public class LoginController {

    @RequestMapping("/login")
    public void login(HttpServletRequest request, HttpServletResponse response){
        request.getSession().setAttribute("username","hzk");
        System.out.println("set ok!");
    }

    @RequestMapping("/getloginuser")
    @ResponseBody
    public String getLoginUser(HttpServletRequest request, HttpServletResponse response){
        try {
            return "141:"+request.getSession().getAttribute("username").toString();
        }catch (Exception e){
            e.printStackTrace();
            return "141:null";
        }
    }
}

(4)web.xml(为了使spring mvc配置生效以及使用spring session过滤请求,web.xml配置如下)

这里需要注意一点的就是,本着简约原则,spring mvc我没配置视图解析器,所以拦截路径使用*.do而不使用/*,因为使用/*的话所有的请求都被mvc拦截,但我没有配置视图解析器所以会出现404情况,所以这里大家注意一下就好了

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!--spring session filter配置-->
  <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>*.do</url-pattern>
  </filter-mapping>

  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--指定spring mvc配置文件位置-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/springmvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>

三、将web应用部署到2个tomcat并配置nginx负载均衡

1.首先将上边的web应用打成war包,然后解压,先将一份部署到141的tomcat上,然后修改web应用的LoginController将上面标色的141改成142后再编译打包一次,然后解压,这次部署到142的tomcat上

2.修改nginx的配置文件nginx.conf

在server外部添加以下配置

upstream mywebapp {
server 192.168.1.141:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.142:8080 weight=1 max_fails=2 fail_timeout=30s;
}

在server内部添加如下配置(pt_springsession是web应用的项目名

location /pt_springsession {
proxy_pass http://mywebapp/pt_springsession/;
}

然后启动2个服务器的tomcat以及nginx,准备就绪


四、测试结果

redis的数据我使用redis desktop manager查看,首先连接上我的redis服务,然后可以看到目前的数据是空

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


因为nginx和tomcat已经启动,这时打开浏览器访问:http://192.168.1.140/pt_springsession/login.do,此时我们刷新一下redis管理台看看

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


可以看到session已经存储到了redis了,然后我们测试一下是否有效,将刚刚的访问路径改成http://192.168.1.140/pt_springsession/getloginuser.do,连续访问几次可以查看到如下结果

 

 

 

 

 

 

 

 

 

 

 

 

 


可以看到无论负载均衡将请求分发到141或者142服务器上,都能到第三方存储(redis)处并读取到session信息,此时分布式的session共享已经实现了


五、总结

在上面的例子中,首先访问login.do,假设这次访问是在141上,141服务器生成一个session并将session存储到redis,然后将sessionId通过cookie的形式返回给浏览器,浏览器修改路径,访问getloginuser.do的时候带上刚刚获得的sessionID,这时nginx将请求分发到了142的机器上,142将拿到的sessionId去redis里面查找,看看有没有对应的session,有的话就返回对应的session信息,从而实现了session外部管理达到session共享的效果

2 对 “分布式-Redis+SpringSession实现session共享”的想法;

发表评论

电子邮件地址不会被公开。