nginx下session丢失原因及解决办法

session机制

简单了解

由于HTTP协议是无状态的协议,一次浏览器和服务器的交互过程就是:
浏览器:你好吗?
服务器:很好!
这就是一次会话,对话完成后,这次会话就结束了,服务器端并不能记住这个人,下次再对话时,服务器端并不知道是上一次的这个人,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是Session。

sessionId传递流程

  1. 客户端在发送请求时 ,会携带Cookie数据发送给Tomcat服务器,
  2. 服务器会解析Cookie中的数据找到sessionId,看sessionId是否有值,如果没有值(浏览器第一次访问出现),Tomcat会自动给sessionId赋值,sessionId通过UUID生成,是唯一的,不存在重复的可能性。
  3. 如果sessionId有值,服务器会获取sessionId,将它的值与服务器中的JVM里的session容器中的key进行比较,如果存在,这说明会话存在,否则sessionId不存在或者过期,这时sessionId会由Tomcat服务器创建生成并赋值。
    这里的Session容器是K-V结构,类似于 map,value存放的是httpSession类型的对象(这里放的是对象的地址)
  4. 数据响应完成后,Tomcat将sessionId放在Cookie中返回给浏览器,存放在浏览器的Cookie中。
    在这里插入图片描述

浏览器中的Cookie也是K-V结构,key放的是SessionId ,value放的是具体的值。
下图是sessionId存放redis中的数据
在这里插入图片描述
HASH是sessionId
sessionAttr:session属性
maxInactiveInterval:最大存在时间,默认是30分钟,单位是秒
lastAccessedTime:最后一次访问时间,单位是毫秒,每次sessionId返回这个值是一定更新的,所以其他值也会跟着更新
creationTime:sessionId创建时间,单位是毫秒

这个session是加了定时器的,到30分钟自动销毁

session丢失原因

nginx多服务器下浏览器访问,会出现session丢失。

实际上通过上面介绍的单服务器对sessionId的识别与创建可以知道,如果Tomcat服务器的session容器中找不到sessionId的值,那就会判定是第一次访问,主动给它生成sessionId。

在多个服务器下,通过nginx的负载均衡,出现可以访问多个服务器的情况。访问第一个服务器,由第一个服务器给sessionId赋值,返回给客户端浏览器,之后再次发出请求,这个请求分配给了另一个Tomcat服务器,这个服务器的Session容器中没找到传过来的sessionId的值,这时它认为这时第一次过来访问的,就给sessionId赋一个新值,响应结果后把sessionId返回给浏览器。之后在发送请求,这个请求分配给第一次访问的Tomcat,它找不到sessionId的值,判定第一次访问,又给重新赋值…
(sessionId是通过UUID生成,全球唯一,不存在重复的可能性)
这就是session丢失的原因。

在这里插入图片描述

解决办法

第一种是使用容器扩展插件来实现

比如基于Tomcat的tomcat-redis-session-manager插件,基于Jetty的jetty-session-redis插件、memcached-session-manager插件

优点:对项目来说是透明的,无需改动代码
缺点:由于过于依赖容器,一旦容器升级或者更换意味着又得重新配置(其实底层是,复制session到其它服务器,所以会有一定的延迟,也不能部署太多的服务器。)

第二种是使用Nginx负载均衡的ip_hash策略实现

用户每次访问都绑定到同一台具体的后台tomcat服务器实现session总是存在

  • 这种方案的局限性是ip不能变,如果手机从北京跳到河北,那么ip会发生变化;
  • 另外负载均衡的时候,如果某一台服务器发生故障,那么会重新定位,也会跳转到别的机器。

第三种是自己写一套Session会话管理的工具类

在需要使用会话的时候都从自己的工具类中获取,而工具类后端存储可以放到Redis中
优点:灵活性很好,自己写
缺点:不容易写,开发需要一些额外的时间,不一定稳定

第四种是使用框架的会话管理工具——Spring session

这个方案既不依赖tomcat容器,又不需要改动代码,只需修改对应的配置文件,由Spring session框架为我们提供,可以说是目前非常完美的session共享解决方案

SpringSession解决过程

将存放session的Session容器放在一个公共的位置,让每一个服务器都可以访问到,像是redis,数据库,磁盘文件等地方

举个例子

  1. 将同一个项目部署到两个Tomcat服务器上面
  2. 通过端口不同进行访问,获取session,到这一步可以证明session不同服务器之间不能共享(session不是放在公共区域)
    解决:
  3. 导入依赖
<!-- Spring session redis 依赖start -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <version>1.3.1.RELEASE</version>
</dependency>
<!-- Spring session redis 依赖end -->
<!-- spring web模块依赖 start -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<!-- spring web模块依赖end -->
  1. web.xml配置
<!-- 配置springSessionRepositoryFilter过滤器 -->
<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>
<!-- (3)加载Spring配置文件 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  1. 创建并配置applicationContext-session.xml
    <context:component-scan base-package="com"/>包扫描配置,这个标签中包含了<context:annotation-config/>的全部功能
    因此实际工作中不需要指定<context:annotation-config/>
 <!--
        启动Spring的注解支持,由于SpringSession内部使用到了Spring的相关注解因此必须启动
        注意:
          <context:component-scan base-package="com"/> 包扫描配置,这个标签中包含了<context:annotation-config/>的全部功能
          因此实际工作中不需要指定<context:annotation-config/>
     -->
    <context:annotation-config/>
    <!--定义SpringSession的配置Bean -->
    <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
       <!--指定Cookie的序列化规则对象,用于改变SpringSessionCookie的存放规则 -->
        <property name="cookieSerializer"  ref="defaultCookieSerializer"/>
    </bean>

    <!--自定义Cookie规则对象 -->
    <bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
        <!--指定SpringSession的Cookie数据要存放到域名的根路径下,用于解决同域名下不同项目的Session共享 -->
        <property name="cookiePath" value="/"/>
    </bean>

    <!-- 配置jedis连接工厂,用于连接redis
        如果工作中项目已经集成了Redis那么这个配置和可以不写的
     -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="192.168.245.128"/>
        <property name="port" value="6379"/>
        <property name="password" value="123456"/>
    </bean>
  1. 在applicationContext.xml文件导入applicationContext-session.xml文件
<import resource="applicationContext-session.xml"/>
  1. 部署测试
    先开启redis服务,在开启两个Tomcat服务,一个Tomcat执行放session,一个Tomcat执行取session
    放session方法:request.getSession().setAttribute(“sessionName”,“hello session!”);
    取session方法:request.getSession().getAttribute(“sessionName”);
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值