Sagas模式
Sagas属于一个错误管理模式,也同时用于控制复杂事务的执行和回滚等。同时,Compensating-Transaction模式的的实现也是也是类似于Sagas策略的,可以对比参考一下。
Sagas的最开始的出现是因为一些长时间的事务的实现(最开始的时候仅仅是因为数据内的事务),现在也包括一些跨越多个区域的分布式事务。这些长时间持续的事务无法简单地通过一些典型的ACID模型使用多段提交配合持有锁的方式来实现。Sagas策略正式用来解决这个问题,和多段式处理不同,Sagas会将工作分成单独的事务,包含正常的操作和回滚的操作。
如下图:
上图展示了一个简单的Saga。如果有旅客预订了行程,需要预订汽车,航班以及旅店。如果无法获取全部的信息,可能最好就是不要出发。对开发者来说,肯定无法将所有的服务都定义为分布式的ACID事务。这时可以将租车行为定义为一个整体,其中包含如何去预订以及如何取消,当然,机票和酒店也提供同样的服务。
然后这些行为可以组合在一起构成了一个行为链。开发者还可以将整个行为链加密,这样只有该行为链的接收者才能够操控这个行为链。当一个行为完成后,会将完成的信息记录到一个集合(比如说,是一个队列)中,之后可以通过这个集合访问到对应的行为。当一个行为失败的实收,行为将本地清理完毕,然后将消息发送给该集合,从而路由到之前执行成功的行为,然后回滚所有的事务。
如果开发者对于旅行行程了解一些的话,就会知道上面的行为链其实是有风险的。一般来说,提前预订租车服务几乎都会成功,因为租车公司都会有足够的时间来帮助你安排车辆。但是预订宾馆就有一些风险了,一般无押金的情况下,只能提前24小时预订,而航班的退改一般情况下还要收费的,所以最后预订是对的。
使用举例
下面的程序作为样例可以帮助我们更好的了解Sagas策略
程序会生成一个典型的集合用来访问对应的行为链中的行为,会创建3个独立的进程,每一个进程都会负责一个指定的任务。分别是租车,预订酒店以及预订机票三个独立的任务。
static ActivityHost[] processes;
static void Main(string[] args)
{
var routingSlip = new RoutingSlip(new WorkItem[]
{
new WorkItem<ReserveCarActivity>(new WorkItemArguments),
new WorkItem<ReserveHotelActivity>(new WorkItemArguments),
new WorkItem<ReserveFlightActivity>(new WorkItemArguments)
});
// imagine these being completely separate processes with queues between them
processes = new ActivityHost[]
{
new ActivityHost<ReserveCarActivity>(Send),
new ActivityHost<ReserveHotelActivity>(Send),
new ActivityHost<ReserveFlightActivity>(Send)
};
// hand off to the first address
Send(routingSlip.ProgressUri, routingSlip);
}
static void Send(Uri uri, RoutingSlip routingSlip)
{
// this is effectively the network dispatch
foreach (var process in processes)
{
if (process.AcceptMessage(uri, routingSlip))
{
break;
}
}
}
其中的AcitivityHost
就是对外部服务的一个抽象,RoutingSlip
是对前面说的集合的抽象。
下面是具体的一个服务的简化实现,ReserveHotelActivity
以及ReserveFlightActivity
的实现就不在此处列出了,下面是ReserveCarActivity
的实现。其中主要包括的几个方法:
DoWork
以及Compensate
方法是Activity抽象出来的用来执行实际操作以及回滚的补偿方法。
WorkItemQueueAddress
以及CompensationQueueAddress
都是用来索引到对应服务的。参考如下代码:
class ReserveCarActivity : Activity
{
static Random rnd = new Random(2);
public override WorkLog DoWork(WorkItem workItem)
{
Console.WriteLine("Reserving car");
var car = workItem.Arguments["vehicleType"];
var reservationId = rnd.Next(100000);
Console.WriteLine("Reserved car {0}", reservationId);
return new WorkLog(this, new WorkResult
{
{ "reservationId", reservationId }
});
}
public override bool Compensate(WorkLog item, RoutingSlip routingSlip)
{
var reservationId = item.Result["reservationId"];
Console.WriteLine("Cancelled car {0}", reservationId);
return true;
}
public override Uri WorkItemQueueAddress
{
get { return new Uri("sb://./carReservations"); }
}
public override Uri CompensationQueueAddress
{
get { return new Uri("sb://./carCancellactions"); }
}
}
RoutingSlip
是对成功行为集合的抽象,用来索引到对应的服务,包含了两个队列,一个是完成的任务,一个是等待执行的任务。RoutingSlip
主要用来控制连接多个行为。如果成功就会将任务向前执行,如果失败就会向后执行
。RoutingSlip
使用队列来向前执行,使用栈来向后执行。
class RoutingSlip
{
readonly Stack<WorkLog> completedWorkLogs = new Stack<WorkLog>();
readonly Queue<WorkItem> nextWorkItem = new Queue<WorkItem>();
public RoutingSlip()
{
}
public RoutingSlip(IEnumerable<WorkItem> workItems)
{
foreach (var workItem in workItems)
{
this.nextWorkItem.Enqueue(workItem);
}
}
public bool IsCompleted
{
get { return this.nextWorkItem.Count == 0; }
}
public bool IsInProgress
{
get { return this.completedWorkLogs.Count > 0; }
}
public bool ProcessNext()
{
if (this.IsCompleted)
{
throw new InvalidOperationException();
}
var currentItem = this.nextWorkItem.Dequeue();
var activity = (Activity) Activator.CreateInstance(
currentItem.ActivityType);
try
{
var result = activity.DoWork(currentItem);
if (result != null)
{
this.completedWorkLogs.Push(result);
return true;
}
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
}
return false;
}
public Uri ProgressUri
{
get
{
if (IsCompleted)
{
return null;
}
else
{
return
((Activity)Activator.CreateInstance(this.nextWorkItem.Peek().ActivityType)).
WorkItemQueueAddress;
}
}
}
public Uri CompensationUri
{
get
{
if (!IsInProgress)
{
return null;
}
else
{
return ((Activity) Activator.CreateInstance(
this.completedWorkLogs.Peek().ActivityType)).
CompensationQueueAddress;
}
}
}
public bool UndoLast()
{
if (!this.IsInProgress)
{
throw new InvalidOperationException();
}
var currentItem = this.completedWorkLogs.Pop();
var activity = (Activity) Activator.CreateInstance(
currentItem.ActivityType);
try
{
return activity.Compensate(currentItem, this);
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
}
}
ActivityHost
会调用RoutingSlip
上面的ProcessNext
方法来解析下一个行为并正向调用DoWork()
或者反向调用Compensate()
方法.
abstract class ActivityHost
{
Action<Uri, RoutingSlip> send;
public ActivityHost(Action<Uri, RoutingSlip> send)
{
this.send = send;
}
public void ProcessForwardMessage(RoutingSlip routingSlip)
{
if (!routingSlip.IsCompleted)
{
// if the current step is successful, proceed
// otherwise go to the Unwind path
if (routingSlip.ProcessNext())
{
// recursion stands for passing context via message
// the routing slip can be fully serialized and passed
// between systems.
this.send(routingSlip.ProgressUri, routingSlip);
}
else
{
// pass message to unwind message route
this.send(routingSlip.CompensationUri, routingSlip);
}
}
}
public void ProcessBackwardMessage(RoutingSlip routingSlip)
{
if (routingSlip.IsInProgress)
{
// UndoLast can put new work on the routing slip
// and return false to go back on the forward
// path
if (routingSlip.UndoLast())
{
// recursion stands for passing context via message
// the routing slip can be fully serialized and passed
// between systems
this.send(routingSlip.CompensationUri, routingSlip);
}
else
{
this.send(routingSlip.ProgressUri, routingSlip);
}
}
}
public abstract bool AcceptMessage(Uri uri, RoutingSlip routingSlip);
}
相关的其他模式
- Compensating-Transaction模式。Compensating-Transaction通常被用来回滚需要实现最终一致性模型的功能,其中针对于数据一致性以及补偿失败的考虑更为详细。
- Data Consistency Primer。Data Consistency Primer中的内容提供了更多关于最终一致性的特性的说明。
Sagas模式的更多相关文章
- Compensating-Transaction模式
在应用中,会将一系列相关的操作定义为一个连续的操作,当其中一个或者多个步骤失败的时候,Compensating-Transaction模式会重置(回滚)这个连续的操作.在云应用中,这些需要保证一致性的 ...
- Compensating Transaction Pattern(事务修正模式)
Undo the work performed by a series of steps, which together define an eventually consistent operati ...
- saga中的saga(A Saga on Sagas)
此文翻译自msdn,侵删. 原文地址:https://msdn.microsoft.com/en-us/library/jj591569.aspx Process Managers, Coordina ...
- 让你的saga更具有可伸缩性(Scaling NServiceBus Sagas)
https://lostechies.com/jimmybogard/2013/03/26/scaling-nservicebus-sagas/ 当我们使用NServiceBus sagas (pro ...
- Saga的实现模式——进化(Saga implementation patterns – variations)
在之前的几个博客中,我主要讲了两个saga的实现模式: 基于command的控制者模式 基于事件的观察者模式 当然,这些都不是实现saga的唯一方式.我们甚至可以将这些结合起来. 发布者——收集者 回 ...
- Saga的实现模式——观察者(Saga implementation patterns – Observer)
https://lostechies.com/jimmybogard/2013/03/11/saga-implementation-patterns-observer/ 侵删. NServiceBus ...
- 分布式事务:Saga模式
1 Saga相关概念 1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem发表了一篇Paper Sagas,讲述的是如何处理long lived transac ...
- 【原】谈谈对Objective-C中代理模式的误解
[原]谈谈对Objective-C中代理模式的误解 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这篇文章主要是对代理模式和委托模式进行了对比,个人认为Objective ...
- 彻底理解AC多模式匹配算法
(本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...
随机推荐
- ionic开发之Android的focus起作用,而iOS不起作用
基于ionic的iOS的hybird APP无法使用focus获取焦点和键盘的问题. 解决办法就是: 原本APP的配置文件config.xml里面默认有一句 这句话的大概意思就是键盘的显示需要用户去触 ...
- springcloud 入门 4 (rebbon源码解读)
Ribbon: ribbon是springcloud个服务之间调用的一种实现方式:主要提供了客户端的负载均衡.feign集成了Ribbon(下节介绍) 源码分析: 1> 源码入口 @LoadBa ...
- Hibernate 拦截器
拦截器 你已经学到,在 Hibernate 中,一个对象将被创建和保持.一旦对象已经被修改,它必须被保存到数据库里.这个过程持续直到下一次对象被需要,它将被从持久的存储中加载. 因此一个对象通过它生命 ...
- HTTP 错误 401.3 - Unauthorized asp.net mvc 图片,css,js没有权限访问
一.在服务器上发布了一个asp.net的网站,结果是页面可以显示,但是css,js,images无法访问,报错是没有权限,HTTP 错误 401.3 - Unauthorized 二.根据以往的经验, ...
- 关于 Azure Windows VM 的磁盘和 VHD
就像其他任何计算机一样,Azure 中的虚拟机将磁盘用作存储操作系统.应用程序和数据的位置. 所有 Azure 虚拟机都至少有两个磁盘,即 Windows 操作系统磁盘和临时磁盘. 操作系统磁盘基于映 ...
- SQL Server 的 RowGuid/RowGuidCol 是什么意思?
SQL Server 中设计表时,有个属性叫 RowGuid(用 SQL 时,关键词是 RowGuidCol),这个是什么意思呢?这个是全局唯一标识符,与“标识”不同,标识是只在本表中唯一,全局唯一标 ...
- python之demo1----改编自turtle.py文件中的demo
""" 改编自turtle.py自带demo 执行 python -m turtledemo 命令查看系统内置demo的源码 绘制:需要通过import turtle引入 ...
- MySQL索引设计不可忽视的知识点
本文主要讨论MySQL索引的部分知识.将会从MySQL索引基础.索引优化实战和数据库索引背后的数据结构三部分相关内容,下面一一展开. 一.MySQL——索引基础 首先,我们将从索引基础开始介绍一下什么 ...
- linux访问Windows共享文件命令
mount -o username=username,password=password,ip=10.0.0.1 //10.0.0.1/backupscm /home/package/image_vm ...
- RecyclerView使用技巧(item动画及嵌套高度适配解决方案)
原文地址 · Frank-Zhu http://frank-zhu.github.io/android/2015/02/26/android-recyclerview-part-3/?utm_sou ...