当前位置: 首页 > Java > 正文

异步模式下的Web请求的介绍

1 星2 星3 星4 星5 星 (1 次投票, 评分: 5.00, 总分: 5)
Loading ... Loading ...
baidu_share

缘起

早在两年前做开放平台的时候,由于平台的特质,就开始寻求对于Web请求异步的解决方案,当时Jetty和Tomcat都在最新的版本中集成类似于Comet和Asyn Process的功能,但经过测试,效果不佳,因此也没有再深入去了解其中的一些设计理念。时隔两年,依然在做开放平台,但当研究twitter和facebook api的时候,发现已经有了Streaming 模式的Web请求处理模式,由此又再次的去了解今天,在Servlet3规范已经逐渐成熟的情况下,容器,开源项目对于Web请求的异步化是否已经有了很大的提高。在我看来,传统的Web Container要改造成为异步模式要解决内在和外在两方面问题,内在需要将那么多年的成熟体系打破,以分阶段,异步化的方式来利用现有的功能(异步化很大问题就是复杂了流程,包括对多任务多线程的协调,对资源的分配和回收等等),外在就是要让用户如何能够在最小的代价下移植到异步模式的场景(如果代价很大,那么就很难在已有系统上推广,同时使用的学习曲线也是推动新技术的一项很重要的指标)。闲话不多说,后续会从概念,到具体的概念实现,最后到实际测试,给一个完整的评估。目标很明确,最基本要学会设计中的一些好的思想,如果能够找到使用场景就尝试使用,最终在具体场景下改造现有系统比对效果。

Comet & Async Request Process

概念

Comet和Async Request Process的技术已经不算是什么新鲜事物了,但是真正在后台业务体系中大量使用其实还不多,主要原因还是现在成熟的Web容器都是基于servlet 2.5实现,遵循的也是标准的Http无状态应答式请求处理模式。但随着互联网应用不断发展,时刻都以人为本的情况下,如何让用户体验不断提升,就要求能够在很多场景尽量减少交互延时,尽量多的有人性化的展现(更多的数据交互)。因此,Comet和Async Request Process的应用场景就诞生了,这里我只是说应用场景诞生了,也表明其实传统的应答式请求处理模式在很多场景还是有他的优势。

Comet中文如果直译叫做彗星,其实是比较形象的一种说法。Comet在某些场景下也被叫做Http Streaming。Comet作为一种技术手段,其实是指在客户端和服务端建立连接后,可以由服务端主动发起请求将数据推送给客户端,而客户端根据推送的数据增量迭代的更新展示。因此服务端的数据推送就好比彗星一样不定时的传送到了客户端。

Async Request Process也可以被叫做long polling,表示异步请求处理。Async Request Process和传统的Http请求的差别就和BIO与NIO差别类似。Web容器或者传统的Socket应用在处理请求的时候,通常都是One Connection One Thread来处理,申请资源的回收速度根据业务处理来决定,如果后端处理的慢,那么在连接输入和输出部分的资源就会闲置而导致浪费(input buffer 和 output buffer),通过Selector和事件模型可以将流程切割成为更细的任务阶段,提高资源利用率。

 

优缺点及应用场景

在Servlet3规范中已经将Comet和ARP(Async Request Process)都作为基本内容涵盖在内,很多支持servlet3的容器也都已经支持了这些特性,后面会具体的谈到。但是这些特性其实也是在一些特定场景下才会体现出其价值,同时也存在着自身的不足之处。

Comet与传统的处理模式最大的特点就在于http通道的长连接和服务端主动推送,基于事件模型。对于客户端频繁要去获取状态数据或者消息数据的场景下,通过传统的请求方式来轮询会极大消耗服务端的资源(带宽和业务处理能力),同时反复建立数据连接也会造成服务端和客户端性能的消耗。但另一方面,其实这两个特点也会成为缺点,保持长连接会导致大量的连接资源消耗(如果没有数据传输的话),另一方面服务端如果数据推送过于频繁,会导致客户端崩溃。

对于Comet和传统Http请求处理的取舍,需要考虑这些因素:

1.  是否是单个客户端反复需要请求服务端获取数据或者状态的场景。是则继续2的判断,否则考虑使用传统的方式。

2.  客户端数目多少。如果客户端数目不多,则直接采用Comet,否则考虑第三点。

3.  单个客户端对于服务端请求的频度。(主要是由服务端数据状态变化的频度来决定),频度高,则考虑才用Comet,因为如果频度高,那么长连接的利用率就会高,则长连接带来的消耗就可以忽略。

对于服务端数据推送过多导致客户端崩溃,可以通过在服务端做数据合并或者在客户端丢弃数据的方式来提升性能。(建议在服务端处理,减少带宽和两方的性能消耗)

最后就是Comet的编程模型基于事件模式和Http长连接,那么首先需要选择新版本的容器,例如Tomcat7或者jetty6以上或者glassfish的新版本,其次服务端开发需要符合事件模型驱动的设计,客户端也需要支持长连接的数据增量推送处理和展示。另一方面确保你的网络方面在做LB的时候不会由于Http长连接过多导致LB性能大幅度下降,同时客户端网络如果质量不好也会间接导致服务端Load上升。

Async Request Process的特点在于长连接和类似于NIO的设计理念,将服务请求处理过程更加细化,基于事件模型驱动和Selector的方式,提高了高并发下的服务处理能力,同时也在资源管理方面提供了更多的优化空间。ARP在不同的容器中或者开源项目中实现的细节都有可能不同,特别是对于请求的挂起,唤醒,终止,对于资源的分配,回收,都有自己的优化和设计思路,后续再介绍Jetty的Continuation会谈到它的一些设计理念。在一定程度上Comet设计是包含了ARP的,ARP只负责一次请求的交互。ARP的优势就在于能够最大限度优化容器或者Socket连接处理能力,优化资源分配,提高并发处理能力。劣势在于编程习惯不符合常规的模式,对框架的要求高(异步协同,线程和资源管理等,由此带来的复杂度会导致可用性会受到影响)。

对于ARP和传统Http请求处理的取舍,需要考虑这些因素:

1.  是否是高并发应用。不是则选择传统方式,是则考虑第二点。

2.  服务处理时间较长或者不确定。如果处理时间很短,业务逻辑极为简单,则选择传统方式,否则考虑第三点。

3.  瓶颈是否在后端,优化前端处理能力是否会产生反效果。如果后端处理能力强,前端是瓶颈,则选择ARP,如果后端已经到了无可优化的地步,则考虑采用传统方式。

今天很多人没有去使用ARP方式,一方面是容器的不成熟,其次也是因为当前前端不是瓶颈,而且靠堆机器很容易解决问题,而后端例如数据库,存储成为了瓶颈,因此前端优化反而会将水流放的更多,导致后端在没有优化或者无法优化的情况下瓶颈劣势显示的更加突出。因此优化系统不是对局部的优化,而是对整个系统自下而上的优化,任何一个关键路径成为瓶颈,那么其他的优化都失去意义。

这里也实际的举两个例子来说明TOP在那些实现中需要用到Comet和ARP。

ARP的两个应用场景:

TOP最大的一个难点就是服务的隔离问题,所有的淘宝服务无差别的被集成在TOP的服务集群中,TOP的一个基本功能就是Proxy,由于不同服务的处理能力不同,响应时间也是不同,对于图片上传类服务处理速度可能平均要到300ms,而对于普通的请求可能就只需要几ms,而对于容器连接资源的申请却都是一样的,一个Request对应一个Thread去处理,当后端某个服务出现问题或者响应较慢的时候,那么会导致容器的连接资源被大量hold,最后影响到其他服务的正常中转。这里回顾我刚才谈到使用ARP的几个判断点,首先需要处理大并发的情况(当前每天3亿多次api的服务call,到年底估计会有10亿左右的call),其次后端服务处理时间是不定的,有些长有些短,有些根据服务当前所处压力而不同,最后平台的服务由于单个问题而影响全部,其实表明后端服务能力其实不是瓶颈(当然对于出现问题的服务会采取降级和保护的措施,在没有实施保护的时候需要能够继续提供对于其他正常服务的中转)。

TOP的请求处理流程其实可以分成很多个Pipe,有安全的Pipe,有业务预处理Pipe,有业务转发Pipe,有业务后处理Pipe等等,其中参数的解析和处理及业务转发等待回应是最消耗的两个Pipe,参数解析当前通过lazy读取stream来减少错误请求对于内存的消耗(这同jetty的一个设计类似),对于业务中转及等待回应的过程其实可以作为一个异步的事件交由服务框架处理,而将请求接收处理线程挂起,释放掉必要的资源(输入输出缓冲),提高整体处理能力。

Comet的应用场景:

对于很多大商家需要能够比较及时的了解当前的交易处理状况和订单情况,因此需要有API能够支持这样的场景。最初采用类似于notify的方式去实现,但是在实施的过程中发现,外部服务notify的成本过高(服务回调地址的接收能力及可用性较差,服务端的资源大量消耗),最后修改成为客户端有限制的定期轮询获取增量数据。其实,现在外部宣称很多对外的http方式的notify都不是很靠谱,也有类似于pubsubhubbub的方式,但其实这也增加了一层订阅关系的中转和维护的成本。如果采用Comet的方式,一来可以节省大量轮询带来的开销,同时复用长连接可以减少外部连接产生的通信消耗,在加上对数据推送的优化合并,在一定程度上可以实现外部数据推送的场景。

同样还有在产品推广的业务上,当很多外部店铺推广一款商品时候,如果商品发生了变化需要能够告知推广的应用插件,如果靠轮询会给TOP带来不小的压力,因此通过修改事件触发Comet事件来更新商品推广信息。

 

外部开源实现

外部有很多项目实现了Comet和ARP,这里就举出其中一部分:

Web容器:

Jetty 6以上的版本(Continuation),Tomcat 6以上,JBoss配置resteasy,glassfish(glassfish的grizzly容器内核天然支持)

开源项目:

asyncWeb(基于mina)和xsocket

具体的使用可以参看这些项目的技术文档,我这里只是给出一些设计的特点说明,在异步模式下的Web请求(实践篇)中结合详细改造和测试来说明具体的实施效果及在改造过程中需要注意的内容。

Jetty

我个人比较喜欢的一个内嵌式容器,现在像GAE和Hadoop都在用它,它也是最早支持Comet和ARP的容器。在我过去的基于SCA规范的服务框架中,发布REST的内嵌容器就采用的是Jetty,处理能力还是比较强,在业务协议方面也支持比较广(同时支持ajp协议,ssl等)。

Jetty中的异步处理叫做Continuation,这里不是基于事件驱动模型的,而是通过Continuation这个对象来suspend/resume请求处理线程,同时它的suspend/resume也不是和传统的wait/notify一样,在阻塞的地方直接挂起或者被唤醒。它的resume其实又再次模拟了请求,会二次进入服务处理流程,同时第一次和第二次请求数据是不共享的(可以通过attachment来传递数据)。就这么看来,其实在suspend的时候所有的资源都被释放了,仅仅只是保存了请求来源信息在队列中,在后续被唤醒的时候再次模拟请求,由业务代码在实现中判断是否是第一次进入,并且在不同进入时请求处理过程做差异化实现,最终将不同的逻辑通过不同阶段的重入判断来分阶段处理。

Jetty有两个技术优化点:

1.split buffers。jetty6 采用split buffer架构和动态buffer分配架构。一个idle的connection没有buffers分配给他,一旦请求收到,则会有小的请求头buffer会被分配。大部分都是小请求,则只需要分配消息头buffer,当发现有大数据内容的时候则分配大buffer.当在等待回写数据到response的时候,输出缓存不会被分配。只有当servlet开始写入数据到response的时候,输出缓存才被分配,只有当response被提交的时候,response的消息头缓存才被分配,并且写出到输出缓存,有效的执行写操作。总的来说分配缓存只有在他们需要的时候,而且是根据需求分配

2.延时分发。支持用异步io读取内容,延时分发请求到处理器,减少在处理器等待的时间。(简单来说就是等到read了才去调用服务处理,避免无谓的资源等待浪费)

TOMCAT

Tomcat采用的与Jetty不同的设计方式,它的Comet是事件驱动的。每一个传统的Servlet需要实现CometProcessor接口,这个接口就需要实现类似于原来servlet的service的event方法,event方法会在各种事件发生的时候被激发,event当前主要包含了整个处理的生命周期(begin,close,error,read)。

需要注意的是在Tomcat配置connector的时候必须选择apr或者nio的connector,否则是不生效的。可以看到,就和NIO一样,对于连接建立,数据可读,都是基于事件触发,将业务和具体的连接分开,提高在业务处理较慢的情况下,服务器的吞吐能力。这种设计是比较贴近于NIO的设计思想的。

上面介绍的都是服务端的异步处理,在客户端其实也需要实现异步化的处理模式,通常情况况下都是基于事件模型实现的。服务端和客户端如何在异步的情况下理解消息的来源,一种是通过默认消息发送和接收保持顺序一致的方式,另一种就是通过颁发消息会话号来实现。

到此为止都是对于技术的介绍,没有实质性的使用,后续会根据TOP的实际应用场景去尝试改造(主要采用jetty或者Tomcat来实施),并作压力测试,最终得出实际的使用结果,来判断技术的成熟度。

本文固定链接: http://www.chepoo.com/web-requests-in-asynchronous-mode-introduction.html | IT技术精华网

异步模式下的Web请求的介绍:等您坐沙发呢!

发表评论