[WCF REST] 解决资源并发修改的一个有效的手段:条件更新(Conditional Update)

条件获取(Conditional Update)可以避免相同数据的重复传输,进而提高性能。条件更新(Conditional

Update)用于解决资源并发操作问题。如果我们预先获取一个资源进行修改或者删除,条件更新检验帮助我们确认资源被获取出来到针对它的修改/删除操作被提交的这段时间内是否被其他人改动过。[源代码从这里下载]

一、HTTP对条件更新的支持

HTTP为条件更新提供了相应的报头,我们按照分析条件获取的方式来分析条件更新在HTTP请求/回复过程中的实现。客户端第一次向服务端发起针对某个资源的请求,服务端除了将资源数据作为回复消息主体返回之外,会将与资源关联并且能够可以用于对其进行对等性判断的某个值作为回复的ETag报头,这与条件获取时一致的。

客户端通过回复获得请求的资源和ETag报头值。对于资源修改操作,客户端直接针对获取的资源进行相应的修改,并将修改后的资源以HTTP请求的方式向服务端提交;对于资源删除操作,则可以指定被删除资源的唯一标识直接向服务端发送删除的请求。而之前获取的ETag指将会作为请求消息的If-Match报头。

服务端接收到资源修改/删除请求后先获取到现有的资源的ETag值,并将此值与请求消息的If-Match报头值进行比较。如果两者不一致,则表明试图被修改/删除的资源已经被修改了,在这种情况下会直接回复一个HTTP状态为“412
(Precondition Failed)”的空消息。条件更新同时支持针对PUT、POST和DELETE这三种方法的HTTP请求。

二、WebOperationContext与条件更新

服务端进行条件更新检测,以及客户端对If-Match请求报头的设置都可以通过当前的WebOperationContext来完成。如下面的代码片断所示,表示入栈请求上下文的IncomingWebRequestContext类型具有如下四个CheckConditionalUpdate方法重载用于进行添加更新检测。

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

实现在CheckConditionalUpdate方法中的条件更新检测具有这样的逻辑:对于HTTP方法为PUT的请求,如果If-Match报头值不为“*”,则直接抛出HTTP状态为PreconditionFailed的WebFaultException异常;对于HTTP方法为POST和DELETE的请求来说,如果If-Match报头值为“*”或者包含指定的entityTag则验证通过,否则同样则直接抛出HTTP状态为PreconditionFailed的WebFaultException异常。

表示出栈请求上下文的OutgoingWebRequestContext类型具有如下一个IfMatch属性,客户端可以通过该属性对请求消息的If-Match报头进行设置。

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

三、实例演示:通过条件更新解决对相同资源的并发修改

我们同样通过对EmployeesService进行相应的改造来模拟如何通过添加更新实现对相同资源的并发操作问题,这次我们修改的是用于获取指定ID员工信息的Get操作和用于修改员工信息的Update操作。Get操作在返回与指定员工ID匹配的Employee对象之前我们将该对象的哈希码作为了回复消息的ETag报头(Employee类型重写了GetHashCode方法)。

   1: public class EmployeesService : IEmployees
   2: {
   3:     //其他成员
   4:     public Employee Get(string id)
   5:     {
   6:         Employee employee = employees.FirstOrDefault(e => e.Id == id);
   7:         if (null == employee)
   8:         {
   9:             throw new WebFaultException(HttpStatusCode.NotFound);
  10:         }
  11:         WebOperationContext.Current.OutgoingResponse.SetETag(employee.GetHashCode());
  12:         return employee;
  13:     }
  14:     public void Update(Employee employee)
  15:     {
  16:         var existing = employees.FirstOrDefault(e => e.Id == employee.Id);
  17:         if (null == existing)
  18:         {
  19:             throw new WebFaultException(HttpStatusCode.NotFound);
  20:         }
  21:         //模拟并发修改
  22:         existing.Name += Guid.NewGuid().ToString();
  23:  
  24:         WebOperationContext.Current.IncomingRequest.CheckConditionalUpdate(existing.GetHashCode());
  25:         employees.Remove(existing);            
  26:         employees.Add(employee);
  27:         WebOperationContext.Current.OutgoingResponse.SetETag(employee.GetHashCode());
  28:  
  29:     }
  30: }

Update方法中我们通过手工修改相应员工的Name属性的方式来模拟针对相同员工信息的并发修改。在真正实施修改之前调用当前IncomingWebRequestContext的CheckConditionalUpdate方法进行条件更新检测,而作为参数传入的ETag值为代表目前员工的Employee对象的哈希码。方法的最后我们对回复消息的ETag报头作了更新。

我们通过手工创建HTTP请求的方式对上述的两个服务操作进行调用。如下面的代码片断所示,我们首先通过创建的HttpWebRequest对象调用Get操作获得ID为001的员工信息并将其打印出来。然后创建调用Update操作的HttpWebRequest,并对HTTP方法(POST)和内容类型(application/xml)进行了相应的设置。我们之前针对员工获取请求得到ETag报头和员工数据作为本次请求的If-Match报头和主体。如果调用GetResponse方法抛出WebException异常,并且其回复状态为PreconditionFailed,则表明试图修改的员工信息已被另一个用户修改过了,所以我么打印“服务端数据已发生变化”字样。

   1: Uri address = new Uri("http://127.0.0.1:3721/employees/001");
   2: var request = (HttpWebRequest)HttpWebRequest.Create(address);
   3: request.Method = "GET";
   4: var response = (HttpWebResponse)request.GetResponse();
   5: string employee;
   6: using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
   7: {
   8:     employee = reader.ReadToEnd();
   9:     Console.WriteLine("获取员工信息:");
  10:     Console.WriteLine(employee + "\n");
  11: }
  12: try
  13: {
  14:     address = new Uri("http://127.0.0.1:3721/employees/");
  15:     request = (HttpWebRequest)HttpWebRequest.Create(address);
  16:     request.Method = "POST";
  17:     request.ContentType = "application/xml";
  18:     byte[] buffer = Encoding.UTF8.GetBytes(employee);
  19:     request.GetRequestStream().Write(Encoding.UTF8.GetBytes(employee), 0, buffer.Length);
  20:     request.Headers.Add(HttpRequestHeader.IfMatch, response.Headers[HttpResponseHeader.ETag]);
  21:     Console.WriteLine("修改员工信息:");
  22:     request.GetResponse();
  23: }
  24: catch (WebException ex)
  25: {
  26:     response = ex.Response as HttpWebResponse;
  27:     if (null == response)
  28:     {
  29:         throw;
  30:     }
  31:     if (response.StatusCode == HttpStatusCode.PreconditionFailed)
  32:     {
  33:         Console.WriteLine("服务端数据已发生变化");
  34:     }
  35:     else
  36:     {
  37:         throw;
  38:     }
  39: }

在服务成功寄宿的情况下调用这段程序会在控制台上输出如下的结果。由于并发错误的发生,员工信息其实并没有被真正修改。

   1: 获取员工信息:
   2: <Employee xmlns="http://www.artech.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Department>开发部</Department><Grade>G7</Grade><Id>001</Id><Name>张三</Name></Employee>
   3:  
   4: 修改员工信息:
   5: 服务端数据已发生变化

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

原文链接

时间: 2024-12-06 04:30:31

[WCF REST] 解决资源并发修改的一个有效的手段:条件更新(Conditional Update)的相关文章

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

条件获取(Conditional Retrieval)旨在解决这样的问题:客户端获取某个资源并对其进行缓存,当再次获取相同资源时,如果资源数据与之前获取的一致,则不再返回真正的资源数据,而是在回复中设置一个"标识"表明获取的资源并未发生改变.[源代码从这里下载] 一. HTTP对条件获取的支持 HTTP对条件获取提供了原生的支持.具体的实现是这样的:服务端接收到客户端针对某个资源的第一次获取请求时,除了将资源数据作为HTTP回复主体返回之外,还会设置一个叫做ETag的回复报头.这个ET

每一个程序员都应该知道的高并发处理技巧、创业公司如何解决高并发问题、互联网高并发问题解决思路、caoz大神多年经验总结分享

原文:每一个程序员都应该知道的高并发处理技巧.创业公司如何解决高并发问题.互联网高并发问题解决思路.caoz大神多年经验总结分享 本文来源于caoz梦呓公众号高并发专辑,以图形化.松耦合的方式,对互联网高并发问题做了详细解读与分析,"技术在短期内被高估,而在长期中又被低估",而不同的场景和人员成本又导致了巨头的方案可能并不适合创业公司,那么如何保证高并发问题不成为创业路上的拦路虎,是每一个全栈工程师.资深系统工程师.有理想的程序员必备的技能,希望本文助您寻找属于自己的"成金之

WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇]

在Part I 中,我们创建了一个InterceptService,并且通过一个特殊的EndpointBehavior,ClientViaBehavior实现了message的拦截.转发功能.在本节中,我们将讨论另外一种不同的实现方式.如何说ClientViaBehavior是基于Client端的实现方式,那么我们今天讨论的是基于Service的实现方式. 在对新的实现方式展开介绍之前,我们先来介绍一下关于逻辑地址和物理地址. 一.逻辑地址和物理地址 我们知道,WCF通过Endpoint进行通信

如何解决高并发的抢购问题

问题描述 如何解决高并发的抢购问题 前几天去南京付融宝面试,提了这样一个问题:在某天的上午10点有这样一个抢购活动,抢购的商品数量1000,初步估计那个时间点抢购的人数在100万左右,如何处理这样的一个问题. 解决方案 首先,你要有足够的服务器,保持前端页面能够正常调用.你可以用内容分发网络(cdn)使得前端应用层可以工作.然后,你可以产生一个随机数,以大约0.01的概率从前端服务器将订购请求发送到你的业务层,其余直接返回售罄.此时你的业务层已经只有1万的并发了,用事务队列保证抢购和存货的匹配.

restlet-在基于spring的REST服务 中如何让每一个资源类中的 每个资源方法都有一个URL呢?

问题描述 在基于spring的REST服务 中如何让每一个资源类中的 每个资源方法都有一个URL呢? 如 在这一篇 博文中 http://my.oschina.net/jiyayun/blog/146446 每一个资源类中都只有一个 方法可用.如果我在一个资源类中有多个 资源方法应该怎么弄呢?求大神帮忙,谢谢了. 解决方案 因为每个类对应一个url如果是每个方法,那么就冲突了.你可以用代码生成器产生代理类,每个类对应一个原始类的方法来解决这个问题.

文字直播专题列表如何解决大并发问题

问题描述 文字直播专题列表如何解决大并发问题 今天想在CSDN与各位探讨一个弹窗大并发的问题.应用表现:图片.文字直播类新闻列表,编辑实时发布最新内容,前端页面最新的内容在上面,老的在下面 并发场景:因为是新闻类直播,遇到突发性事件(如马航失联.亚航失联),需要对重大新闻进行消息弹窗,推送的用户量级别是3000W左右. 问题:如何解决这种短事件内人大并发问题,平台架构方面如何搭建? 缓存机制可否优化?TIPS:服务器有22台,程序已经做了缓存加速. 抱歉,账号下没有金币了,希望各位技术大牛能一起

无法找到资源。 说明: HTTP 404。您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用。请检查以下 URL 并确保其拼写正确

问题描述 法找到资源.说明:HTTP404.您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用.请检查以下URL并确保其拼写正确.请求的URL:/Aquaculture/department/Purchase.aspx请高手们帮忙看看,是哪写错了,这是登录的后台,我想实现选择不同部门,跳转到不同页面,然后就一直报上面那个错误代码如下:usingSystem;usingSystem.Collections;usingSystem.Configuration;using

C#解决SQlite并发异常问题的方法(使用读写锁)_C#教程

本文实例讲述了C#解决SQlite并发异常问题的方法.分享给大家供大家参考,具体如下: 使用C#访问sqlite时,常会遇到多线程并发导致SQLITE数据库损坏的问题. SQLite是文件级别的数据库,其锁也是文件级别的:多个线程可以同时读,但是同时只能有一个线程写.Android提供了SqliteOpenHelper类,加入Java的锁机制以便调用.但在C#中未提供类似功能. 作者利用读写锁(ReaderWriterLock),达到了多线程安全访问的目标. using System; usin

android-怎么在一个activity 中修改另 一个activity 的ui?

问题描述 怎么在一个activity 中修改另 一个activity 的ui? 如题,怎么在一个activity 中修改另 一个activity 的ui? 怎么在一个activity 中修改另 一个activity 的ui? 解决方案 http://www.cnblogs.com/ycxyyzw/p/3875544.html 解决方案二: 能详细点吗..... 解决方案三: 不知道你说的是不是这样 100为随意 唯一final int值 A中点击某个地方 Intent intent = new