博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[WCF REST] 解决资源并发修改的一个有效的手段:条件更新(Conditional Update)
阅读量:6978 次
发布时间:2019-06-27

本文共 5124 字,大约阅读时间需要 17 分钟。

可以避免相同数据的重复传输,进而提高性能。条件更新(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: 
开发部
G7
001
张三
3: 
4: 修改员工信息:
5: 服务端数据已发生变化
作者:蒋金楠
微信公众账号:大内老A
微博:
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号
蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
你可能感兴趣的文章
Mysql采坑只utf8
查看>>
Pdf Convert Image 的解决方案
查看>>
做一个项目时遇到中文乱码,于是在入口文件加了个header("Content-type:text/html;charset=utf-8");结果一刷新网页就自动下载本网页文件;...
查看>>
Python中的字符串与字符编码
查看>>
Python3之logging模块浅析
查看>>
四大组件之内容提供者
查看>>
第一章:Nginx安装
查看>>
【CentOS7】LINUX下面桌面的安装
查看>>
jquery(ajax)+ashx简单开发框架(原创)
查看>>
Georgia Tech- 新的篇章
查看>>
Maven依赖中的scope详解
查看>>
利用Message Crackers简化消息映射
查看>>
SQL中,将一列中的多条数据连接在一起
查看>>
PYTHON-mysql fetchall和 fetchone之间的区别
查看>>
kibana做图表无法选取需要选的字段
查看>>
linux学习之centos(三):网卡配置
查看>>
面经中高频知识点归纳(二)
查看>>
setTimeout()和setInterval() 何时被调用执行
查看>>
5、新干线大爆炸事件
查看>>
11-vue的使用
查看>>