上一节我们讲了基于XML的SOAP协议,SOAP的S是啥意思来着?是Simple,但是好像一点儿都不简单啊!
你会发现,对于SOAP来讲,无论XML中调用的是什么函数,多是通过HTTP的POST方法发送的。但是咱们原来学HTTP的时候,我们知道HTTP除了POST,还有PUT、DELETE、GET等方法,这些也可以代表一个个动作,而且基本满足增、删、查、改的需求,比如增是POST,删是DELETE,查是GET,改是PUT。
传输协议问题
对于SOAP来讲,比如我创建一个订单,用POST,在XML里面写明动作是CreateOrder;删除一个订单,还是用POST,在XML里面写明了动作是DeleteOrder。其实创建订单完全可以使用POST动作,然后在XML里面放一个订单的信息就可以了,而删除用DELETE动作,然后在XML里面放一个订单的ID就可以了。
于是上面的那个SOAP就变成下面这个简单的模样。
POST/purchaseOrderHTTP/1Host:www.geektimcomContent-Type:application/xml;charset=utf-8Content-Length:nnn2018-07-01趣谈网络协议刘超68
而且XML的格式也可以改成另外一种简单的文本化的对象表示格式JSON。
POST/purchaseOrderHTTP/1Host:www.geektimcomContent-Type:application/json;charset=utf-8Content-Length:nnn{“order”:{“date”:“2018-07-01”,“className”:'趣谈网络协议',“Author”:'刘超',“price”:“68”}}
经常写Web应用的应该已经发现,这就是RESTful格式的API的样子。
协议约定问题
然而RESTful可不仅仅是指API,而是一种架构风格,全称RepresentationalStateTransfer,表述性状态转移,来自一篇重要的论文《架构风格与基于网络的软件架构设计》。
这篇文章从深层次,更加抽象地论证了一个互联网应用应该有的设计要点,而这些设计要点,成为后来我们能看到的所有高并发应用设计都必须要考虑的问题,再加上RESTAPI比较简单直接,所以后来几乎成为互联网应用的标准接口。
和SOAP不一样,REST不是一种严格规定的标准,它其实是一种设计风格。如果按这种风格进行设计,RESTful接口和SOAP接口都能做到,只不过后面的架构是REST倡导的,而SOAP相对比较关注前面的接口。
而且由于能够通过WSDL生成客户端的Stub,因而SOAP常常被用于类似传统的RPC方式,也即调用远端和调用本地是一样的。
然而本地调用和远程跨网络调用毕竟不一样,这里的不一样还不仅仅是因为有网络而导致的客户端和服务端的分离,从而带来的网络性能问题。更重要的问题是,客户端和服务端谁来维护状态。所谓的状态就是对某个数据当前处理到什么程度了。
当有了RPC之后,我们本来期望对上层透明,就像上一节说的“远在天边,尽在眼前”。于是使用RPC的时候,对于状态的问题也没有太多的考虑。
不光NFS,如果浏览翻页,我们经常要实现函数next(),在一个列表中取下一页,但是这就需要服务端记住,客户端A上次浏览到20~30页了,那它调用next(),应该显示30~40页,而客户端B上次浏览到100~110页了,调用next()应该显示110~120页。
上面的例子都是在RPC场景下,由服务端来维护状态,很多SOAP接口设计的时候,也常常按这种模式。这种模式原来没有问题,是因为客户端和服务端之间的比例没有失衡。因为一般不会同时有太多的客户端同时连上来,所以NFS还能把每个客户端的状态都记住。
公司内部使用的ERP系统,如果使用SOAP的方式实现,并且服务端为每个登录的用户维护浏览到报表那一页的状态,由于一个公司内部的人也不会太多,把ERP放在一个强大的物理机上,也能记得过来。
但是互联网场景下,客户端和服务端就彻底失衡了。你可以想象“双十一”,多少人同时来购物,作为服务端,它能记得过来吗?当然不可能,只好多个服务端同时提供服务,大家分担一下。但是这就存在一个问题,服务端怎么把自己记住的客户端状态告诉另一个服务端呢?或者说,你让我给你分担工作,你也要把工作的前因后果给我说清楚啊!
再比如,客户端说我想访问下一页,服务端说,我怎么知道你当前访问到哪一页了。所以客户端要先看看自己访问到了100~110页,然后告诉服务器说,我想访问110~120页。
这就是服务端的无状态化。这样服务端就可以横向扩展了,一百个人一起服务,不用交接,每个人都能处理。
所谓的无状态,其实是服务端维护资源的状态,客户端维护会话的状态。对于服务端来讲,只有资源的状态改变了,客户端才调用POST、PUT、DELETE方法来找我;如果资源的状态没变,只是客户端的状态变了,就不用告诉我了,对于我来说都是统一的GET。
虽然这只改进了GET,但是已经带来了很大的进步。因为对于互联网应用,大多数是读多写少的。而且只要服务端的资源状态不变,就给了我们缓存的可能。例如可以将状态缓存到接入层,甚至缓存到CDN的边缘节点,这都是资源状态不变的好处。
按照这种思路,对于API的设计,就慢慢变成了以资源为核心,而非以过程为核心。也就是说,客户端只要告诉服务端你想让资源状态最终变成什么样就可以了,而不用告诉我过程,不用告诉我动作。
这种API的设计需要实现幂等,因为网络不稳定,就会经常出错,因而需要重试,但是一旦重试,就会存在幂等的问题,也就是同一个调用,多次调用的结果应该一样,不能一次支付调用,因为调用三次变成了支付三次。不能进入cda,做了三次,就变成了cda/a/a。也不能扣减库存,调用了三次,就扣减三次库存。
当然按照这种设计模式,无论RESTfulAPI还是SOAPAPI都可以将架构实现成无状态的,面向资源的、幂等的、横向扩展的、可缓存的。
但是SOAP的XML正文中,是可以放任何动作的。例如XML里面可以写
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,而且是一种架构模式,主要面向资源,提供无状态服务,有利于横向扩展应对高并发。
来源:
刘超-趣谈网络协议系列课;
文章为作者独立观点,不代表股票交易接口观点