REST架构实质

REST(Representational State Transfer) 曾经被误解为只是CRUD(增删改查),从这个层面上,好像REST只是和RPC一个层面的东西,没有什么了不起,其实这些都是对REST误读。

理解REST需要从系统集成整合以及架构的伸缩性方面入手,这方面有一篇很重要的REST博文: I finally get REST. Wow.
http://www.pluralsight.com/community/blogs/tewald/archive/2007/04/26/46984.aspx

作者认为:每个通讯协议都有一个状态机,当你使用RPC时,你要做些方法来改变通讯的状态,但是这些状态是封装在服务器端或客户端的专门通讯模块中,比如通过Hessian/SOAP等Proxy技术进行RPC调用,虽然可以在客户端很方便地象调用本地服务一样,缺点总是伴随优点到来,由于Proxy封装了客户端和服务器的通讯,就很容易让客户端和服务器紧耦合。

这其实是C/S架构的一个本质问题,而B/S架构通过简单的浏览器则解耦了客户端和服务器的耦合,别小看丑丑的浏览器,特别是当初还没有AJAX辅助时,浏览器被很多传统C/S讥笑和不屑,但是他们不知道B/S架构后面蕴含的深刻朴实的设计道理,是真正大道至简。所以,当我们重新回归RIA富客户端代表的C/S架构时,必须吸取Web架构的本质优点,这样才是螺旋式上升。

回归话题,由于传统架构隐藏了通讯模块进而发生了紧耦合,而REST最大作用则是将通讯状态以URI显式表现出来,也就是将通讯状态的透明性,这样做最大的好处就可以在通讯环节引入伸缩性。

REST核心思想就是:以URI形式将状态机表现为动态的节点图,我们就能引入动态负载平衡dynamic load-balancing, 数据重导向data-directed-routing, versioning 等其他正常的Web底层设计架构Web infrastructure,从而使客户端和服务器之间服务器之间享受Web架构的好处。

谈到系统集成,SOAP/Web服务/SOA被厂商吹嘘了很多年,很多应用也在使用SOAP,SOAP和REST无疑是相互竞争的,SOA其实也是一种的RPC,是一种基于XML的RPC,因此,同样存在通讯状态隐藏的致命问题。

上述博客作者写了另外一篇文章来比较(http://www.pluralsight.com/community/blogs/tewald/archive/2007/04/27/47031.aspx)
比如,两个城市之间航班看成一个协议,这个协议有下面几个状态:
<ready>
- searched (查询)
- retrieved details (获得细节)
- reserved (预订)

通过URI表现出来是:
<none>
- http://quuxTravel.com/searched
- ??? depends on previous state
- ??? depends on previous state

客户端通过get方式http://quuxTravel.com/searched?src=London&dest=NYC进行查询,得到结果如下:
<itineraries>
<itinerary src=“London“ dest=“NYC“ price=“400.03“>
<getDetails uri=“http://quuxTravel.com/details?itinerary=402“ />
<reserve uri=“http://reservations.bookingsunlimited.com/quuxTravel?itinerary=402“ />
</itinerary>
<itinerary src=“London“ dest=“NYC“ price=“109.88“>
<getDetails uri=“http://quuxTravel.com/details?itinerary=219“ />
<reserve uri=“http://reservations.bookingsunlimited.com/quuxTravel?itinerary=219“ />
</itinerary>
</itineraries>
客户端目前处于“查询”状态,通过遍历上述itineraries旅游线路结果集合,寻找出最便宜的一家,如果用户想查询一个不在上述结果的信息比如飞行总体时间,他能通过上述结果属性getDetails/@uri中保存的URI信息再次GET获得,这样就会切换到 retrieved details状态。然后又会回到searched状态。

当用户选择一条路线后,通过保存在reserve属性中的URI值就进入 reserved状态,客户端得到一些reserved方面的信息,至此,整个业务算完成了。

如果使用RPC如何来完成呢?我们必须创建一个接口如下:
interface IFlightSystem
{
Itineraries Search(string src, string dest);
Details GetDetails(int itineraryId);
Confirmation Reserve(itineraryId);
}
客户端可以通过调用这个接口的几个方面来完成上述一些业务.

在这里,接口表达了和前面REST同样的协议,所不同的是, RPC客户端依赖服务器端这个接口,也就是两者通过接口耦合了,更致命的是:searched (查询) retrieved details (获得细节) reserved三个状态耦合到同一个服务器中,如果我们想将这三个状态分离分散到多台服务器上,除非重写服务器端代码,否则无能为力。

而在前面REST调用中,每个状态都是通过URI重新定位,这样,我们可以在这三个状态中引入伸缩性,比如searched状态的URI在A服务器,而retrieved details的URI则是B服务器的网址,而reserved的URI则是c服务器,看看我们的业务不再是铁板一块了。

这就是REST本质魅力,REST和RPC/SOAP本质区别是透明性。REST透明性可以让我们以更细粒度引入伸缩性,这样以REST为主要形式可以组建一个分布式的大型架构,而这个目的恰恰是重量解决方案SOA提出的目标,现在我们有了另外一个轻量的选择。

[该贴被admin于2009-07-14 09:51修改过]

看了这篇文章,对REST架构有了更加深入的认识。多谢banq老师。

A terabyte cache with the RESTful Ehcache Server
使用RESTful EhCache服务器实现TB级级别缓存
http://gregluck.com/blog/archives/2008/08/_the_restful_eh.html

使用RESTful接口实现数据分区(也可以称为通常的数据库分离),一个最大的ehcache可以有20GB内存,而最大的磁盘存储是100Gb. 使用数据分区将这些节点组合在一起,可以得到更大的内存,50个节点X 20GB可以得到1TB。

非冗余的一个设计:

带客户端Hash的设计,代码如下:
String[] cacheservers = new String[]{"cacheserver0.company.com",
"cacheserver1.company.com",
"cacheserver2.company.com",
"cacheserver3.company.com",
"cacheserver4.company.com",
"cacheserver5.company.com"};
Object key = "123231";
int hash =Math.abs(key.hashCode());
int cacheserverIndex = hash % cacheservers.length;
String cacheserver = cacheservers[cacheserverIndex]
图示:


有趣的是,一些内容交换的负载均衡器可以使用某种形式的正则表达式可以实现URI路由,如下代码所示,所以,你可以不选择上面客户端Hash散列实现分割的方式来实现负载平衡器。
/ehcache/rest/sampleCache1/a1 =>cluster1
/ehcache/rest/sampleCache1/a2 =>cluster2

当然,更复杂的是使用F5负载平衡器,还可以使用TCL创建iRules,但是比正则表达式要复杂。F5的URI hashing iRule见:
http://devcentral.f5.com/Default.aspx?tabid=63&PageID=153&ArticleID=135&articleType=ArticleView

个人建议:图中Cluster组可以使用Terracotta,可自动整合Ehcache,对程序无侵入性,更新性能好于memcached,比JMS JGroups要高级多。

见配置:http://www.terracotta.org/web/display/docs/About+Terracotta+Configuration+Files
[该贴被banq于2009-07-11 18:14修改过]

我下了Roy博士的文章《软件架构风格和基于网络的软件架构设计》这可能是REST概念的起源了,似懂非懂的看了一遍,确实非常不好理解。我对其中“网络上的操作实质就是请求从一个状态转移到另一个状态”不是很理解。另外大量号称懂得REST的人都被Roy痛骂“你们起个其他的名字都可以,别玷污了我的REST”,其中Roy强调“REST必须是超文本驱动的”这个“超文本驱动”又如何解释?

to banq:

JAVA里好像没有像JDBC和JMS那样缓存的标准API,所以这种产品差异导致开发不能进行下去的风险只能自己写通用接口屏蔽。

按照banq航班的例子,我不是很理解的是:在没有REST的时候,也是这样做的阿,只不过如果是浏览器请求的话,返回的是整个HTML页面,其中某行包含的连接引用了Detail的地址。难道REST只是针对于服务器与服务器之间的远程调用才有意义?而非最终客户端的解决方案?

>网络上的操作实质就是请求从一个状态转移到另一个状态
这个实际可以从上面第一个帖子中旅游线路举例中可以看出。

另外DBC(Design by contract)模式中提出三个约束:前置条件与后置条件和不变性,其实质隐喻是这样:行为条件导致状态变化,状态必须保持不变性。这是大多数软件系统的实质,是大道至简的一句话。

这和Roy博士这句话是一个意思,有状态就要保持一致性,以前我们都是在服务器端来实现,这里DDD中也大量谈了一致性问题,通过根实体和边界来实现,需要锁或事务来具体实现,而REST认为将状态转移到客户端后,服务器端也许就不用这么吃力了。

这是一个新思路,是否成功也是需要拭目以待的,所以,现在很多人在讨论REST的事务问题,这是维持状态一致性要求导致的。

>REST必须是超文本驱动的
这个我非常认同,在我上面的"REST是什么"中已经翻译了Roy比较JavaScript和Java结论,JavaScript或Annotation可以认为是一种超文本。

这里有另外一篇文章:
REST API必须是超文本驱动的
REST APIs must be hypertext-driven
http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

文中批判了GlassFish开发的 SocialSite REST API.实际是RPC,不是REST,现在很多初学者觉得GlassFish很好,因为它又在玩把什么好玩意都放在一个盒子里,诱骗小孩的把戏了,不懂的人看到,里面有很多糖糖果果,好啊,其实拿来后都没有用,用起来必须和Netbeans结合才好用,这样不可分离的技术有屁用?完全是违背现在透明化细分大势。

REST就是把通讯暴露出来,但是又不完全暴露,若隐若现才最美,这就是设计艺术性。

超文本可以和html这样表现技术一起下载,没有用户延迟感,这就是为什么Java applet被Javascript/ajax替代的根本原因,所以,超文本成为REST一个基本约束。

在这篇文章中,ROy提了6个REST框架ROF必须具备的条件,这也是我们架构选型必须注意的,否则选出来的就不是真的RESTful,有时间我会按照这6个意思将Jdon框架拓展到真正的RESTful,现在一些框架以为支持CRUD就成了RESTful,其实都是假的,皮毛而已。

正如您所说“REST站在系统集成的角度上才看得出优势”,倘若一个系统,在开发初期就已经预见到肯定由有限的资源组成,典型地,比如jdon网站,不会出现发帖列表来自jdon网站服务器,而点击帖子则去javaeye网站索取细节的情况,涉及资源非常有限,那么用REST重构是不是有些激进,发挥不了优势?
更一般的说,现在企业级应用一般都是构建在公司局域网,企业级应用有其自身的复杂性“业务逻辑”,但是站在资源角度上,就算最复杂的企业级应用,资源也是有限的,也是数得清的。
而互联网应用就不一样,一个电子商城获取个人信息可能连结到twritter,连接到msn,连接到qq,连接到ebay,无限的互联网资源才可以让REST大显身手。

感觉这种东西应该就是云的概念,从云中选择多个服务,然后在众多相同的服务中找到最合适的。

我纠正一下,超文本应该是html之类具备媒体资源导航功能的语言,不一定是html,超文本包括信息发布和控制,控制这块应该是由类似javascript来完成。

REST系统集成优势,所谓系统定义也是可变化的,REST应该理解为状态集成,一个系统可以是一个状态,但是一个系统内部可以由很多状态组成,因此,从状态角度将任何软件系统进行切分,这是REST的一个独特视角。

目前没有REST情况下,终端用户到请求处理服务器之间已经可以做到超文本驱动了了,就是HTML中包含某个连接,点击连接下一个页面又包含其它的连接。。。。你的意思是不是服务器和服务器之间的调用还没实现超文本驱动?

是的,不但服务器与服务器之间,而且RIA和服务器之间都要从浏览器html这样大道至简的架构中吸取力量,如果将来这些架构之间以及这些系统内部都使用REST,那就是一个真正的互联互通目标了。

如果网络上的通讯都是超文本,那么会导致问题就是

1 粗粒度和数据通讯接口
2 困难的错误调试
3 与面向对象技术的冲突
4 更长的开发和排错周期

数据通讯接口可不像浏览器一样,浏览器接到错误的标签,无非就是某个格没对齐,而如果一切都是文本,那么未来某两个服务器之间文本出些许差错可能导致整个系统不预期的灾难性后果,文本块越大可能隐藏的潜在风险越多,确信带来的资源位置无关性的优势相对于这些劣势是值得的???

其实超文本就是XML,Html也是一种XML,只不过这个XML中必须有与具体状态对应的资源URI。

而SOAP等基于XML的则是服从于组件服务粒度,而不是从客户端状态来考量的,这完全是两个不同方向。

所以,RETS是SOAP/Web服务的升级,如果超文本有什么问题的话,那么SOAP/Web服务体系架构就会有问题。

关键是对REST真正理解,ROY自己也发感概,为什么这么多人误解了REST意思?都误读了呢?

他认为REST只是一个基于几十年的发簪经验,谋求让软件获取更长寿命的一个方法,但是更多人比较擅长短视方法,忽视甚至否定长期方法,这就是误读和矛盾所在。

我本人也崇尚软件是有生命的,所以才能长期活着的宗旨。