[WCF REST] 解决资源并发修改的一个有效的手段:条件更新(Conditional Update)
2012-02-14 08:41
911 查看
条件获取(Conditional Update)可以避免相同数据的重复传输,进而提高性能。条件更新(Conditional Update)用于解决资源并发操作问题。如果我们预先获取一个资源进行修改或者删除,条件更新检验帮助我们确认资源被获取出来到针对它的修改/删除操作被提交的这段时间内是否被其他人改动过。[源代码从这里下载]
客户端通过回复获得请求的资源和ETag报头值。对于资源修改操作,客户端直接针对获取的资源进行相应的修改,并将修改后的资源以HTTP请求的方式向服务端提交;对于资源删除操作,则可以指定被删除资源的唯一标识直接向服务端发送删除的请求。而之前获取的ETag指将会作为请求消息的If-Match报头。
服务端接收到资源修改/删除请求后先获取到现有的资源的ETag值,并将此值与请求消息的If-Match报头值进行比较。如果两者不一致,则表明试图被修改/删除的资源已经被修改了,在这种情况下会直接回复一个HTTP状态为“412 (Precondition Failed)”的空消息。条件更新同时支持针对PUT、POST和DELETE这三种方法的HTTP请求。
[/code]
实现在CheckConditionalUpdate方法中的条件更新检测具有这样的逻辑:对于HTTP方法为PUT的请求,如果If-Match报头值不为“*”,则直接抛出HTTP状态为PreconditionFailed的WebFaultException异常;对于HTTP方法为POST和DELETE的请求来说,如果If-Match报头值为“*”或者包含指定的entityTag则验证通过,否则同样则直接抛出HTTP状态为PreconditionFailed的WebFaultException异常。
表示出栈请求上下文的OutgoingWebRequestContext类型具有如下一个IfMatch属性,客户端可以通过该属性对请求消息的If-Match报头进行设置。
[/code]
[/code]
Update方法中我们通过手工修改相应员工的Name属性的方式来模拟针对相同员工信息的并发修改。在真正实施修改之前调用当前IncomingWebRequestContext的CheckConditionalUpdate方法进行条件更新检测,而作为参数传入的ETag值为代表目前员工的Employee对象的哈希码。方法的最后我们对回复消息的ETag报头作了更新。
我们通过手工创建HTTP请求的方式对上述的两个服务操作进行调用。如下面的代码片断所示,我们首先通过创建的HttpWebRequest对象调用Get操作获得ID为001的员工信息并将其打印出来。然后创建调用Update操作的HttpWebRequest,并对HTTP方法(POST)和内容类型(application/xml)进行了相应的设置。我们之前针对员工获取请求得到ETag报头和员工数据作为本次请求的If-Match报头和主体。如果调用GetResponse方法抛出WebException异常,并且其回复状态为PreconditionFailed,则表明试图修改的员工信息已被另一个用户修改过了,所以我么打印“服务端数据已发生变化”字样。
[/code]
在服务成功寄宿的情况下调用这段程序会在控制台上输出如下的结果。由于并发错误的发生,员工信息其实并没有被真正修改。
[/code]
一、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方法重载用于进行添加更新检测。[code] public class IncomingWebRequestContext { //其他成员 public void CheckConditionalUpdate(Guid entityTag); public void CheckConditionalUpdate(int entityTag); public void CheckConditionalUpdate(long entityTag); public void CheckConditionalUpdate(string entityTag); }
[/code]
实现在CheckConditionalUpdate方法中的条件更新检测具有这样的逻辑:对于HTTP方法为PUT的请求,如果If-Match报头值不为“*”,则直接抛出HTTP状态为PreconditionFailed的WebFaultException异常;对于HTTP方法为POST和DELETE的请求来说,如果If-Match报头值为“*”或者包含指定的entityTag则验证通过,否则同样则直接抛出HTTP状态为PreconditionFailed的WebFaultException异常。
表示出栈请求上下文的OutgoingWebRequestContext类型具有如下一个IfMatch属性,客户端可以通过该属性对请求消息的If-Match报头进行设置。
[code] public class OutgoingWebRequestContext { //其他成员 public string IfMatch { get; set; } }
[/code]
三、实例演示:通过条件更新解决对相同资源的并发修改
我们同样通过对EmployeesService进行相应的改造来模拟如何通过添加更新实现对相同资源的并发操作问题,这次我们修改的是用于获取指定ID员工信息的Get操作和用于修改员工信息的Update操作。Get操作在返回与指定员工ID匹配的Employee对象之前我们将该对象的哈希码作为了回复消息的ETag报头(Employee类型重写了GetHashCode方法)。[code] public class EmployeesService : IEmployees { //其他成员 public Employee Get(string id) { Employee employee = employees.FirstOrDefault(e => e.Id == id); if (null == employee) { throw new WebFaultException(HttpStatusCode.NotFound); } WebOperationContext.Current.OutgoingResponse.SetETag(employee.GetHashCode()); return employee; } public void Update(Employee employee) { var existing = employees.FirstOrDefault(e => e.Id == employee.Id); if (null == existing) { throw new WebFaultException(HttpStatusCode.NotFound); } //模拟并发修改 existing.Name += Guid.NewGuid().ToString(); WebOperationContext.Current.IncomingRequest.CheckConditionalUpdate(existing.GetHashCode()); employees.Remove(existing); employees.Add(employee); WebOperationContext.Current.OutgoingResponse.SetETag(employee.GetHashCode()); } }
[/code]
Update方法中我们通过手工修改相应员工的Name属性的方式来模拟针对相同员工信息的并发修改。在真正实施修改之前调用当前IncomingWebRequestContext的CheckConditionalUpdate方法进行条件更新检测,而作为参数传入的ETag值为代表目前员工的Employee对象的哈希码。方法的最后我们对回复消息的ETag报头作了更新。
我们通过手工创建HTTP请求的方式对上述的两个服务操作进行调用。如下面的代码片断所示,我们首先通过创建的HttpWebRequest对象调用Get操作获得ID为001的员工信息并将其打印出来。然后创建调用Update操作的HttpWebRequest,并对HTTP方法(POST)和内容类型(application/xml)进行了相应的设置。我们之前针对员工获取请求得到ETag报头和员工数据作为本次请求的If-Match报头和主体。如果调用GetResponse方法抛出WebException异常,并且其回复状态为PreconditionFailed,则表明试图修改的员工信息已被另一个用户修改过了,所以我么打印“服务端数据已发生变化”字样。
[code] Uri address = new Uri("http://127.0.0.1:3721/employees/001"); var request = (HttpWebRequest)HttpWebRequest.Create(address); request.Method = "GET"; var response = (HttpWebResponse)request.GetResponse(); string employee; using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) { employee = reader.ReadToEnd(); Console.WriteLine("获取员工信息:"); Console.WriteLine(employee + "\n"); } try { address = new Uri("http://127.0.0.1:3721/employees/"); request = (HttpWebRequest)HttpWebRequest.Create(address); request.Method = "POST"; request.ContentType = "application/xml"; byte[] buffer = Encoding.UTF8.GetBytes(employee); request.GetRequestStream().Write(Encoding.UTF8.GetBytes(employee), 0, buffer.Length); request.Headers.Add(HttpRequestHeader.IfMatch, response.Headers[HttpResponseHeader.ETag]); Console.WriteLine("修改员工信息:"); request.GetResponse(); } catch (WebException ex) { response = ex.Response as HttpWebResponse; if (null == response) { throw; } if (response.StatusCode == HttpStatusCode.PreconditionFailed) { Console.WriteLine("服务端数据已发生变化"); } else { throw; } }
[/code]
在服务成功寄宿的情况下调用这段程序会在控制台上输出如下的结果。由于并发错误的发生,员工信息其实并没有被真正修改。
[code] 获取员工信息: <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> 修改员工信息: 服务端数据已发生变化
[/code]
相关文章推荐
- [WCF REST] 解决资源并发修改的一个有效的手段:条件更新(Conditional Update)
- [WCF REST] 提高性能的一个有效的手段:条件资源获取(Conditional Retrieval)
- [WCF REST] 提高性能的一个有效的手段:条件资源获取(Conditional Retrieval)
- [WCF REST] 提高性能的一个有效的手段:条件资源获取(Conditional Retrieval)
- c#system.InvalidOperation 当传递已修改行的datarow集合时,更新要求有效的 UpdateCommand。
- b/s软件中使用Access数据库,只能查询,不能添加、修改、删除,提示操作必须是一个可更新的查询解决。
- mysql性能优化(五) mysql中SELECT+UPDATE处理并发更新问题解决方案
- 当传递具有已修改行的 DataRow 集合时,更新要求有效的 UpdateCommand问题解决
- 如何解决多线程并发访问一个资源的安全性问题?
- 关于sql server的纪录修改冲突解决和oracle的for update 的并发锁定测试
- datagridview当传递具有已修改行的 DataRow 集合时,更新要求有效的 UpdateCommand。
- 关于ADO.Net使用TableAdapter时产生:更新要求有效的deletecommand或update,insert,selectcommand的解决办法,以及“违反并发性”处理
- 附5、MDT 2013 Update 1批量部署-更新版无法修改共享文件夹参数的解决办法
- MySQL中SELECT+UPDATE处理并发更新问题解决方案分享
- MySQL中SELECT+UPDATE处理并发更新问题解决方案分享
- 网站后台登录aspcms 提示错误号:-2147467259,错误描述:操作必须使用一个可更新的查询。sql=update AspCms_Content set TimeStatus=0 where TimeStatus=1 and Timeing <= 解决方法。
- Transact-SQL 示例 - 一个UPDATE实现多个数据列的条件更新
- MySQL中SELECT+UPDATE处理并发更新问题解决方案分享
- cocos2d-x-3.12 打包android apk时 怎么修改已经配置好的setup.py参数 和解决 XX不是一个有效的 Android 目标平台 错误
- 当传递具有已修改行的 DataRow 集合时,更新要求有效的 UpdateCommand问题解决