一般的Web开发有时候很有趣,但是通常却是令人烦恼的。JavaWeb开发人员要花费很长时间来提供无状态模型,但是产生的性能和部署的简单性使得这种努力是值得的。在本文中,我将讨论一种完全不同的Web开发方式,称为延续服务器(continuationserver)。延续服务器提供了一个有状态的编程模型,同时又没有舍弃无状态所固有的可伸缩性,从而使Web应用程序开发更加容易。关于本系列
应用程序无法移植。大多数客户机-服务器开发环境要求使用专门的硬件和软件环境。
应用程序难以部署。必须单独地管理数千个客户机。
最大的开销是隐藏的。部署成了最重要的约束,因为进入生产阶段之后的开销大大增加了。
客户机-服务器计算仍然向前发展。公司常常是根据比较低的软件和硬件开销来做出财务决策,但是在进入生产阶段之后管理开销会大大增加。到了1995年,客户机-服务器模型需要进行重大的改进,而且这种改进确实出现了。
进入Web开发
Web开发在20世纪90年代中期迅速发展起来。由于Java语言的出现,开发人员可以用新功能来构建分布式Web应用程序,同时解决了最严重的客户机-服务器问题。这些新功能包括:
受约束的通信。请求/响应Web模型具有基于终端的开发的所有特征。用户在表单中进行输入、发出请求并获得响应。客户机和服务器之间的频繁通信受到了控制,性能得到了提高。
不共享任何东西的体系结构。基于servlet的编程可以是无状态的。这意味着一个servlet可以为任何客户机服务,固定的servlet池可以为许多用户服务。不需要为每个用户保留一个servlet。性能也因此得到了改进。
客户机上的共同标准。通过在所有客户机上部署一个共同的浏览器,就可以构建一个界面并在所有客户机上产生一致的可视效果。支持多种浏览器客户机虽还有一定问题,但是不像支持本地用户界面库那样困难。许多可移植性问题消失了。
更好的部署模型。通过将浏览器作为共同的客户机,软件分发大大简化了。公司可以将应用程序部署在少数几个互联网服务器上并在整个企业中共享它们。网络体系结构常常可以在多台服务器之间共享请求,所以要增加处理容量,只需增加服务器。客户端部署也很容易,只需确保客户机上有正确的浏览器。管理因此大大简化了。
性能、可伸缩性、可管理性和可移植性都大大提高了,互联网革命因此进入了快车道。但是,您必须面对一些重要的问题。
不是乌托邦
无状态(stateless)这个简单的单词将沉重的负担从系统转移到了开发人员身上。其后果是不容质疑的:尽管由于不必为每个用户维护一个服务(或servlet),而获得了很好的可伸缩性;但是,对状态进行管理的责任从编程语言转移到了开发人员身上。目前,可以将Web开发看成一系列无状态的请求,见图1:
图1.Web应用程序由请求/响应对组成
采用这种模型,浏览器得到了严格控制。应用程序只对浏览器发出的请求进行响应。如果请求是小型的独立请求,那么这个模型是有效的;但不幸的是,对于驱动有多个应用组成部分的Web应用程序,它是不合适的。最常见的例子是在线购物。点击Submit按钮两次,常常会意外地重复订购同一商品。以后当您发现购物车中有重复的商品时会大感意外。
向用户提供有状态体验的方法通常是:将与一次交谈相关的所有数据放进一个会话中,并用cookie、隐藏字段或URL变量在客户机上标识用户会话。对于每个新的请求,必须依次执行以下步骤:
从客户机获得用户的标识符
从会话中恢复交谈状态
处理用户的请求
构建响应
将交谈状态存储在会话中
将响应发送给用户
我对这个问题说得太轻描淡写了,因为使用无状态模型来模拟有状态应用程序可能造成更严重的问题。最严重的问题从Web开发刚出现时就存在了,就是如何处理Back按钮。
老问题的新答案
在Web开发中,可以利用有状态模型为用户提供无状态体验。您听到这种说法可能会感到震惊。实际上,在HackersandPainters(参见参考资料)中,PaulGraham就讨论了早在1995年在ViaWeb中使用的底层方法。这种方法使用一种称为延续(continuation)的编程控制结构。
基本思想是:可以让编程框架在请求之前装载应用程序的状态,并在每个请求之后保存应用程序的状态。我首先介绍一下Ruby编程语言中的延续。
一个Ruby示例
如果希望执行代码,请安装Ruby并输入irb。通过在>字符后面输入命令,定义一个称为loop的方法,见清单1:
清单1.创建loop方法
irb(main):001:0>defloop(interrupt)
irb(main):002:1>foriin1..10
irb(main):003:2>puts"Valueofi:#{i}"
irb(main):004:2>callcc{|c|returnc}ifi==interrupt
irb(main):005:2>end
irb(main):006:1>end
=>nil
loop方法接受一个称为interrupt的参数。它启动一个从1到i的for循环,打印i的值,然后做一些奇怪的事儿。神秘的callcc语句意味着用延续进行调用。可以把延续看成在某一时间点上“冻结的”程序状态。Ruby调用花括号中的代码块,同时传递一个延续对象。花括号中的代码是一个闭包,它仅仅是传递给callcc的代码块。最终结果是,callcc捕获执行的状态并将结果存储在c中。现在,可以调用这个方法并在循环的任意位置中断执行,这会捕获程序的状态。在以后,可以恢复状态。
现在,执行这个方法两次,见清单2:
清单2.执行loop方法
irb(main):007:0>cont=loop5
Valueofi:1
Valueofi:2
Valueofi:3
Valueofi:4
Valueofi:5
=>#<Continuation:0x2b5a358>
irb(main):008:0>cont.call
Valueofi:6
Valueofi:7
Valueofi:8
Valueofi:9
Valueofi:10
=>110
irb(main):009:0>cont=loop8
Valueofi:1
Valueofi:2
Valueofi:3
Valueofi:4
Valueofi:5
Valueofi:6
Valueofi:7
Valueofi:8
=>#<Continuation:0x2b562f0>
irb(main):010:0>cont.call
Valueofi:9
Valueofi:10
每次执行调用时,延续会获得执行的状态。所以,使用延续的Web开发框架可以在处理每个请求之后捕获一个延续,并用一个标识符将它存储在会话中。然后,框架可以在处理每个新请求之前从会话中恢复延续,采用的方法与存储用户数据一样。
优点和缺点
延续服务器方式在许多方面都很出色——有状态的编程模型和具有无状态性能的用户体验。这种方式的优点如下:
它确保了请求之间的无状态。框架可以识别一个URL中的各个延续并将延续存储在会话中。
它提供了一个有状态编程模型。框架可以在任何时候恢复任何延续。如果用户第二次提交一个表单,延续可以恢复以前某个时间点上的状态。
可以根据业务规则让延续失效,这样就很容易防止两次提交表单。
解决了Back按钮的问题。因为可以直接获得任意时间点上的执行状态,如果用户点击了Back按钮,框架只需恢复适当的延续。
线程化变得容易了,因为不共享任何资源。没有资源冲突就意味着大多数线程化问题消失了。
延续大大简化了Web开发模型。有了延续,就可以将Web应用程序看成具有一系列请求的应用程序,能够提供更多的用户信息。图2给出了修改后的应用程序流。在用户启动应用程序之后,Web服务器处于控制之中。应用程序不再是应付以任意次序到达的任意请求,而是变成了与一个用户进行统一且直接的交谈。
图2.延续支持更自然的应用程序流
与许多高阶抽象相似,延续也有缺点。这些缺点将影响依赖于延续的整体方式。延续服务器必须解决下面这些常见问题:
延续是昂贵的。将数据放进会话中更容易了,这意味着会有更多的人将更多的数据放进会话中。框架设计者可以利用一种称为部分延续(partialcontinuations)的方法来降低开销,按照这种方法,框架只保存延续中应用程序特有的部分。
URL变得难看了。大多数延续服务器会在URL后面附加一个难看的延续标识符。
用户范例改变了。如果大量使用延续服务器,用户体验就会改变。Back按钮实际上会工作,这种体验会让某些用户感到迷惑。例如,如果在购物车中放了某些东西之后点击Back按钮,您会期望应用程序将这些东西从购物车中去掉吗?
总的来说,我相信延续代表着一种重大的技术进步,在不久的将来您可能会看到流行的延续服务器实现。现在我们来看看其他语言中的某些实现,然后我展示一些具体的细节。
其他语言中的实现
有好几种语言都具有基于延续的方法(参见参考资料中的链接以了解关于它们的更多信息)。最常见的实现是用Lisp、Ruby和Smalltalk编写的。
Seaside是最流行的延续服务器,由AviBryant用Smalltalk实现。如果您希望掌握一个完全不同的极具生产效率的Web开发框架,那么就看看Seaside吧。这种体验会改变您进行Java编程的方式。
用Ruby编写的Iowa也是由AviBryant创建的,灵感来自WebObjects。它现在在Ruby中使用和维护,具有一种不同的支持模式。
Wee框架是另一种Ruby框架,它具有延续服务器的许多特征,但是没有使用本机语言延续。
Wee:Wee是另一种Ruby延续框架。
Continuity:Continuity是一种用于Perl的基于延续的Web编程框架。
讨论
developerWorksblogs:加入developerWorks社区。