JavaWeb--session

背景及产生

我们用浏览器打开一个网页,用到的是HTTP协议,它是无状态的,什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。但是这种无状态的的好处是快速。

所以就会带来一个问题就是,我希望几个请求的页面要有关联,比如:我在www.a.com/login.jsp里面登陆了,我在www.a.com/index.jsp 也希望是登陆状态,但是,这是2个不同的页面,也就是2个不同的HTTP请求,这2个HTTP请求是无状态的,也就是无关联的,所以无法单纯的在index.jsp中读取到它在login.jsp中已经登陆了!

那咋搞呢?我不可能这2个页面我都去登陆一遍吧。或者用笨方法这2个页面都去查询数据库,如果有登陆状态,就判断是登陆的了。这种查询数据库的方案虽然可行,但是每次都要去查询数据库不是个事,会造成数据库的压力。

cookie

cookie技术是客户端的解决方案(当然随着html5的出现,比cookie更为强劲和安全的技术出现了,但是鉴于html5的普及度不够,就不做本文讨论的内容了),Cookie就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。让我们说得更具体一些:当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体(Response Body)中的,而是存放于HTTP响应头(Response Header);当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置,对于Windows操作系统而言,我们可以从: [系统盘]:\Documents and Settings[用户名]\Cookies目录中找到存储的Cookie;自此,客户端再向服务器发送请求的时候,都会把相应的Cookie再次发回至服务器。而这次,Cookie信息则存放在HTTP请求头(Request Header)了。有了Cookie这样的技术实现,服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的Cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。通常,我们可以从很多网站的登录界面中看到“请记住我”这样的选项,如果你勾选了它之后再登录,那么在下一次访问该网站的时候就不需要进行重复而繁琐的登录动作了,而这个功能就是通过Cookie实现的。

cookie是把少量的信息存储在用户自己的电脑上,它在一个域名下是一个全局的,只要设置它的存储路径在域名www.a.com下 ,那么当用户用浏览器访问时,jsp就可以从这个域名的任意页面读取cookie中的信息。所以就很好的解决了我在www.a.com/login.php页面登陆了,我也可以在www.a.com/index.jsp获取到这个登陆信息了。同时又不用反复去查询数据库。

虽然这种方案很不错,也很快速方便,但是由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了。

session

session技术则是服务端的解决方案,它是通过服务器来保持状态的。由于Session这个词汇包含的语义很多,因此需要在这里明确一下 Session的含义。首先,我们通常都会把Session翻译成会话,因此我们可以把客户端浏览器与服务器之间一系列交互的动作称为一个 Session。从这个语义出发,我们会提到Session持续的时间,会提到在Session过程中进行了什么操作等等;其次,Session指的是服务器端为客户端所开辟的存储空间,在其中保存的信息就是用于保持状态。从这个语义出发,我们则会提到往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,从而再次使用之。正式这样一个过程,用户的状态也就得以保持了。

session是解决http协议无状态问题的服务端解决方案,它能让客户端和服务端一系列交互动作变成一个完整的事务,能使网站变成一个真正意义上的软件。

cookie与session的关系

cookie和session的方案虽然分别属于客户端和服务端,但是服务端的session的实现对客户端的cookie有依赖关系的,上面我讲到服务端执行session机制时候会生成session的id值,这个id值会发送给客户端,客户端每次请求都会把这个id值放到http请求的头部发送给服务端,而这个id值在客户端会保存下来,保存的容器就是cookie,因此当我们完全禁掉浏览器的cookie的时候,服务端的session也会不能正常使用

Token

如果访问服务器多了, 就得由成千上万,甚至几十万个。
这对服务器说是一个巨大的开销 , 严重的限制了服务器扩展能力, 比如说我用两个机器组成了一个集群, 小F通过机器A登录了系统, 那session id会保存在机器A上, 假设小F的下一次请求被转发到机器B怎么办? 机器B可没有小F的 session id啊。
那只好做session 的复制了, 把session id 在两个机器之间搬来搬去, 快累死了。但是太繁琐了。

Memcached: 把session id 集中存储到一个地方, 所有的机器都来访问这个地方的数据, 这样一来,就不用复制了, 但是增加了单点失败的可能性, 要是那个负责session 的机器挂了,把这个单点的机器也搞出集群,增加可靠性.

于是有人就一直在思考, 我为什么要保存这可恶的session呢, 只让每个客户端去保存该多好?
可是如果不保存这些session id , 怎么验证客户端发给我的session id 的确是我生成的呢? 如果不去验证,我们都不知道他们是不是合法登录的用户, 那些不怀好意的家伙们就可以伪造session id , 为所欲为了。关键点就是验证 !
比如说, 小F已经登录了系统, 我给他发一个令牌(token), 里边包含了小F的 user id, 下一次小F 再次通过Http 请求访问我的时候, 把这个token 通过Http header 带过来不就可以了。
不过这和session id没有本质区别啊, 任何人都可以可以伪造, 所以我得想点儿办法, 让别人伪造不了。
那就对数据做一个签名吧, 比如说我用HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为token , 由于密钥别人不知道, 就无法伪造token了。
这个token 我不保存, 当小F把这个token 给我发过来的时候,我再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。
Token 中的数据是明文保存的(虽然我会用Base64做下编码, 但那不是加密), 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息。
这样一来, 就不保存session id 了, 只是生成token , 然后验证token , 用CPU计算时间获取了session 存储空间 !
解除了session id这个负担, 可以说是无事一身轻, 机器集群现在可以轻松地做水平扩展, 用户访问量增大, 直接加机器就行。

表单重复提交

什么是表单重复提交?

同一个表单中的数据内容多次提交到服务器。 危害:

服务器重复处理信息,负担加重。

如果是保存数据可能导致保存多份相同数据。

几种重复提交

1)提交完表单后,直接刷新页面,会再次提交。

  • 根本原因:Servlet 处理完请求以后,直接转发到目标页面。

  • 这样整一个业务,只发送了一次请求,那么当你在浏览器中点击刷新按钮或者狂按 f5,会一直都会刷新之前的请求

解决方案:使用重定向跳转到目标页面

2)提交表单后,由于网速差等原因,服务器还未返回结果,连续点击提交按钮,会重 复提交。

  • 根本原因:按钮可以多次点击

  • 解决方案:通过 js,使得按钮只能提交一次。
    $(“#form1”).submit(function(){ $(“#sub_btn”).prop(“disabled”,true); })

3)表单提交后,点击浏览器回退按钮,不刷新页面,点击提交按钮再次提交表单

  • 根本原因:服务器并不能识别请求是否重复。

  • 解决方案:使用 token 机制。

1、页面生成时,产生一个唯一的 token 值。将此值放入 session

2、表单提交时,带上这个 token 值。

3、服务端验证 token 值存在,则提交表单,然后移除此值。验证 token 不存在,说明是之前验证过一次被移除了,所以是重复请求。不予处理

模拟购物车

https://blog.csdn.net/Dzy_water/article/details/79749246 详见博客

相关问题:

由上面所描述的session实现机制,我们会发现,为了弥补http协议的无状态的特点,服务端会占用一定的内存和cpu用来存储和处理session计算的开销,这也就是tomcat这个的web容器的并发连接那么低(tomcat官方文档里默认的连接数是200)原因之一。因此很多java语言编写的网站,在生产环境里web容器之前会加一个静态资源服务器,例如:apache服务器或nginx服务器,静态资源服务器没有解决http无状态问题的功能,因此部署静态资源的服务器也就不会让出内存或cpu计算资源专门去处理像session这样的功能,这些内存和cpu资源可以更有效的处理每个http请求,因此静态资源服务器的并发连接数更高,所以我们可以让那些没有状态保持要求的请求直接在静态服务器里处理,而要进行状态保持的请求则在java的web容器里进行处理,这样能更好的提升网站的效率。

当下的互联网网站为了提高网站安全性和并发量,服务端的部署的服务器的数量往往是大于或等于两台,多台服务器对外提供的服务是等价的,但是不同的服务器上面肯定会有不同的web容器,由上面的讲述我们知道session的实现机制都是web容器里内部机制,这就导致一个web容器里所生成的session的id值是不同的,因此当一个请求到了A服务器,浏览器得到响应后,客户端存下的是A服务器上所生成的session的id,当在另一个请求分发到了B服务器,B服务器上的web容器是不能识别这个session的id值,更不会有这个sessionID所对应记录下来的信息,这个时候就需要两个不同web容器之间进行session的同步。Tomcat容器有一个官方的解决方案就是使用apache+tomcat+mod_jk方案,当一个web容器里session的信息发生变化后,该web容器会向另一个web容器进行广播,另一个web收到广播后将session信息同步到自己的容器里,这个过程是十分消耗系统资源,当访问量增加会严重影响到网站的效率和稳定性。

我现在所做的网站里有一个解决方案,当用户请求网站的时候会先将请求发送给硬件的负载均衡设备,该设备可以截获客户端发送过来的session的id值,然后我们根据这个id值找到产生这个session的服务器,将请求直接发送给这台服务器。这种解决方案看起来解决了session共享问题,其实结果是将集群系统最终变回了单点系统,如果处理请求的web容器挂掉了,那么用户的相关会话操作也就废掉了。此外,这种做法也干扰了负载均衡服务器的负载均衡的计算,让请求的分发并不是公平的。

一般大型互联公司的网站都是有一个个独立的频道所组成的,例如我们常用的百度,会有百度搜索,百度音乐,百度百科等等,我相信他们不会把这些不同频道都给一个开发团队完成,应该每个频道都是一个独立开发团队,因为每个频道的应用的都是独立的web应用,那么就存在一个跨站点的session同步的问题,跨站点的登录可以使用单点登录的(SSO)的解决方案,但是不管什么解决方案,跨站点的session共享任然是逃避不了的问题。

解决方案

由上所述,session一共有两个问题需要解决:

  1. session的存储应该独立于web容器,也要独立于部署web容器的服务器;
    2)如何进行高效的session同步。

在讲到解决这些问题之前,我们首先要考虑下session如何存储才是高效,是存在内存、文件还是数据库了?文件和数据库的存储方式都是将session的数据固化到硬盘上,操作硬盘的方式就是IO,IO操作的效率是远远低于操作内存的数据,因此文件和数据库存储方式是不可取的,所以将session数据存储到内存是最佳的选择。因此最好的解决方案就是使用分布式缓存技术,例如:memcached和redis,将session信息的存储独立出来也是解决session同步问题的方法。
Tomcat的session同步也有使用memcache的解决方案,大家可以参加下面的文章:

http://blog.sina.com.cn/s/blog_5376c71901017bqx.html
但是该方案只是解决了同步问题,session机制任然和web容器紧耦合,我们需要一个高效、可扩展的解决方案,那么我们就应该不是简单的把session独立出来存储而是设计一个完全独立的session机制,它既能给每个web应用提供session的功能又可以实现session同步,下面是一篇用zookeeper实现的分布式session方案:
http://www.open-open.com/lib/view/open1378556537303.html

redis存储session

之前说的都是用文件files存储,现在想用redis,好处有哪些?
更快的读取和写入速度。redis是直接操纵内存数据的,肯定是要比文件的形式快很多。
更好的设置好过期时间。文件存储的sess_sdewfrsf文件其实被删除掉还是要考运气的和概率的,很有可能造成sess_文件没即时删除,造成存储磁盘空间过多,和读取SESSION就变慢了。
更好的分布式同步。设置redis 的主从同步,可以快速的同步session到各台web服务器,比文件存储更加快速。

在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上,如果不同的服务器用的是不同的reidis服务,那么可能就会出现,一个登录用户,一会是登录状态,一会又不是登录状态。所以session这个时候就要同步了。刚好,我们选择用redis作为了存储,是可以在多台redis 服务器中同步的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值