快速可扩展的Ajax流代理——提供持续下载跨域数据

简介

由于浏览器禁止跨域的XMLHTTP调用,所有的Ajax网站都必须有一个服务端代理来从外部域比如Flickr或者Digg来抓去内容。对客户端Javascript代码来说,一个XMLHttp的调用将请求传递给宿主在相同域里的服务端代理,然后由代理来从外部服务器上下载内容,并回传给客户端。通常,所有从外部服务器获取内容的Ajax站点都采用这种代理方案,除了一些罕见的使用JSONP的人。当网站上的许多组件正在从外部域下载内容时,这样的代理将会被大量地调用。所以,当代理开始被百万次地调用时,它将变成一个可扩展的问题。另外,一个页面整体的负载均衡很大程度上依赖于当代理向页面提供内容时它的性能。这篇文章,我们来看看我们如何能将传统的Ajax代理变得更快,异步,持续提供内容流,从而使其更具扩展性。

Ajax代理 进行时

当你访问Pageflakes.com时,你可以看到这样的代理正在工作。你将看到许多不同的内容,例如天气预报、flickr图片、YouTuBe的视频以及RSS等,像一个个部件一样从许多不同的外部域中被加载。所有这些加载都需要使用一个内容代理。该内容代理在最近一个月为差不多42.3百万的URL提供过服务。使它既快又有扩展性,是对我们相当大的挑战!有时内容代理需要提供兆字节数据的服务,这更加提高了这些挑战的高度。因为这样的代理会被大量的调用,如果我们能够为每个调用平均节约大概100ms,我们每个月就可以为下载、上传、处理的时间节约大概4.23百万秒。这大概1175人时都被全世界百万人在浏览器前等待下载内容而浪费掉了。

这样的内容代理将外部服务器的URL作为一个查询参数。它从这些URL下载内容,然后将这些内容作为响应回传给服务器。


图片:内容代理像一个中间人一样在浏览器和外部域之间工作。

上面的时间线显示了,一个请求如何到达服务器,然后服务器向外部服务器发出请求,下载响应,并将它传输给客户端。从代理到浏览器的响应箭头比从外部服务器到代理的箭头更长,这是因为通常一个代理服务器的宿主环境比用户的互联网连接有更快的下载速度。

一个基本的代理

这种内容代理也同样存在与我的开源Ajax网站——Dropthings.com中。你可以去CodePlex看它的代码是如何实现这样一个代理的。

下面是一个非常简单,同步,没有流的阻塞代理。


尽管它显示了通常的原则,但它没有接近一个真实的代理,因为:

(1)      他是一个同步代理,因此没有可扩展性。每一个对该Web方法的调用,都导致Asp.net线程处于等待状态,直到对外部URL的调用完成。

(2)      它是非流式的。它第一次从服务器上下载整个内容,将内容存储在一个字符串中,然后向浏览器更新整个内容。如果你点击一个MSDN Feed URL,它将从服务器下载220KB的巨大的RSS XML,将它存储到一个220KB的长字符串中(总得来说,是.net内置String类型的双倍大小,并且都是Unicode字符),然后将这220KB写到asp.net响应对象(Response)的缓冲区(buffer)中,并将另外的220KB的UTF8的字节数组存储在内存中。然后,那220KB将被传递到IIS,以便可以传输到浏览器。

(3)      它没有在服务端存产生一个正确的响应头来缓存响应。它也没有从源文件中提供重要的头部,例如Content-Type。

(4)      如果一个外部URL提供对内容的GZIP压缩,它解压内容到一个字符串来表示,因此它浪费了服务器的内存。

(5)      它没有在服务端缓存内容。因此,重复对相同外部URL的调用也将从外部URL重新下载数据,因此浪费了你服务端的带宽。

我们需要一个异步的streaming proxy当它从外部域服务器下载后传输内容到浏览器。因此,它将从外部服务器下载字节流到一个小块儿中,并且直接将其传输到浏览器。结果是,在调用Web Service之后浏览器将看到一个持续的字节传输。当内容已经完全从服务器上下载下来后,将没有延迟。

一个更好的代理

之前,我展示了复杂的基于流代理的代码,让我们讨论一个改进式的方案。让我们建立一个比上面更好的内容代理。上面的代理是一个同步并且非流式,但没有其他问题的代理。我们将构建一个命名为Regular.ashx的HTTP Handler,它将把URL作为查询参数。它也将缓存作为一个查询参数,该查询参数将用来产生一个正确的响应头来在浏览器上缓存内容。因此,它将一次又一次减少浏览器重复下载相同内容的时间。



上面的代理主要增强了两点功能:

l  它允许服务端缓存内容。在一段时间内来自不同浏览器的相同URL请求,在服务端将不会再次下载,而是从服务端缓存中获取数据。

l  它产生一个正确的输出响应头,可以让内容缓存到浏览器端。

l  它没有在内存中解压下载的内容。它保持原始字节流的完整。它节省了内存。

l  它用一种无缓冲的形式发送数据,这意味着asp.net的Response对象没有缓冲响应,因此节约了内存。

然而,这是一个“阻塞”式的代理。

更好的代理——基于流

我们需要构建一个基于流的异步代理来提供更好的性能。下面的图阐述了这是为什么:


图片:持续的流式代理

就像你所看到的,当服务器下载内容的时候,数据从服务器被发送回浏览器,服务端下载的延时被消除。因此,如果服务器花300ms来从一个外部资源下载内容,然后花700ms将它发送回服务器,你就可以在服务器和浏览器之间节约300ms的网络延迟(因为异步就会边下载边传输数据流)。这种方案在当外部服务器提供的内容非常慢并且需要花相当长的一段时间来提供内容时效果非常好。外部站点越慢,采用这种持续流式代理,你节约的时间就越多。当你的站点很远时,这比起阻塞式的方案在性能上是一个很大的提升。

这种持续式的代理方案是:

l  用一个特殊的线程(读取器线程)来从外部服务器读取一个8KB的字节块,目的是使其不阻塞。

l  将读取到的块存储在一个被称为管线流的内存队列里面。

l  从该队列里将块写到Asp.net的Response对象。

l  如果队列完成,使其处于等待状态直到更多的字节被读取器线程下载。


这种管线流需要是线程安全的,并且它需要支持“阻塞式”的读取。“阻塞式”的读取,这意味着如果一个线程尝试读取一个块儿并且流是空的,将暂停该线程直到另一个线程在流上写完东西。一旦在流上的一个写操作发生了,它将恢复读线程并且允许它继续读。我从CodeProjectarticle by James Kolpack那里获得了管线流的代码,并且测试了并确信它有很高的性能,支持存储字节块而不是单个字节,支持等待超时等等。

我将普通的代理(阻塞、同步、下载完所有之后才传输数据)和流式代理(从外部服务器到浏览器持续传输数据)做了一些对比。两个代理下载MSDN资源并且传输到浏览器。显示在下面的时间是从浏览器发出请求到代理然后回传整个响应到客户端。


这不是一个非常科学的图片,并且响应的时间还取决于从浏览器到代理服务器之间连接的速度和从代理服务器到外部服务器的速度。但,它显示了大部分时间里,流式代理比通常的代理表现出更好的性能。

构建流式代理

构建一个性能好于普通流式代理并不是那么简单。我尝试了三种方式,最终找出了最佳组合可以表现出比普通代理更好的性能。

该流式代理使用HttpWebRequest和HttpWebResponse来从一个外部服务器下载数据。它们被用来对如何读取数据取得更多得控制,更具体地说,是读取WebClient不提供的字节块儿。另外,这里有某些对构建一个代理需要的快速、可扩展HttpWebRequest的优化。


DownloadData方法从输出流(连接到外部服务器)下载数据,然后将其发送给asp.net的Response流。


这里,我曾尝试了三种不同的方案。一个是现在被注释掉的,叫做TransmitDataAsyncOptimized,是最好的解决方案。我将解释这三种方案。DownloadData方法的目的是在发送数据之前准备asp.net的输出流。然后,它使用这三种方案里的其中一种发送数据,并缓存下载的字节到内存流中。

第一种方案是从连接到外部服务器的输出流中读取8192字节,然后直接写到响应(TransmitDataAsyncOptimized)。

这里,readStream是从HttpWebResponse.GetResponseStream调用返回的输出流。它从外部服务器下载。responseBuffer仅仅是用来在内存中存储整个响应一个内存流,目的是我们能够缓存它。

这个方案甚至比一个通常的代理更慢。在做了一些代码级别的性能分析之后,看起来写入OutputStream花费了相当长得时间,因为IIS尝试发送数据到浏览器。所以,这里会有网络延迟和传输数据的延迟。从频繁调用累积的网络延时到OutputStream.Write加起来显著地延迟了整个操作。

第二个方案是尝试多线程。一个从asp.net线程创建的新线程,持续地从Socket读取,甚至不用等待Response.OutputStream发送字节到浏览器端。主asp.net线程等待直到所有的字节被收集完成,然后直接传输它们到response。


这里,读取是在PipeStream上进行的,而不是从asp.net线程的socket上。这里有一个新线程被催生,它将数据写入PipeStream就像它从外部站点下载字节一下。结果,我们有asp.net线程持续地将数据写入OutputStream,并且有另外一个线程从外部服务器不断地下载数据。接下来的代码从外部服务器下载数据,然后存储到PipeStream中。


这个方案的问题是,仍然有很多Response.OutputStream.Write的调用发送。外部服务器发送各种不同字节数的内容,有时3592字节,有时8192字节,并且有时仅仅501字节。它完全依赖于从你的服务器到外部服务器的连接有多快。通常,微软的服务器都是非常快速的,当你调用_ResponseStream.Read从MSDN读取资源的时候,你总是能获得8192(缓存区的最大容量)的字节,但,当你连接到一个非可靠的服务器,例如在澳大利亚,你将无法在每次读取调用时都获得8192个字节的数据。所以你将以超过你对Response.OutputStream.Write预期调用次数而结束。所以,一个更好的最终方案是介绍另一个缓冲区,它将存储将写到asp.netResponse的字节,并且一旦8192个字节已经准备好传输时它将自己清空缓冲区到Response.OutputStream。在这中间缓冲区将确保总是有8192个字节被发送到Response.OutputStream。

上面的方法确保一次仅有8192字节被写入asp.netResponse.Stream中。以这种方式,写的次数是总字节数/8192。

用异步httphandler构建流式代理

现在,我们正基于流来传输字节,我们需要让这个代理“异步”让它不把持着asp.net的主线程太长时间。变成异步,这意味着一旦asp.net线程向外部服务器发出一个调用,它就会被释放。当外部服务器调用完成并且字节都下载完成,它将从asp.net抢占一个线程然后完成执行。

当代理不是异步的时候,它会使得asp.net线程很繁忙,直到整个的连接以及下载操作完成。如果外部服务器响应很慢,那它就没有必要持有asp.net线程太长时间。结果如果代理正对一个很慢的服务器发起太多的请求,asp.net线程不久就将消耗殆尽,并且你的服务器将停止响应任何新请求。

构建一个异步代理的第一步是实现IhttpAsyncHandler然后将ProcessRequest方法分成两部分:BeginProcessRequest和EndProcessRequest。Begin方法将对HttpWebRequest.BeginGetResponse发出一个调用,然后线程返回到asp.net的线程池。


当BeginProcessRequest调用完成并且外部服务器已经开始向我们发送响应数据,asp.net会调用EndProcessRequest方法。该方法从外部服务器下载数据,然后发送回浏览器端。


现在你已经拥有了它——一个快速的,可扩展的,持续流式的代理,它总是会比通常的代理有更好的性能。

如果,你正在考虑写HttpHelper,AsyncState,以及SyncResult类,这里有一些现成的类,下面是这些帮助类的代码:


源代码下载地址:

http://download.csdn.net/detail/yanghua_kobe/3702484

转载于:https://www.cnblogs.com/wdpp/archive/2011/10/20/2386598.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422717.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Markdown编辑器 公式指导手册

#Cmd Markdown 公式指导手册 标签: Tutorial 2018-03-20 补档: 收到很多小伙伴对本文的源文档转载需求,故传了一份 md 文件,请按需 下载 。 本文固定链接: https://www.zybuluo.com/codeep/note/163962 点击跳转至 Cmd Markdown …

python建db文件_临时数据库之python用sqlite3模块操作sqlite

SQLite是一个包含在C库中的轻量级数据库。它并不需要独立的维护进程,并且允许使用非标准变体(nonstandard variant)的SQL查询语句来访问数据库。一些应用可是使用SQLite保存内部数据。它也可以在构建应用原型的时候使用,以便于以后转移到更大型的数据库。…

MDX as的使用

用到as的时候,如果用到了某个纬度,例如:withmember mydate as now()member [告警投诉数量a] as lookupcube("[ALARM_RECORD]","[Measures].[告警投诉数量]") member [故障总量啊] as lookupcube("[EOMS_FAULT_RECO…

【算法设计与分析】03 算法及其时间复杂度

在学习算法的时间复杂度之前,需要了解下面5条概念 什么是算法的时间复杂度? 针对指定基本运算,计数算法所做的运算次数。什么是基本运算?比较、加法、乘法、置指针、交换…什么是输入规模?输入串的编码长度&#xff0c…

用单片机测量流体流速的_影响超声波流量计(热量表)测量精度的主要因素

1、上下游直管段的影响由于时差式超声波流量计标定系数K值是雷诺数函数,所以当流体从层流过渡到紊流时,其流速分布不均匀,标定系数K值将产生较大的变化,从而影响测量准确度。根据设计要求换能器应安装在上游直管段为10倍管径、下游…

【算法设计与分析】04 函数的渐进的界

今天学习函数的渐进的界,会涉及多种数学符号。对以后学习分析算法复杂度有很大的帮助。 1 大OOO符号 定义: 设 f 和 g是定义域为自然数集N上的函数. 若存在正数 c 和 n0, 使得 对一切 n ≥\geq≥ n0有: c≤f(n)≤cg(n)c\leq f(n) \leq cg(n…

11月10日 14:00~16:00 上海敏捷开发沙龙

主题:火星人陈勇将赴上海主办线下沙龙,主题是“自组织团队与松结对编程”(2011 微软 TechED演讲主题),演讲后有团队问答PK活动。日期:2011年11月10日时间:下午14:00~16&a…

c语言头文件和源文件_C语言头文件防卫式声明

C语言一般提供三种预处理功能:宏处理、文件包含、条件编译。头文件防卫式申明中会用到条件编译中 #ifndef、#define、#endif 的用法。所以,首先价绍下条件编译。1 条件编译一般情况下,在生成可执行文件的过程中,源程序文件中的所有…

tomcat原理,一个客户端请求的处理过程

假设来自客户的请求为:http://localhost:8080/wsota/wsota_index.jsp1) 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得2) Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应3) Engine获得…

python定义函数需要保留字def_定义函数时要用到哪一个python保留字?_学小易找答案...

【简答题】常见的足部畸形有哪些?例举6种以上。【其它】请将会计学小册子第四章完成情况(选择判断业务题2. 6.)的所有内容拍照上传嗷【其它】以小组为单位进行现场调研,对足球场外的广场区域植物造景进行统计,每个同学利用CAD软件对该场地进行绘图,掌握园林中自然式绿地的植物…

aop编程时出现错误信息:java.lang.NoClassDefFoundError

代码错误信息: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘calImpl’ defined in file [/Users/lyy0217/Downloads/SSM/target/classes/com/cm/service/aop/CalImpl.class]: BeanPostProcessor before instantiat…

Firebug控制台详解

作者: 阮一峰 日期: 2011年3月26日 Firebug是网页开发的利器,能够极大地提升工作效率。 但是,它不太容易上手。我曾经翻译过一篇《Firebug入门指南》,介绍了一些基本用法。今天,继续介绍它的高级用法。 Fi…

标签 href 怎么拼接_【微信】用户-标签的兴趣建模

这一篇分享的是CIKM2020微信的learning to build user-tag profile,主要介绍了微信看一看("Top Stories")中,如何进行用户-标签的兴趣建模,提升推荐效果。1、背景看一下微信看一看场景下的推荐流程&#xff…

【算法设计与分析】05 有关函数的渐进的界的定理

上一篇文章学习了函数的渐近的界定义,本篇文章继续学习函数渐近的界定理。这些定理的证明,用到了函数渐近的界的定义。点击查看上一篇文章:【算法设计与分析】04 函数的渐进的界 文章目录1. 定理11.1 证明定理11.2 估计函数的阶1.3 一些重要的…

一般窗体的设计方式

一般窗体的设计方式: 包含有(搜索框、数据列表框、分页框)、由搜索返回的数据列表中是否包含“当前页码”、“页数量”的名称来决定显示分页功能。 如果你的搜索框经常性出现大于10个字段的搜索的话,那么应该采用HashTable的参数传…

hive 如何将数组转成字符串_教你如何将Power Logic的原理图转成Orcad的原理图

1、使用Power Logic软件打开pads的原理图(此处使用的是PADS9.5版本的);2、执行菜单命令:File->Export->在弹出的对话框中点击“保存”按钮,然后选择“Select All”,并选择PADS Logic2005的版本输出,最后点击“OK…

【算法设计与分析】06 几类重要的函数

本篇文章中会用到上一篇文章的定理:【算法设计与分析】05 有关函数的渐进的界的定理 主要学习常见的一些函数的阶 1. 基本函数类 以下按阶的高低排序: 至少指数级: 2n, 3n, n!, …多项式级: n, n2, nlogn, n1/2, …对数多项式级…

vscode私钥设置_VSCode远程开发配置指南

凭借Windows10的大量组件和Visual Studio Code的强大插件,在Windows下远程开发Linux相关程序如今已经成为高效的选择。比起Vim,我更加喜欢VS Code,最主要的原因就是无需繁琐的配置,并且一次配置,可以通用很多环境。这大…

【算法设计与分析】07 算法的数学基础

接下来的几篇文章将是学习算法的数学基础内容。 具体的文章包括(持续更新):