无状态的HTTP协议


探究凭证前,我们需要了解HTTP一个特点:无状态。


HTTP无状态指: HTTP协议对事务处理是没有记忆能力的, 也就是说服务器不知道客户端是什么状态。当我们向服务器发送请求后,服务器解析此请求,然后返回对应的相应,服务器负责完成整个过程。 这个过程是完全独立的,服务器不会记录前后状态的变化,也就是缺少状态记录。 也就是说如果后续处理需要前面的信息,就必须重传,这导致需要额外传递一些前面的重复请求,才能获取后续响应,然而这种效果显然太浪费资源了。

于是两种用于保持HTTP连接状态的技术就出现了,他们就是​​Session​​​和​​Cookies​​。Session在服务端,也就是网站的服务器,用来保存用户的会话信息Cookies在客户端(可以理解为浏览器端),有了Cookies,浏览器在下次访问网站时会自动附带上它发送给服务器,服务器通过识别Cookies鉴别出是哪个用户,判断是否是登录状态,然后返回对应的响应

Cookies保存了登录的凭证,有了它,只需要在下次请求中携带Cookies发送请求 就不用重新输入用户名,密码等信息重新登录了。所以在爬虫中,一般会将登录成功后获取的Cookies放在请求头中直接请求,而不必重新模拟登录。

为什么有session?


首先大家知道,http协议是无状态的,即你连续访问某个网页100次和访问1次对服务器来说是没有区别对待的,因为它记不住你。


那么,在一些场合,确实需要服务器记住当前用户怎么办?比如用户登录邮箱后,接下来要收邮件、写邮件,总不能每次操作都让用户输入用户名和密码吧,为了解决这个问题,session的方案就被提了出来,事实上它并不是什么新技术,而且也不能脱离http协议以及任何现有的web技术。

原理很简单,假设你访问网页时就像逛澡堂,第一次进去你是没有钥匙的,这个时候你交了钱服务台就分配一把钥匙给你,你走到哪里都要带上,因为这是你身份的唯一标识,接下来你用这把钥匙可以去打开一个专有的储物柜存储你的衣物,游完泳,你再用钥匙去打开柜子拿出衣物,最后离开游泳池时,把钥匙归还,你的这次游泳的过程就是一次session,或者叫做会话,在这个例子中,钥匙就是​​session​​​的​​key​​,而储物柜可以理解为存储用户会话信息的介质。

那么在web server中如何实现session呢?


想必看了上面的例子你会很容易理解,主要是解决两个问题,一个是钥匙的问题,一个是存储用户信息的问题。对于第一个问题,即什么东西可以让你每次请求都会自动带到服务器呢?如果你比较了解http协议,那么答案一目了然,就是cookie,如果你想为用户建立一次会话,可以在用户授权成功时给他一个cookie,叫做会话id,它当然是唯一的,比如PHP就会为建立会话的用户默认set一个名为phpsessid,值看起来为一个随机字符串的cookie,如果下次发现用户带了这个​​cookie​​​,服务器就知道,哎呀,刚刚这位顾客来了。   剩下的是解决第二个问题,即如何存储用户的信息,服务器知道会话id为abc的用户来了,那abc想存储自己的私人信息,比如购物车信息,如何处理?这个时候可以用内存、也可以用文件,也可以用数据库了,但有个要求是,数据需要用用户的会话id即可取到,比如php就默认会把会话id为abc的用户会话数据存储到​​​/tmp/phpsess_abc​​的文件里面,每次读取都要反序列化程序可以理解的数据,写的时候又需要序列化为持久的数据格式。


如何实现session的共享?

首先我们应该明白,为什么要实现共享,如果你的网站是存放在一个机器上,那么是不存在这个问题的,因为会话数据就在这台机器,但是如果你使用了负载均衡把请求分发到不同的机器呢?这个时候会话id在客户端是没有问题的,但是如果用户的两次请求到了两台不同的机器,而它的​​session​​​数据可能存在其中一台机器,这个时候就会出现取不到​​session​​​数据的情况,于是​​session​​的共享就成了一个问题。

1. 基于NFS的Session共享

NFS是Net FileSystem的简称,最早由Sun公司为解决Unix网络主机间的目录共享而研发。

这个方案实现最为简单,无需做过多的二次开发,仅需将共享目录服务器mount到各频道服务器的本地session目录即可,缺点是NFS依托于复杂的安全机制和文件系统,因此并发效率不高,尤其对于session这类高并发读写的小文件,会由于共享目录服务器的io-wait过高,最终拖累前端WEB应用程序的执行效率。

2. 基于数据库的Session共享

首选当然是大名鼎鼎的MySQL数据库,并且建议使用内存表Heap,提高session操作的读写效率。这个方案的实用性比较强,相信大家普遍在使用,它的缺点在于session的并发读写能力取决于Mysql数据库的性能,同时需要自己实现session淘汰逻辑,以便定时从数据表中更新、删除 session记录,当并发过高时容易出现表锁,虽然我们可以选择行级锁的表引擎,但不得不否认使用数据库存储Session还是有些杀鸡用牛刀的架势。

3. 基于Cookie的Session共享

这个方案我们可能比较陌生,但它在大型网站中还是比较普遍被使用。原理是将全站用户的Session信息加密、序列化后以Cookie的方式,统一种植在根域名下(如:.host.com),利用浏览器访问该根域名下的所有二级域名站点时,会传递与之域名对应的所有Cookie内容的特性,从而实现用户的Cookie化Session 在多服务间的共享访问。

这个方案的优点无需额外的服务器资源;缺点是由于受http协议头信心长度的限制,仅能够存储小部分的用户信息,同时Cookie化的 Session内容需要进行安全加解密(如:采用DES、RSA等进行明文加解密;再由MD5、SHA-1等算法进行防伪认证),另外它也会占用一定的带宽资源,因为浏览器会在请求当前域名下任何资源时将本地Cookie附加在http头中传递到服务器。

4. 基于Memcache的Session共享

Memcache由于是一款基于Libevent多路异步I/O技术的内存共享系统,简单的Key + Value数据存储模式使得代码逻辑小巧高效,因此在并发处理能力上占据了绝对优势,目前本人所经历的项目达到2000/秒 平均查询,并且服务器CPU消耗依然不到10%。

另外值得一提的是Memcache的内存hash表所特有的Expires数据过期淘汰机制,正好和Session的过期机制不谋而合,降低了过期Session数据删除的代码复杂度,对比“基于数据库的存储方案”,仅这块逻辑就给数据表产生巨大的查询压力。

SESSION 的数据保存在哪里呢?

当然是在服务器端,但不是保存在内存中,而是保存在文件或数据库中。(下面以PHP为例)

PHP中的session存储

默认情况下,​​php.ini​​​ 中设置的 ​​SESSION​​​ 保存方式是 ​​files(session.save_handler = files)​​​,即使用读写文件的方式保存 SESSION 数据,而 SESSION 文件保存的目录由 ​​session.save_path​​​ 指定,文件名以 ​​sess_​​​ 为前缀,后跟 ​​SESSION ID​​​,如:​​sess_a69278cf61b8e35d0fe35cdb3f79c71b​​。文件中的数据即是序列化之后的 SESSION 数据了。

如果访问量大,可能产生的 SESSION 文件会比较多,这时可以设置分级目录进行 SESSION 文件的保存,效率会提高很多,设置方法为:​​session.save_path="N;/save_path"​​,N 为分级的级数,save_path 为开始目录。

当写入 SESSION 数据的时候,​​PHP​​​ 会获取到客户端的 ​​SESSION_ID​​​,然后根据这个 ​​SESSION ID​​​ 到指定的 ​​SESSION​​​ 文件保存目录中找到相应的 ​​SESSION​​​ 文件,不存在则创建之,最后将数据序列化之后写入文件。读取 ​​SESSION​​​ 数据是也是类似的操作流程,对读出来的数据需要进行解序列化,生成相应的 ​​SESSION​​ 变量

那么Session在何时创建呢?

当然还是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同创建Session的方法,而在Java中是通过调用​​HttpServletRequest​​​的​​getSession​​方法(使用true作为参数)创建的。在创建了Session的同时,服务器会为该Session生成唯一的Session id,而这个Session id在随后的请求中会被用来重新获得已经创建的Session;在Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发到客户端的只有Session id;当客户端再次发送请求的时候,会将这个Session id带上,服务器接受到请求之后就会依据Session id找到相应的Session,从而再次使用之。

创建: sessionid第一次产生是在直到某server端程序调用 ​​HttpServletRequest.getSession(true)​​这样的语句时才被创建。

删除: 程序调用​​HttpSession.invalidate()​​;程序关闭。   session存放在哪里:服务器端的内存中。不过session可以通过特殊的方式做持久化管理(memcache,redis)。

session的id是从哪里来的,sessionID是如何使用的:当客户端第一次请求session对象时候,服务器会为客户端创建一个session,并将通过特殊算法算出一个session的ID,用来标识该session对象。

常见误区:
会话Cookie和持久Cookie

传说中 会话Cookie是把Cookie放在浏览器内存中,浏览器关闭后该Cookie失效, 持久Cookie则会保存到客户端的硬盘中,下次还可以继续时候,长久保持用户登录状态。

传说是假的,过期时间是由Cookie的Max Age或Expires决定的。 持久化Cookie是把有效时间设置得比较长,这样下次访问时 仍然携带之前的cookie,就可以直接保持登录状态。

Session误区

在谈论Session机制时,会有这样一种误解:只要关闭浏览器,Session就消失了。 这种理解是错误的。 对于Session来说,除非程序通知服务器删除Session,否则服务器会一直保留。

但是在我们关闭浏览器时,浏览器不会主动在关闭之前通知服务器它将要关闭,所以服务器不会有机会知道浏览器已经关闭。之所以有这种错觉,是因为大部分​​Session​​​机制使用会话​​Cookies​​​来保存​​SessionID​​​信息,而关闭浏览器后,​​Cookies​​​消失了,再次连接服务器时,就无法找到原来的​​Session​​​了。如果服务器设置的​​Cookies​​​保存到硬盘上,或者用某种手段改写浏览器发出HTTP请求头,把原来的​​Cookies​​​发送给服务器,则当再次打开浏览器时,仍然能够找到原来的​​SessionID​​​,仍然可以保存登录状态。 由于关闭浏览器不会使​​Session​​​被删除,这就需要服务器为​​Session​​​设置一个失效时间,当距离客户端上一次使用​​Session​​​的时间超过这个失效时间时,服务器就认为客户端停止了活动,会把​​Session​​删除以节省存储空间。