《企业应用架构模式》,作为.NET程序员读过此书真的不多,.NET充满了各种各样的糖,他们是来自.NET开发团队的礼物,但在新技术日新月异的今天,你是否真正了解现在的.NET
Framework呢?
面向对象设计模式发展到今天,已经出现了很多超越语言的架构模式,这本Martin Fowler大师于2002年左右所著"Patterns of Enterprise Application",总结了当时软件行业的最新发展成果,而这些成熟的精华在之后的.NET Framework设计中大多得到了充分的运用,深深的影响了.NET Framework的设计。我试图通过对这些经典模式的重温,同时在庞大的.NET Framework中寻找他们的踪迹,看看这么多年来,这些经典的模式,他们发展的怎么样了。当然,这对于我来说也是一个再学习和提高的过程。
今天先试写一章,关于Session State模式。
从根本上说,Session State的引入来自连续的业务逻辑和无状态访问协议的冲突。
这句话有些拗口,举一个简单的例子,在一个在线书店系统中,一个典型的买书的业务逻辑包括这么几个子业务逻辑:
l 选定要买的图书
l 计算总价以及优惠
l 填写送货等相关信息
l 生成订单
l 通过各种途径付款
很容易理解,这些业务逻辑是有先后顺序的,并且后面的操作必须建立在前面的步骤之上。这边形成了所谓“连续的业务逻辑”。
另一方面,我们通常使用的HTTP协议是典型的无状态协议,用户每次提交一个页面的访问请求时,都必须建立全新的连接,这里“全新的连接”是关键,它意味着每一次请求都是完全独立的,因此当提交“生成订单”的请求时,服务器并不知道是哪个用户正在提交请求,也并不知道用户是否已经选择了图书,输入了送货地址等等,因为之前用户访问的信息都没有被保存下来。
如果将这里的用户以及他所处业务逻辑中每个步骤看作一个状态,也就是一些数据,然后换句话说,就是服务器不能保存用户的状态,再换句话说,每次提交后当前数据都会丢失。
由于HTTP协议的这种缺陷,这样冲突就产生了,为了解决这样的问题,我们的解决方案,就是引入Session State模式。
正因为HTTP无法保存状态,所以实现Session State的目的,也就是要想办法把状态保存起来。
按照Session保存的位置,Session State模式被分为三种。
将Session存储在客户端——Client Session State
将Session存储在服务器——Server Session State
将Session存储在数据库——Database Session State
Client Session State基本思想是客户端每次提交HTTP请求时“报告”自己当前的状态,也就是把状态数据存储在客户端。客户端有很多种实现方式,不论哪种方式,最大的优点就是它天生的包含了对于每个用户的标识——每个用户的状态信息不可能被误认为其他用户。但是,由于状态信息存储在客户端,因此状态信息在每次提交HTTP请求时都会被传输到服务端,假如这个状态信息尺寸过大,那么将会带来很恐怖的网络流量。另外,由于状态信息每次由客户端自己提交,因此这个状态信息的安全性就成了很大的问题,我们必须使用各种加密技术防止它被篡改。
Server Session State的基本思想是服务器处理请求之后,在内存里保存下用户的状态信息,下次请求时再读取出来。和Client Session State相反,这样安全性和流量的问题得到了解决,但是如何标识每个用户成了问题——一个WebServer往往要同时处理成千上万的用户。
另外将状态信息存储在服务器上也会有其他的问题,比如性能问题,成千上万的用户状态信息很可能快速吃光服务器的内存,如果是以文件形式存放在硬盘上,那么造成的I/O流量又不可忽略。
分布式服务也是大问题,如果存在多台服务器构成的集群,我们必须考虑用户状态信息如何在多台服务器之间共享。
还有一个关于状态信息失效的问题,对于一个大型的B2C应用而言,很多用户已经离开,但我们根本无法知道什么时候改让他的状态信息失效。如果不及时清除已经失效的状态信息,显然服务器的内存或者硬盘将会经受考验。
Server Session State的一个变形是Database Session State,也就是把状态信息存放在数据库中,这样其实仍然属于Server Session State,不过把一些烦人操作交给了数据库习惯。
上面仅仅总结了一些几种基本的优缺点,下面我们看看在.NET框架中是怎么实现Session State模式的。
如果你使用过ASP.NET,应该不会对Session变量陌生,.NET框架已经做了足够好的封装,我们只需要在后台调用Session这个变量就可以方面的存储一次会话中的用户状态。
例如:
Session[“UserName”] = “Yannic”; (存储)
String currentUser = Session[“UserName”] (读取)
很多书上会告诉你,这个Session会在每个用户第一次访问的时候被建立,并且专属于这个用户,页面刷新后,其中保存的值依然存在。因此我们使用Session存储用户相关的状态信息。没错,这正是Session最正统的用法。
我们从模式的角度来看看Session变量,Session变量正是Server Session State的一种实现。当用户第一次登陆时,ASP.NET自动在内存中为用户准备好存储状态信息的地方,也就是一个Session的实例,每次提交过程中只要有需要,我们就可以向其中写入用户当前的状态信息,这样也就实现有状态的传输。
既然是Server Session State的实现,它自然就会面临一些问题,首先就是用户标识问题。ASP.NET的做法是为每一个Session实例生成一个SessionID,并且将这个ID保存在客户端。通常情况下,这个ID是以Cookies的形式保存在客户端的,当Cookies不可用时,ASP.NET也提供了譬如利用URL参数等方法保存SessionID的功能。技术细节可以查看MSDN,这里就不多说了。
需要额外补充的一点是,Session的所有数据都存放在服务器的内存中,由于Session是每用户独立的,因此在用户访问量较大的页面没有限制的使用Session很容易造成服务器内存资源的不足。
并非一定叫Session的才叫Session模式,只要用来保存用户状态的模式都可以算作Session State模式。
Cookies不是.NET框架提供的功能,而是浏览器和HTTP协议定义的东西,用来在客户端保存用户的状态。在ASP.NET中,Cookies被封装成了Request.Cookies(用于读取客户端Cookies)和Response.Cookies属性(用于向客户端写入Cookies),服务端可以方便的对其进行读写操作,有时候能达到和Session一样的效果,不过一定要记住,Cookies是存放在客户端的。
还有最关键的一点,Cookies是以文本形式存在并且是可以被任意删除修改或者禁用的,因此千万不要在Cookies中保存敏感信息,也绝对不要依赖Cookies的数据,
正因为作为Client Session State的Cookies是如此不可靠,因此ASP.NET框架给我们提供了另外一种Client Session State,也就是ViewState。
Client Session State中的状态信息除了可以存放在Cookies中,另外一种方法就是在页面中定义一个隐藏表单域,用来存储状态信息。ViewState默认实现即如此,只要没有显示关闭ViewState,每个ASP.NET页面都会自动生成一个隐藏的表单域:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="" />
Value属性的即是当前保存的状态信息,由于默认ASP.NET会将信息序加密并且序列化成难以理解的字符串,因此ViewState安全性相对来说是比较高的,但是也不能完全依赖ViewState,你只需要随意以”ViewState查看器”为关键字搜索一下,就可以找到一些能查看ViewState内容的小工具。一定要注意,ViewState也是不可靠的。
那么ViewState有什么用呢?最大的用处大概就是用在ASP.NET的服务器控件状态保存上吧,ViewState和ASP.net的Postback机制紧密相关,一般来说由于ViewState不可靠,他并不被用来保存如最前面所说的业务逻辑状态,而经常被用来保存和界面关系比较大的界面状态信息。
你一定会发现在ASP.NET中并没有提供所谓的Database Session State模式的实现。我想也许.NET的开发团队认为服务端的Session和客户端的ViewState已经足够满足绝大部分应用了,但是在ASP.NET 2.0及之后的版本中,ASP.NET还是提供了可以扩展的ViewState接口,利用它我们可以使用数据库来保存状态信息。
如上所说,默认情况下,ViewState是存储在页面上的隐藏字段中的,但是ASP.NET2.0开放了接口使得我们可以手动控制把ViewState存储到其他地方。
自定义ViewState的存储可以通过重载LoadPageStateFromPersistenceMedium/SavePageStateFromPersistenceMedium这两个方法进行,其实这用的很少,一个经典的应用场景是在开发ASP.NET Mobile Form的时候使用这个功能,这样可以最大限度的减少到客户端的数据流量。
当然,还有另一种办法,就是我们手动的将某些变量存储到数据库,然后在适当的时候读出到服务器Session变量中,反正,.NET框架并没有直接提供在数据库中存储状态信息的功能,如果真的要用,我们只有自己动手,丰衣足食。
还要提一下Martin Fowler的观点,如果没有确切的必要,还是不要使用Database Session State的好,毕竟ASP.NET已经提供了足够强大的Session和ViewState了。
反过来说,只有的确分布式或者提供严格错误回滚的状态信息时,才考虑使用Database Session State。