1 背景
由于种种原因,http协议是无状态的([参考这里](#emfdld)),两次请求之间没有任何联系。而现在大多数web应用是需要记录会话信息的,比如,用户输入用户名密码登录到系统后,再访问系统中的其它页面,系统应该能够识别到该用户已经登录了。为了解决这个问题,出现了在客户端保持状态的cookie和服务端保持状态的session技术。<br /> 我们可以把这个问题做个类别,把用户访问网站类别成顾客到咖啡店消费。咖啡店为了促销,推出了消费满5杯赠送一杯的优惠。而现实情况中,一次消费5杯的情况比较少,这就需要用某种方式记录下来顾客的消费数量,便于下次计数。有如下几个方案:
1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。
2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。
3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。
2 cookie
cookie机制就像上面说的一样简单。关键需要解决以下几个问题:cookie如何发放、存放什么以及如何使用。
cookie一般是服务端生成,在http响应头中设置一行特殊的指示以提示浏览器按照指示生成,同时也可以通过javascript脚本生成。
cookie内容有:名称,值,失效时间,路径,域。其中失效时间可以设置为0,-1和一个具体的值。如果设置为0,表示立即销毁cookie,即不保存cookie;如果为-1,表示该cookie的生命周期为浏览器会话期间,关闭浏览器cookie即失效;如果是一个具体的值,浏览器会将该cookie保持到硬盘中进行持久化,关闭浏览器后再次打开仍然有效。
浏览器在发送HTTP请求时,会自动检索出与本次请求相匹配的cookie(检查过期时间、路径、域),随HTTP请求一块发送给服务端。
3 session
session是一种服务端保持状态的机制。服务器使用类似散列表的数据结构来保存不同的状态。基本原理如下:
客户端第一次发起HTTP请求后,服务端生成一个session对象,对象里包括sessionid、请求时间、客户端IP等信息,其中sessionid为该session对象的唯一标识,服务端在HTTP响应信息中将该sessionid发送给浏览器。浏览器后续的请求都将这个sessionid传给服务端,服务端按照sessionid找到对应的session对象完成身份识别。
以上过程中,在将sessionid传给浏览器端后,如何保证后续的请求都会带着sessionid呢?一般有2种方案。一种是将sessionid通过cookie存储到浏览器端,后续的请求自动带着这个sessionid的cookie;另一种方案是url回写,将sessionid作为url请求参数传给后端。实际应用中通常是2者结合起来使用,如tomcat是一开始同时使用Cookie和URL回写机制,如果发现客户端支持Cookie,就继续使用Cookie,停止使用URL回写。如果发现Cookie被禁用,就一直使用URL回写。
3.1 session超时
由于服务端需要在内存中记录下所有前端连接的session,为了防止占用内存无限增长,需要设立session的超时机制,将不再活跃的session删除。我们在谈论session失效时,通常有种说法:浏览器关闭,session就失效了。其实分析一下,这种说法是不成立的。因为session是存储在服务器端的,除非客前端主动发一个通知到后端,告诉后端删除session,否则后端是不知道什么时候该删除这个session的。而浏览器关闭时,是没有机会通知后端的。之所以有这种错觉,是因为大多数情况下sessionid是通过会话cookie存储在浏览器端的,浏览器关闭后会话cookie清空,导致浏览器端无法将sessionid传递到后端,后端自然就匹配不上这个session了,但内存中仍然会缓存这个实际上已经失效的session对象。为了解决这个问题,需要设置session超时机制,当检测到该session一段时间内没有请求进来时,从内存中将其删除。
3.2 tomcat session设置
3.2.1 超时的3种方法
1、在tomcat——>conf——>servler.xml文件中定义:
<Context path="/test" docBase="/test"
defaultSessionTimeOut="3600" isWARExpanded="true"
isWARValidated="false" isInvokerEnabled="true"
isWorkDirPersistent="false"/>
2、在web.xml中定义:这个针对具体项目:
<session-config>
<session-timeout>20</session-timeout>
</session-config>
3、在程序中定义:这个就针对具体页面了:
session.setMaxInactiveInterval(30*60);
3.2.2 配置tomcat的session持久化链接
<Manager
className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true"
maxActiveSession="-1"
minIdleSwap="0"
maxIdleSwap="30"
maxIdleBackup="0"
>
<Store
className="org.apache.catalina.session.FileStore"
checkInterval=”60”
directory="../session"/>
</Manager>
或
<Store
calssName="org.apache.catalina.JDBCStore"
driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost/tomsessionDB?user=root&password="
sessionTable="tomcat_session"
sessionIdCol="session_id"
sessionDataCol="session_data"
sessionValidCol="session_valid"
sessionMaxInactiveCol="max_inactive"
sessionLastAccessedCol="last_access"
sessionAppCol="app_name"
checkInterval="60"
debug="99" />
saveOnRestart:(true/false)配置服务重启工程中对session的处理,若为true,则关闭前把有效的session保存,启动后重新载入
maxActiveSession:活动状态Session的最大数,为-1时则不限制,否则Session Manager将会把超出的Session对象转移到Session Store中。
minIdleSwap:Session不活动的最短时间,超过该时间,Session Manager 可能会把该Session对象转移到Session Store中,单位为秒。
maxidleSwap:Session不活动的最长时间,超过该时间,Session Manager 将会把该Session对象转移到Session Store中,该Session将不在内存中。
maxidleBackup: Session不活动的最长时间,超过该时间,Session Manager 将会把该Session对象备份到Session Store中,但该Session对象依然存在内存中。
4 HttpSession
java中通过javax.servlet.http.HttpServlet接口实现session功能。httpServlet只是定义了一套接口,由具体的web容器负责实现。
该接口提供如下功能:
查询和修改一个session对象,如sessionid、创建时间、最后更新时间等、
绑定自定义对象到session上,允许用户数据在多次请求中保持不变
对绑定在session上的对象,可以通过继承2个接口实现部分监听功能:
HttpSessionBindingListener 监听对象的绑定和解绑
HttpSessionActivationListener 监听session对象的钝化(内存 —> 硬盘)和活化(硬盘 —> 内存)
5 session共享
目前集群应用中,需要在不同的web服务间共享session数据,一般是将session存储的redis等缓存中,从而实现session共享。
另外,tomcat也提供一个简单的集群方案,可以实现几个tomcat之间的session共享,tomcat官方建议不要超过5个。
6 参考
6.1 http无状态协议
个人觉得,无状态主要考虑2方面原因。一方面是为了减少服务端的压力;另一方面,也是最主要的,在互联网诞生初期,所有的页面都是静态的,不存在复杂用户交互的场景,因此将http协议设置为无状态的,满足当时的应用场景,同时也是最高效的。