上一节我们了解了基于XML的SOAP协议。SOAP中的S是什么意思?很简单,但看起来一点都不简单!传输协议问题?对于SOAP,例如,我创建一个订单,使用POST,并在XML中将action写为CreateOrder;删除一个订单,或者使用POST,将动作用XML写成DeleteOrder。其实可以用POST动作创建一个订单,然后在XML里放一个订单信息,用DELETE动作删除,再在XML里放一个订单ID。于是上面的SOAP就变成了下面这个简单的样子。POST/purchaseOrderHTTP/1.1Host:www.cnblog.comContent-Type:application/xml;charset=utf-8Content-Length:nnn2018-07-01栗子焖鸡58和XML格式也可以更改为另一种简单的文本对象表示格式JSON。Web应用程序的编写者应该经常注意到这就是RESTfulAPI的样子。协议协议问题不过,RESTful不仅仅是指API,而是一种架构风格。全称是RepresentationalStateTransfer,来源于一篇重要论文《架构风格与基于网络的软件架构设计》(ArchitecturalStylesandtheDesignofNetwork-basedSoftwareArchitectures)。这篇文章更深入、更抽象地论证了一个互联网应用应该具备的设计点,而这些设计点成为我们后面能看到的所有高并发应用设计必须考虑的问题,再加上RESTAPI就比较简单了直接,几乎成为互联网应用的标准接口。所以,与SOAP不同,REST不是一个硬性标准,它实际上是一种设计风格。如果按照这种风格设计,既可以实现RESTful接口,也可以实现SOAP接口,但REST提倡的是后者的架构,而SOAP更注重前者的接口。由于可以通过WSDL生成客户端的Stub,所以SOAP的使用方式往往类似于传统的RPC,即调用远程端与调用本地端是一样的。但是,本地调用和远程跨网调用毕竟是不一样的。这里的区别不仅仅是因为网络的原因导致客户端和服务端分离,带来网络性能问题。更重要的问题是谁来维护客户端和服务器端的状态。所谓状态就是某个数据当前被处理到什么程度。这里有一些例子。比如浏览到哪个目录,看到哪个页面,想买东西,需要扣除库存。这些都是状态。本地调用其实没有人关心这个问题,因为数据都是本地的,大家处理起来一样,一边处理完,另一边马上就可以看到。有了RPC,我们原本期望对上层是透明的,就像上一节说的“天之遥,望之所及”。所以在使用RPC的时候,并没有过多的考虑状态问题。就像NFS一样,客户端会告诉服务器我要进入哪个目录,服务器必须为某个客户端维护一个状态,也就是客户端当前正在浏览哪个目录。比如client输入cdhello,服务器肯定记得上次浏览到/root/liuchao的某个地方,所以client这次的输入应该给它显示/root/liuchao/hello下的文件列表。而如果有另一个client,同样输入cdhello,服务器还记得上次浏览到/var/lib的某个地方,所以它会显示/var/lib/hello给client。不仅仅是NFS,如果你浏览和翻页,我们往往需要实现函数next()来取一个列表中的下一页,但这需要服务器记住客户端A上次浏览到20-30页,那么调用next()应该显示30-40页,但是客户端B上次浏览的是100-110页,调用next()应该显示110-120页。以上例子都是在RPC场景下,服务器维护状态。很多SOAP接口往往都是按照这种模式设计的。这个模型没有问题,因为客户端和服务器之间的比例没有失衡。因为同时连接的客户端一般不会太多,NFS还可以记住每个客户端的状态。如果公司内部使用的ERP系统是用SOAP实现的,服务器为每个登录用户维护浏览到报表的页面状态,由于公司人员不多,把ERP放在一个在强大的物理机上也能记住。但是在互联网场景下,客户端和服务端是完全不平衡的。你可以想象“双十一”,有多少人同时来购物,作为一个服务器,它能记得来吗?当然不可能,所以要多台服务器同时提供服务,大家共享。但是这里有个问题,服务器如何把自己记住的客户端的状态告诉其他服务器呢?换句话说,如果你让我把作品分享给你,你必须把作品的来龙去脉给我解释清楚!服务器端必须考虑它。既然有这么多客户,我们就分工吧。服务器只记录资源的状态,如文件状态、报表状态、库存状态,而客户端维护自己的状态。比如你访问了哪个目录,报表的哪个页面等等。这也影响了API,也就是说,当客户端维护自己的状态时,不能通过这种方式调用服务端。比如客户端说,我要访问当前目录下的hello路径。服务器说,我怎么知道你现在的路径。所以client首先要检查自己当前的路径是/root/liuchao,然后告诉server我要访问/root/liuchao/hello这个路径。再比如,客户端说我要访问下一个页面,服务器说,我怎么知道你当前访问的是哪个页面。所以客户端首先要检查自己是否访问了100-110页,然后告诉服务器我要访问110-120页。这就是服务器的无状态。这样服务器就可以横向扩展,一百个人一起服务,不用交接,每个人都能搞定。所谓无状态就是服务器维护资源的状态,客户端维护会话的状态。对于服务端,只有当资源状态发生变化时,客户端才会调用POST、PUT、DELETE方法来找我;据说是统一的GET。虽然这只是改进了GET,但它已经是一个很大的改进。因为对于互联网应用来说,大部分都是读多写少。而只要服务器的资源状态不变,就给了我们缓存的可能。比如状态可以缓存到接入层,甚至可以缓存到CDN的边缘节点,这就是资源状态不变的好处。按照这种思路,API的设计逐渐变成了以资源为中心,而不是以流程为中心。也就是说,client只需要告诉server你希望这个资源状态最后变成什么,而不用告诉我这个过程或者action。也是一个文件目录的例子。客户端应该访问哪个绝对路径,而不是一个动作,我要进入某个路径。再比如,库存调用应该检查当前的库存数量,然后减去购买的数量得到最终的库存数量。此时应该设置为目标库存(但必须与当前库存匹配),而不是告诉减去多少库存。这种API的设计需要做到幂等,因为网络不稳定,会经常出错,所以需要重试,但是一旦重试,就会出现幂等的问题,就是同一个调用,多次调用的结果应该是同理,不能一次付费,因为调用三次就变成三次付费了。进不去cda,进了3次就变成了cda/a/a。库存也不能扣除。如果调用三次,则库存将被扣除三次。当然,按照这种设计模式,RESTfulAPI和SOAPAPI都可以将架构实现为无状态、面向资源、幂等、水平可扩展、可缓存。但在SOAP的XML文本中,可以放置任何动作。例如、等都可以用XML来写。这对于使用SOAP的人来说很方便,可以将很多操作放在API中。RESTful并没有那么复杂,也没有为客户提供那么多的可能性。文中的JSON基本描述了资源的状态,没有办法描述动作,唯一可以触发的动作是CRUD,即POST、GET、PUT、DELETE,即对于状态改变。所以,从界面上看,让你去死吧。当然,也有很多取巧的方法。在使用RESTfulAPI的情况下,仍然提供基于操作的有状态请求是一种反模式。服务发现问题对于RESTfulAPI,我们已经解决了传输协议的问题——基于HTTP,协议约定的问题——基于JSON,最后要解决的就是服务发现的问题。有一个著名的基于RESTfulAPI的跨系统调用框架叫做SpringCloud。SpringCloud中有一个名为Eureka的组件。传说阿基米德在洗澡时发现了浮力原理。他高兴得连裤子都穿不上了,跑到街上大喊:“Eureka(我找到了)!”所以用Eureka来实现注册中心,负责维护服务的注册列表。服务子服务提供者,与Eureka进行服务注册、更新和下线操作。注册的主要数据包括服务名称、机器IP、端口号、域名等。‖‖‖另一方是服务消费者,从Eureka获取服务提供者的注册信息。为了实现负载均衡和容错,服务提供者可以注册多个。当消费者要调用服务时,会从注册中心读取多个服务,那么如何调用呢?当然是RESTful方式。SpringCloud提供了一个RestTemplate工具,用于将请求对象转换为JSON并发起Rest调用。RestTemplate的调用也分为POST、PUT、GET和DELETE。返回结果时,根据返回的JSON解析成对象。这样封装起来,调用起来也很方便。总结SOAP过于复杂,设计是面向动作的,所以往往因为架构问题导致并发量上不去;RESTful不仅仅是一种API,更是一种架构模型,主要面向资源,提供无状态服务,有利于横向Scale应对高并发。