[WCF REST] 提高性能的一个有效的手段:条件资源获取(Conditional Retrieval)

条件获取(Conditional Retrieval)旨在解决这样的问题:客户端获取某个资源并对其进行缓存,当再次获取相同资源时,如果资源数据与之前获取的一致,则不再返回真正的资源数据,而是在回复中设置一个“标识”表明获取的资源并未发生改变。[源代码从这里下载]

一、 HTTP对条件获取的支持

HTTP对条件获取提供了原生的支持。具体的实现是这样的:服务端接收到客户端针对某个资源的第一次获取请求时,除了将资源数据作为HTTP回复主体返回之外,还会设置一个叫做ETag的回复报头。这个ETag与资源本身关联并且可以对资源进行对等性判断,比如我们可以将资源内容的哈希码作为这个ETag报头。

客户端接收到资源后对其进行缓存,并从回复中获取到这个ETag报头值。当再次对相同的资源进行请求时,它会为HTTP请求添加一个名为If-None-Match报头,而该报头的值就是这个缓存的ETag值。服务端接收到该请求之后会通过If-None-Match请求报头确认最新的资源数据是否与该报头值代表的数据一致,如果一致则回复一个状态为“304
(Not Modified)”的空消息,否则将新的资源置于回复消息的主体并附上基于新资源数据的ETag报头。

除此之外,条件获取还支持另一种基于“最近修改时间”的资源改变判断机制。这种机制也很简单:服务端记录下资源最近一次修改的时间,并被作为客户端第一次访问请求的ETag回复报头。客户端针对相同资源的后续请求会将此ETag表示的时间作为一个名为If-Modified-Since的报头,而服务端则将该报头的时间和资源最近一次修改的时间进行比较从而确定请求的资源是否被改变。如果资源尚未改变则同样回复以状态为“304
(Not
Modified)”的空消息,否则将新的资源置于回复消息的主体并附上新的ETag报头。条件获取仅仅针对方法类型为GET和HEAD的HTTP请求。

二、 WebOperationContext与条件获取

对于Web

HTTP编程模型来说,通过当前WebOperationContext可以很容易地进行条件获取的检测和相相关HTTP报头的设置和获取。具体来说,服务端通过表示入栈请求上下文的IncomingWebRequestContext对象的CheckConditionalRetrieve方法进行条件获取的检测。其中参数类型为DateTime的重载用采用“最近修改时间”的资源改变判断机制。如果确资源尚未改变,则直接抛出一个HTTP状态为NotModified的WebFaultException,并将lastModified参数表示的时间作为回复消息的ETag报头。

对于其他的4个CheckConditionalRetrieve方法,作为参数的entityTag(ETag)将与请求消息的If-None-Match进行比较,如果不一致也会抛出HTTP状态为NotModified的WebFaultException,并将该参数值作为回复消息的ETag报头。

   1: public class IncomingWebRequestContext
   2: {    
   3:     //其他成员
   4:     public void CheckConditionalRetrieve(DateTime lastModified);
   5:  
   6:     public void CheckConditionalRetrieve(Guid entityTag);
   7:     public void CheckConditionalRetrieve(int entityTag);
   8:     public void CheckConditionalRetrieve(long entityTag);
   9:     public void CheckConditionalRetrieve(string entityTag);
  10:  
  11:     public DateTime? IfModifiedSince { get; }
  12:     public IEnumerable<string> IfNoneMatch { get; }
  13: }

IncomingWebRequestContext还具有IfModifiedSince和IfNoneMatch这两个只读属性,它们分别返回请求消息的If-Modified-Since和If-None-Match报头。而服务端针对回复消息的ETag报头的设置可以通过OutgoingWebResponseContext的四个SetETag方法来完成。

   1: public class OutgoingWebResponseContext
   2: {
   3:     //其他成员
   4:     public void SetETag(Guid entityTag);
   5:     public void SetETag(int entityTag);
   6:     public void SetETag(long entityTag);
   7:     public void SetETag(string entityTag);
   8: }

对于客户端来说,它可以通过当前WebOperationContext的IncomingResponse属性得到表示入栈回复上下文的IncomingWebResponseContext对象,并通过其只读属性ETag获取当前HTTP回复的ETag报头。

   1: public class IncomingWebResponseContext
   2: {
   3:     //其他成员
   4:     public string ETag { get; }
   5: }

如果客户端需要为请求设置If-Modified-Since和If-None-Match报头,则可以通过当前WebOperationContext的OutgoingRequest属性得到表示出栈请求上下文的OutgoingWebRequestContext对象,然后分别设置IfModifiedSince和IfNoneMatch属性即可。

   1: public class OutgoingWebRequestContext
   2: {
   3:     //其他成员
   4:     public string IfModifiedSince { get; set; }
   5:     public string IfNoneMatch { get; set; }
   6: }

需要注意的是,如果采用WCF客户端进行服务调用,一旦接收到状态为“304(Not Modified)”的回复会抛出如下图所示的ProtocolException异常,并提示“远程服务器返回了意外响应: (304) Not Modified”。

三、实例演示:创建基于条件获取的REST服务

接下来我们按照条件获取的方式来改造之前演示的用于管理员工信息的EmployeesService。假设我们的员工数量比较多,用于获取所有员工列表的GetAll操作将会返回一个庞大的数据。如果客户端对第一次获取到的员工列表进行缓存,那么对有后续针对GetAll操作的请求,在员工信息没有任何改变的情况下服务端只需要回复一个状态为304(Not
Modified)的HTTP消息即可。

为此我们对EmployeesService的GetAll操作方法进行了如下的改造:我们通过当前WebOperationContext得到表示入栈请求上下文的IncomingWebRequestContext对象,并调用其CheckConditionalRetrieve进行条件获取检验,而传入的参数是最新员工列表对象的哈希码。在返回员工列表之前我们将此哈希码作为了回复消息的ETag报头。

   1: public class EmployeesService : IEmployees
   2: {
   3:     //其他成员
   4:     private static IList<Employee> employees = new List<Employee>
   5:     {
   6:         new Employee{ Id = "001", Name="张三", Department="开发部", Grade = "G7"},    
   7:         new Employee{ Id = "002", Name="李四", Department="人事部", Grade = "G6"}
   8:     };
   9:     public IEnumerable<Employee> GetAll()
  10:     {
  11:         int hashCode = employees.GetHashCode();
  12:         WebOperationContext.Current.IncomingRequest.CheckConditionalRetrieve(hashCode);
  13:         WebOperationContext.Current.OutgoingResponse.SetETag(hashCode);
  14:         return employees;
  15:     }
  16: }

我们通过手工发送HTTP请求的方式来调用EmployeesService的GetAll操作,为此我们创建了如下一个GetAllEmployees方法。该方法的参数ifNoneMatch和eTag分别表示请求消息的If-None-Match报头和回复消息的ETag报头。我们通过调用HttpWebRequest的静态方法Create基于服务操作地址创建一个HttpWebRequest对象,并设置该请求的If-None-Match报头的HTTP方法(GET)。

我们通过调用HttpWebRequest对象的GetResponse发送请求并得到回复,在打印回复内容之前我们获取了回复的ETag报头。在回复状态为“304
(Not Modified)”的情况下,GetResponse方法会
抛出一个WebException异常,所以我们对该类型的异常进行的捕获。如果WebException异常的StatusCode属性返回的HTTP状态是我们预知的NotModified,则意味着获取的员工列表未曾改变,于是我们在控制台上打印“服务端数据未发生变化”字样。

   1: static void GetAllEmployees(string ifNoneMatch, out string eTag)
   2: {
   3:     eTag = ifNoneMatch;
   4:     Uri address = new Uri("http://127.0.0.1:3721/employees/all");
   5:     var request = (HttpWebRequest)HttpWebRequest.Create(address);
   6:     if (!string.IsNullOrEmpty(ifNoneMatch))
   7:     {
   8:         request.Headers.Add(HttpRequestHeader.IfNoneMatch, ifNoneMatch);
   9:     }
  10:     request.Method = "GET";
  11:     try
  12:     {
  13:         var response = (HttpWebResponse)request.GetResponse();
  14:         eTag = response.Headers[HttpResponseHeader.ETag];
  15:         using(StreamReader reader = 
  16:             new StreamReader(response.GetResponseStream(), Encoding.UTF8))
  17:         {
  18:             Console.WriteLine(reader.ReadToEnd() + Environment.NewLine);
  19:         }
  20:     }
  21:     catch (WebException ex)
  22:     {
  23:         HttpWebResponse response = ex.Response as HttpWebResponse;
  24:         if (null == response)
  25:         {
  26:             throw;
  27:         }
  28:         if (response.StatusCode == HttpStatusCode.NotModified)
  29:         {
  30:             Console.WriteLine("服务端数据未发生变化");
  31:             return;
  32:         }
  33:         throw;
  34:     }
  35: }

然后我们通过如下的代码调用上面定义的GetAllEmployees方法进行两次服务调用,并将第一次调用返回的ETag报头作为第二次调用的If-None-Match报头。

   1: string etag;
   2: Console.WriteLine("第1次服务调用:");
   3: GetAllEmployees("", out etag);
   4: Console.WriteLine("第2次服务调用:");
   5: GetAllEmployees(etag, out etag);
   6: Console.Read();

在服务成功寄宿的情况下调用这段程序会在控制台上输出如下的结果,从中我们可以看到员工列表数据只在第1次服务调用中返回。

   1: 第1次服务调用:
   2: <ArrayOfEmployee xmlns="http://www.artech.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Employee><Department>开发部</Department><Grade>G7</Grade><Id>001</Id><Name>张三</Name></Employee><Employee><Department>人事部</Department><Grade>G6</Grade><Id>002</Id><Name>李四</Name></Employee></ArrayOfEmployee>
   3:  
   4: 第2次服务调用:
   5: 服务端数据未发生变化

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-10-30 00:19:04

[WCF REST] 提高性能的一个有效的手段:条件资源获取(Conditional Retrieval)的相关文章

Oracle当数据记录超过千万(50000000)条时,并且不在同一个库,如何从一个表导到另一个表?提高性能

问题描述 Oracle中,当数据记录超过千万(50000000)条时,并且不在同一个库,如何从一个表导到另一个表?提高性能DA库有表TA,有8千万条数据,DB库有同样结构的TB表,如何在将TA表导入TB表,并且如何优化性能?我是用数据链,导的过程中,没有经过任何字段的计算insertintotbselect*fromta@数据链可是这样出现临时表空间TEMP一直增大,导致硬盘没空间了,怎么办?并且TEMP不自动释放如果是在存储过程中加一个计数器,用游标每10000条COMMIT一次,这样会不会提

通过 SQL Server 2005 索引视图提高性能

本文介绍了 SQL Server 2005 Enterprise Edition 中经过改进的索引视图功能.文中对索引视图进行了说明介绍,并讨论了可通过该功能改善性能的一些具体情况 一.索引视图 多年以来,Microsoft SQL Server 一直支持创建称为视图的虚拟表.通常,这些视图的主要作用是: • 提供一种安全机制,将用户限制到一个或多个基表的某个数据子集中. • 提供一种机制,允许开发人员自定义用户通过逻辑方式查看存储在基表中的数据的方式. 通过 SQL Server 2000,S

用 SQL Server 2000 索引视图提高性能

server|视图|索引|性能 什么是索引视图? 许多年来,Microsoft SQL Server 一直都提供创建虚拟表(称为视图)的功能.在过去,这些视图主要有两种用途: 提供安全机制,将用户限制在一个或多个基表中的数据的某个子集. 提供一种机制,允许开发人员定制用户如何才能以逻辑方式查看存储在基表中的数据. SQL Server 2000 已经扩展了 SQL Server 视图的功能,以提高系统性能.它可以在一个视图上创建唯一的群集索引和非群集索引,可以改进最复杂查询的数据访问性能.在 S

ORA FAQ 性能调整系列之——压缩索引会提高性能么?

索引|性能|压缩 Will compressing my indexes improve performance ?压缩索引会提高性能么? Author's name: Jonathan Lewis Author's Email: Jonathan@jlcomp.demon.co.uk Date written: 26th Feb 2003 Oracle version(s): 8.1 - 9.2 Compressed indexes have been around for a couple

Oracle中利用DETERMINISTIC声明提高性能(二) 参数顺序对性能的影响

虽然Oracle提供的DETERMINISTIC声明,本意是确保函数的确定性,但是如何合理利用,是可以用来提高性能的. 这一篇描述参数顺序对性能的影响. 上一篇文章提到了,如果希望通过DETERMINISTIC来获取性能收益,那么采用批量方式是必须的,而且数组的值相对而言越大对于性能的帮助会越大. 但是这里存在一个问题,如果需要处理的数据量本身很大,虽然重复的输入参数不少,但是总的参数不同的值更多,那么即使将ARRAY的值设置到1000,能带来的性能收益也很有限,因为即使1000次调用,也不能保

Oracle中利用DETERMINISTIC声明提高性能(一) ARRAY对性能的影响

虽然Oracle提供的DETERMINISTIC声明,本意是确保函数的确定性,但是如何合理利用,是可以用来提高性能的. 这一篇描述ARRAY对性能的影响. 关于DETERMINISTIC函数,以前已经写过一些文章了,不过对于DETERMINISTIC声明用来提高性能只是简单提了一句,并没有展开来说. 由于函数声明了DETERMINISTIC特性,Oracle对于相同的输入,可以只运行一次,而这对于代码比较复杂,调用时间较长的函数而言,确实可以提高性能. 但是在上面的几篇文章中也提到了,DETER

利用静态只读字段和静态构造函数提高性能

主要内容一句话概括: 把相对固定的数据在编译时仅通过查询一次数据库填充到公开的静态只读泛型集合类型字段中从而杜绝后续可能的数据库查询以提高性能. 相关背景:我们的应用程序中常常需要一些类似全国的省份列表这样的集合数据,而这些数据基本上是固定不变的或者是很长时间才会变一次.对于这样的数据,开发中我们常用的一个做法就是把这些数据存在数据库表中,然后查询填充到需要的地方.我觉得这种做法很脏,因为我们多次去查询这些固定不变的数据没有什么意义,或者进一步我们顶多缓存这些数据以避免一些查询以提高性能,也就是

使用SQL Server 2000索引视图提高性能

本文介绍 SQL Server 2000 企业版的新功能 - 索引视图.讲解索引视图并讨论一些提高性能的具体方案. 什么是索引视图? 许多年来,Microsoft SQL Server 一直都提供创建虚拟表(称为视图)的功能.在过去,这些视图主要有两种用途: 提供安全机制,将用户限制在一个或多个基表中的数据的某个子集. 提供一种机制,允许开发人员定制用户如何才能以逻辑方式查看存储在基表中的数据. SQL Server 2000 已经扩展了 SQL Server 视图的功能,以提高系统性能.它可以

在提高性能的同时,模拟信号链路怎样“走向绿色”呢?

很久以前,精确电气的测量是在原始的实验室环境中进行的,在这类环境中,有充足的电力供应,时间分配也能确保极高的准确性.今天,人们希望将仪表携带到现场,让仪表靠电池电源运行,并立即实现更高的准确性.模拟电路与数字电路不同,不会从较小的几何尺寸产生的比例效应中受益.如果功率消耗得较少,那么噪声(精确测量的大敌)实际上增加了.随着新的低压工艺出现,信噪比(SNR)变得更差了,这是可以理解的,因为信号幅度减小了.那么,在提高性能的同时,模拟信号链路怎样"走向绿色"呢? 很多高速仪表的核心是一个高