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中。
    nginx下session丢失原因及解决办法_第1张图片

浏览器中的Cookie也是K-V结构,key放的是SessionId ,value放的是具体的值。
下图是sessionId存放redis中的数据
nginx下session丢失原因及解决办法_第2张图片
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丢失的原因。

nginx下session丢失原因及解决办法_第3张图片

解决办法

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

比如基于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. 导入依赖

<dependency>
    <groupId>org.springframework.sessiongroupId>
    <artifactId>spring-session-data-redisartifactId>
    <version>1.3.1.RELEASEversion>
dependency>


<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webartifactId>
    <version>4.3.16.RELEASEversion>
dependency>

  1. web.xml配置

<filter>
    <filter-name>springSessionRepositoryFilterfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilterfilter-name>
    <url-pattern>/*url-pattern>
filter-mapping>

<context-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:applicationContext.xmlparam-value>
context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
  1. 创建并配置applicationContext-session.xml
    包扫描配置,这个标签中包含了的全部功能
    因此实际工作中不需要指定
 
    <context:annotation-config/>
    
    <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
       
        <property name="cookieSerializer"  ref="defaultCookieSerializer"/>
    bean>

    
    <bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
        
        <property name="cookiePath" value="/"/>
    bean>

    
    <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”);

你可能感兴趣的:(服务器,web)