事务核对方式,云设计形式

Undo the work performed by a series of steps, which together define an
eventually consistent operation, if one or more of the steps fail.
Operations that follow the eventual consistency model are commonly found
in cloud-hosted applications that implement complex business processes
and workflows.

Volatile Resource Managers in .NET Bring Transactions to the Common Type

Juval Lowy

This article discusses:

  • Overview of transactional processing
  • Transactions in the .NET Framework
  • Volatile Resource Managers
  • Transactional classes and collections
This article uses the following technologies: 
.NET Framework 2.0, Visual C# 2.0

 

Code download available
at: Transactions.exe (148 KB) 
Browse the Code
Online
 

 Contents

Transaction Problem
Space
 
Transaction Properties:
ACID
 
Transactions in .NET Framework
2.0
 
Volatile Resource
Managers
 
Transaction-Based
Locks 

Serialization and
Cloning 

Transactional<T>
Walkthrough
 
Transactional
Collections
 
Conclusion 

Transactional programming has traditionally been restricted to
database-centric applications. Other types of applications did not see
the same benefits from this superior programming model. The
Microsoft®.NET Framework 2.0 introduces
rudimentary support for volatile resource managers, enabling you to
enlist transaction support against memory-based resources, such as class
member variables. I will begin this article by briefly discussing the
problem space that transactions address and the motivation for using
transactions, as well as defining some basic terms such as “ACID.” I
will then introduce the concept of a resource manager, and explain how
and to what extent volatile resource managers are supported in the .NET
Framework 2.0.

Next, I’ll present the required building blocks for properly utilizing
volatile resources, and explain how these building blocks can be used to
wrap existing types, such as the collections from
System.Collections.Generic. Beyond making extensive use of Visual
C# ®2.0, the article walks through
some advanced .NET Framework 2.0 programming techniques, including type
constraining, type cloning, transaction events, and resource enlistment.

 

Transaction Problem Space

Proper error handling and recovery are the most vulnerable points of
many applications. Once an application fails to perform a particular
operation, it should recover and restore the system to a consistent
state—usually the state the system was in before the operation that
caused the error took place. Typically, any operation that can fail is
comprised of multiple smaller steps. Some of those steps can fail after
others have succeeded.

The problem with recovery is the sheer number of partial success and
partial failure permutations that you have to code against. Trying to
handcraft recovery code in a decent-size application is often a futile
attempt, resulting in fragile code that is susceptible to any change in
the application execution or the business use case, incurring both
productivity and performance penalties. In addition, because recovery is
all about restoring the system to a consistent state (typically the
state before the failed operation), you will need to undo all the steps
of the operation that have succeeded. But how do you undo a step such as
deleting a row from a table, a node from a linked list, or an item for a
collection? Not only that, but what if before the operation failed, some
other party accessed your applications and acted upon the system state
that you are going to roll back during the recovery? That other party is
now acting on inconsistent information and, by definition, is in error.
Moreover, your operation may be just a step in some other, much wider
operation that spans multiple components from multiple vendors on
multiple machines. How would you recover the system as a whole in such a
case?

图片 1.gif)

Figure 1 Ensuring Consistency Using Transactions 

The best (and perhaps only) way of maintaining system consistency and
dealing properly with the error recovery challenge is to use
transactions. A transaction is a set of potentiality complex operations
in which the failure of any single operation causes the entire set to
fail as one atomic operation. As illustrated inFigure 1, while the
transaction is in progress, the system is allowed to be in a temporarily
inconsistent state. But once the transaction is complete, the system is
guaranteed to be in a consistent state: either a new consistent state
(State B in the diagram) or the original consistent state the system was
at before the transaction started (State A in the diagram). If the
transaction executes successfully, and manages to transfer the system
from consistent State A to consistent State B, it is called a committed
transaction. If the transaction encounters any error during execution
and rolls back all the steps that have already succeeded, it is called
an aborted transaction. If the transaction fails to either commit or
abort, it is called an in-doubt transaction— these usually require
administrator or user assistance to resolve.

Transactional programming requires working with a resource such as a
database or a message queue that is capable of participating in a
transaction, and able to commit or roll back the changes made during the
transaction. Such resources have been around in one form or another for
decades. Typically, you inform a resource that you want to perform
transactional work against it. This is called enlisting the resource in
the transaction. You then perform work against the resource, and if no
error occurs, you ask the resource to commit the changes made to its
state. If you encounter any error, you ask it to roll back the changes.
During a transaction, it is vital that you do not access any
non-transactional resources (such as the non-transactional file system),
because changes made to those resources will not roll back if the
transaction is aborted.

Since systems typically contain in-memory state, objects must also
manage their state proactively if it’s accessed during a transaction.
One way of doing this is to store all the state in a transactional
resource, such as a database, and have changes to the state commit or
roll back as part of the transaction. Consequently, even though
transactions offer a superior, robust, productivity-oriented programming
model, they are utilized today only by applications that actually do use
resources such as transactional databases. Other types of applications
generally provide lip service to recovery functionality by manually
crafting solutions for the limited set of error cases that the
developers know how to deal with, and as a result they suffer the
productivity and quality consequences.

 

Transaction Properties: ACID

The resource’s transactions must abide by four core properties: atomic,
consistent, isolated, and durable. These are known collectively as the
ACID properties.

Atomic means that when a transaction completes, all the changes it made
to the resource state must be made as if they were all one indivisible
operation. The changes made to the resource are made as if everything
else in the universe stops, the changes are made, and then everything
resumes. A transaction should not leave tasks to do in the background
once it is finished, as this would violate atomicity. Every operation
resulting from the transaction must be included in the transaction
itself.

Because transactions are atomic, a client application becomes a lot
easier to develop. The client does not have to manage partial failure of
its requests or have complex recovery logic. The client knows whether
the transaction either succeeds or fails as a whole. In case of failure,
the client can choose to issue a new request (start a new transaction),
or it can choose to do something else, such as alert the user. The
important thing is that the client does not have to recover the system.

Consistent means the transaction must leave the system in a logical
state. Note that consistency is different from atomicity. Even if all
the changes are committed as one atomic operation, the transaction is
required to guarantee that all those changes make sense within the
context of the system. Usually, it is up to the developer to ensure the
semantics of the operations are consistent. All the transaction is
required to do is to transfer the system from one consistent state to
another.

Isolated means no other entity (transactional or not) is able to see the
intermediate state of the resource during the transaction, because it
may be inconsistent (in fact, even if the resource’s state is
consistent, the transaction could still abort and the changes could be
rolled back). Isolation is crucial to overall system consistency.
Suppose transaction A allows transaction B access to its intermediate
state. Transaction A then aborts, and transaction B decides to commit.
The problem is that transaction B based its execution on system state
that was rolled back and, therefore, transaction B is left unknowingly
inconsistent.

Managing isolation is not trivial. The resources participating in a
transaction must lock the data accessed by the transaction from all
other parties, and must unlock access to that data when the transaction
commits or aborts. In theory, various degrees of transaction isolation
are possible. In general, the more isolated the transaction is, the more
consistent its results will be.

Transactions in the .NET Framework 2.0 by default use the highest degree
of isolation, called serialized. This means the results obtained from a
set of concurrent transactions are identical to the results obtained by
running each transaction serially. To achieve serialization, all the
resources a transaction touches are locked from any other transaction.
If other transactions try to access those resources, they are blocked
and cannot continue executing until the original transaction commits or
aborts.

Durable means the results of a successful transaction are persisted in
the system. At any moment, the application could crash, and the memory
it was using could be erased. If the changes to the system state were
in-memory changes, they would be lost, and the system would be in an
inconsistent state. However, even if you store the information in the
file system, while it can withstand an application crash, the changes
will be lost if the disk crashes. And while you can compensate for disk
crashes using redundant drives, it will be of no use in case of a fire
in the server room. To handle that, you might utilize multiple mirror
sites for your resources, putting them in places with no earthquakes or
floods.

As you can see, durability is really a range of options. How resilient
to such catastrophes the resource should be is an open question that
depends on the nature and sensitivity of the data, your budget,
available time and available system administration staff, and so on. If
durability is a range that actually means various degrees of
persistence, then you could also consider one of the extremes on the
spectrum: volatile, in-memory resources. The advantage of volatile
resources is that they offer better performance than durable resources,
and more importantly, as you will see in this article, they allow you to
approximate much better conventional programming models, while using
transaction support for error recovery.

 

Transactions in .NET Framework 2.0

The .NET Framework 2.0 automates the act of enlisting and managing a
transaction against transactional resources. The System.Transactions
namespace offers a common infrastructure for transaction classes, common
behaviors, and helper classes. The .NET Framework defines a resource
manager as a resource that can automatically enlist in a transaction
managed by System.Transactions. The resource has to detect that it is
being accessed by a transaction and enlist in that transaction. The
System.Transactions namespace supports a concept called an ambient
transaction. This is the transaction your code executes in (and it is
stored in thread-local storage). To obtain a reference to the ambient
transaction, you call the static Current property of the Transaction
class:

 

Transaction ambientTransaction = Transaction.Current;

If there is no ambient transaction, Current will return null. Figure
2
 outlines the most relevant portions of the Transaction class. To
perform transactional work against a resource, use the TransactionScope
class, defined as follows:

 

public class TransactionScope : IDisposable
{
   public void Complete();
   public void Dispose();
   public TransactionScope();
   ... // Additional constructors
}

As the name implies, the TransactionScope class is used to scope a code
section with a transaction:

 

using(TransactionScope scope = new TransactionScope())
{
   ... // Perform transactional work here

   // No errors - commit transaction
   scope.Complete();
}

In its constructor, the TransactionScope object creates a transaction
object and assigns it as the ambient transaction by setting the static
Current property of the Transaction class. TransactionScope is a
disposable object—the transaction will end once the Dispose method is
called (the end of the using block).

图片 2  Figure 2 Transaction
Class

 

[Serializable]
public class Transaction : IDisposable, ISerializable
{
   public event TransactionCompletedEventHandler TransactionCompleted;

   public Enlistment EnlistDurable(Guid resourceManagerIdentifier,
      IEnlistmentNotification enlistmentNotification,
      EnlistmentOptions enlistmentOptions);

   public Enlistment EnlistDurable(Guid resourceManagerIdentifier, 
      ISinglePhaseNotification singlePhaseNotification, 
      EnlistmentOptions enlistmentOptions);

   public Enlistment EnlistVolatile(
       IEnlistmentNotification enlistmentNotification,
       EnlistmentOptions enlistmentOptions);

   public Enlistment EnlistVolatile(
       ISinglePhaseNotification singlePhaseNotification,
       EnlistmentOptions enlistmentOptions);

   public void Rollback();

   public static Transaction Current{ get; set; }

   ... // Additional members
}

The TransactionScope object has no way of knowing whether the
transaction should commit or abort. To address this, every
TransactionScope object has a consistency bit, which is set to false by
default. You can set the consistency bit to true by calling the Complete
method (typically, as the last line in the scope, so that any exceptions
thrown up to that point will skip the call). If the transaction ends and
the consistency bit is set to false, the transaction will abort. For
example, the following scope object will roll back its transaction,
because the consistency bit is never changed from its default value:

 

using(TransactionScope scope = new TransactionScope())
{
}

On the other hand, if you do call Complete and the transaction ends with
the consistency bit set to true, the transaction will try to commit.

System.Transactions is nice to use because you rarely need to interact
with the transaction object directly or explicitly enlist resources, nor
must you care if you are part of a local or a distributed transaction.
By the virtue of being a System.Transactions transactional resource
manager, when it accesses the underlying durable resource, it will use
Transaction.Current to obtain a reference to the ambient transaction. It
will then call one of the EnlistDurable methods, passing an
implementation of IEnlistmentNotification, as shown in Figure 3.

图片 3  Figure 3 IEnlistmentNotification
Interface

 

public interface IEnlistmentNotification
{
   void Commit(Enlistment enlistment);
   void InDoubt(Enlistment enlistment);
   void Prepare(PreparingEnlistment preparingEnlistment);
   void Rollback(Enlistment enlistment);
}

public class Enlistment
{
   public void Done();
}

public class PreparingEnlistment : Enlistment
{
   public void ForceRollback();
   public void Prepared();
   ... // Additional members
}

The System.Transactions transaction manager uses IEnlistmentNotification
to tell the resource manager the outcome of the transaction, asking it
to commit or abort. In addition, IEnlistmentNotification is used by
System.Transactions to manage the two-phase commit protocol. If all of
the participating objects in the transaction vote to commit it, the
transaction manager calls the Prepare method on each resource:

 

void Prepare(PreparingEnlistment preparingEnlistment);

The Prepare essentially asks each resource the following question: if
you were asked to commit the changes, would you? This is the first phase
of the two-phase commit protocol. If the resource wants to abort the
transaction, it calls the ForceRollback method of the supplied
PreparingEnlistment object. If the resource is capable of committing the
changes, it calls the Prepared method.

In the second phase of the protocol, if all of the resource managers
have voted to commit the transaction, the transaction manager calls the
Commit method of the IEnlistmentNotification interface. If any one of
the resource managers have voted to abort the transaction, then the
transaction manager calls the Rollback method. This is how
System.Transactions is able to maintain both atomicity and consistency
across multiple resources.

Since the two-phase commit protocol is overkill when just a single
resource is involved, System.Transactions also offers the
ISinglePhaseNotification interface:

 

public class SinglePhaseEnlistment : Enlistment
{
   public void Aborted();
   public void Committed();
   ... // Additional methods
}

public interface ISinglePhaseNotification : IEnlistmentNotification
{
   void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment);
}

The resource manager can choose to implement ISinglePhaseNotification,
and enlist using one of the enlisting methods of Transaction that take
an ISinglePhaseNotification. In such a case, if that resource is the
single resource in the transaction, System.Transactions will only call
the SinglePhaseCommit method and avoid conducting the two-phase commit
protocol. It is then up to the resource to tell the transaction manager
the outcome of the commit attempt, using the SinglePhaseEnlistment
parameter.

 

Volatile Resource Managers

System.Transactions also enables volatile resource managers, which store
their state in memory. While their state is not durable, volatile
resource managers do often cater to a much wider set of applications
than durable resources. If your class member variables (or method local
variables) are volatile resources, you can access them from within the
transaction, thus significantly simplifying transactional programming.

As far as System.Transactions is concerned, all that a volatile resource
needs to do is implement IEnlistmentNotification, detect that it is
being accessed by a transaction, and auto-enlist in the transaction by
calling the VolatileEnlist method of the transaction object.

There are a number of hurdles in trying to implement
IEnlistmentNotification. Ideally, I would like all my data types to be
transactional. I like a transactional integer, transactional string,
transactional customer object, and so on:

 

public class TransactionalInteger : IEnlistmentNotification
{...}
public class TransactionalString  : IEnlistmentNotification
{...}
public class TransactionalCustomer : IEnlistmentNotification
{...}

But done this way, I would have to implement IEnlistmentNotification by
each type I want to enlist in a transaction. It is therefore much better
to use generics and provide a generic implementation of
IEnlistmentNotification. To accommodate that, I wrote a
Transactional<T> class:

 

public class Transactional<T> : IEnlistmentNotification
{
   public Transactional(T value);
   public Transactional();
   public T Value { get; set; }
   public static implicit operator T(Transactional<T> transactional);
   ... // More members 
}

Transactional<T> is a full-blown volatile resource manager that
automatically enlists in an ambient transaction and commits or rolls
back any changes that are made to its state according to the
transaction’s outcome.

You can use the Value property of Transactional<T> to access the
underlying type and call any method or operator it supports, as shown
in Figure 4. The values of the class member variable m_Number and
the local variable city rolled back to their state before the
transaction.

图片 4  Figure 4 Using
Transactional

 

public class MyClass
{
   Transactional<int> m_Number = new Transactional<int>(3);

   public void MyMethod()
   {
      Transactional<string> city = new Transactional<string>("New York");

      using(TransactionScope scope = new TransactionScope())
      {
         city.Value = "London";
         m_Number.Value = 4;
         m_Number.Value++;
         Debug.Assert(m_Number.Value == 5);

         //No call to scope.Complete(), transaction will abort
      }

      Debug.Assert(m_Number.Value == 3);
      Debug.Assert(city.Value == "New York");

      Debug.Assert(m_Number == 3);//Uses the == operator 
      int number = m_Number;//Uses the casting operator 
   }
}

There are a few challenging issues when implementing
Transactional<T> or any other volatile resource manager. The first
problem is isolation. The volatile resource must adhere to the isolation
property of ACID, lock the underlying resource it manages, and prevent
access by multiple transactions. However, .NET only offers thread-based
locks, which prevent access only by concurrent threads, not concurrent
transactions. The second problem is state management. The resource
manager needs to enable the transaction to modify the state of the
resource and yet must be able to roll back any such changes if the
transaction is aborted.

 

Transaction-Based Locks

Because the .NET Framework 2.0 does not offer a transaction-based lock,
I wrote one myself. I called it TransactionalLock, and gave it a simple
interface:

 

public class TransactionalLock
{
   public void Lock();
   public void Unlock();
   public bool Locked { get; }
}

TransactionalLock provides exclusive locking, thus supporting
serializable-level isolation. Only one transaction at a time is allowed
to own the lock. You acquire the lock by calling the Lock method, and
you release it by calling the Unlock method. If another transaction
tries to acquire the lock, it is blocked. Once the lock is acquired, if
the same transaction calls Lock multiple times it has no further effect.
If there are multiple pending transactions trying to acquire the lock
then they are all blocked, they are placed in a queue and allowed
ownership of the lock in order. If a transaction owns the lock,
TransactionalLock will block even non-transactional calls and place
those callers in the queue as well. If the transaction is completed
(usually aborted or timed out) while it is waiting to acquire the lock,
that transaction is unblocked.

It is important to note that TransactionalLock provides
transaction-based synchronized access, not thread-based access. The .NET
Framework 2.0 allows multiple threads to execute in the scope of the
same transaction, so multiple threads can own the lock as long as they
are in the same transaction. In addition, if one thread unlocks
TransactionalLock, it unlocks it on behalf of all other threads in the
transaction, regardless of how many times Lock was called. This is by
design, to facilitate automatic unlocking based on transaction
completion. If a multithreaded application interacts directly with a
TransactionalLock, then the threads need to coordinate among themselves
when it is permissible to call Unlock.

Figure 5 shows the implementation of TransactionalLock with some of
the code removed for conciseness.Figure 6 and Figure 7 are UML
activity diagrams depicting the Lock and Unlock methods, respectively.

图片 5  Figure 5 TransactionalLock
Class

 

public class TransactionalLock
{
   LinkedList<KeyValuePair<Transaction,ManualResetEvent>>
      m_PendingTransactions = 
        new LinkedList<KeyValuePair<Transaction,ManualResetEvent>>();

   Transaction m_OwningTransaction;

   // Property provides thread-safe access to m_OwningTransaction 
   Transaction OwningTransaction { get { ... } set { ... } } 

   public bool Locked { get { return OwningTransaction != null; } }

   public void Lock() { Lock(Transaction.Current); }

   void Lock(Transaction transaction)
   {
      Monitor.Enter(this);
      if(OwningTransaction == null)
      {
         //Acquire the transaction lock
         if(transaction != null) OwningTransaction = transaction;
         Monitor.Exit(this);
         return;
      }
      else //Some transaction owns the lock
      {

         //We're done if it's the same one as the method parameter
         if(OwningTransaction == transaction)
         {
            Monitor.Exit(this);
            return;
         }
         //Otherwise, need to acquire the transaction lock
         else
         {
            ManualResetEvent manualEvent = new ManualResetEvent(false);

            KeyValuePair<Transaction,ManualResetEvent> pair = 
               new KeyValuePair<Transaction,ManualResetEvent>(
                  transaction,manualEvent);

            m_PendingTransactions.AddLast(pair); 

            if(transaction != null)
            {
               transaction.TransactionCompleted += delegate
               {
                  lock(this)
                  {
                     //Pair may have already been removed if unlocked
                     m_PendingTransactions.Remove(pair);
                  }
                  lock(manualEvent)
                  {
                      if(!manualEvent.SafeWaitHandle.IsClosed)
                      {
                          manualEvent.Set();
                      }
                  }
               };
            }
            Monitor.Exit(this);

            //Block the transaction or the calling thread
            manualEvent.WaitOne();
            lock(manualEvent) manualEvent.Close();
         }
      }
   }

   public void Unlock()
   {
      Debug.Assert(Locked);
      lock(this)
      {
         OwningTransaction = null;

         LinkedListNode<KeyValuePair<Transaction,ManualResetEvent>> 
            node = null;

         if(m_PendingTransactions.Count > 0)
         {
            node = m_PendingTransactions.First;
            m_PendingTransactions.RemoveFirst();
         }

         if(node != null)
         {
            Transaction transaction = node.Value.Key;
            ManualResetEvent manualEvent = node.Value.Value;
            Lock(transaction);
            lock(manualEvent)
            {
               if(!manualEvent.SafeWaitHandle.IsClosed)
               {
                  manualEvent.Set();
               }
            }
         }
      }
   }
}

图片 6.gif)

Figure 6 Operation of the Lock Method 

Let’s walk through the Lock method first. TransactionalLock has the
m_OwningTransaction member variable of the type Transaction, and the
OwningTransaction property provides thread-safe access to it. As long as
OwningTransaction is null, the lock is considered unlocked. When the
Lock method is called, if OwningTransaction is null, TransactionalLock
simply sets OwningTransaction to the current transaction.

图片 7.gif)

Figure 7 Unlock 

If OwningTransaction is not null, TransactionalLock checks whether the
incoming transaction (obtained by calling Transaction.Current) is the
same as the owning transaction and, if so, the lock does nothing. If, on
the other hand, the incoming transaction is different from the owning
transaction, TransactionalLock adds that transaction to a private queue
it maintains, m_PendingTransactions, which is merely a generic linked
list that stores pairs of key and value. The key for each pair is the
transaction that tried to acquire the already-owned lock, and the value
is a manual reset event. The idea is that the calling transaction will
be blocked waiting for that event to be signaled, and the Unlock method
will signal it. The problem now is that the blocked transaction can be
aborted (perhaps by another thread in the transaction), or it can simply
time out and then abort. However, if the calling transaction thread is
blocked, how would it know it was aborted?

Fortunately, the Transaction class provides the TransactionCompleted
event (see Figure 2) that you can subscribe to. That event is raised
on a thread managed by the transaction manager. The Lock method uses an
anonymous method to subscribe to that event. If the transaction is still
in the queue, the anonymous method removes the transaction from the
queue and signals the manual reset event. The Unlock method is even
simpler: first it resets the lock by setting OwningTransaction to null.
Unlock then removes from the head of the m_PendingTransactions queue
the next pair of transaction and matching. It acquires the lock on
behalf of the transaction that was at the head of the queue and then
unblocks it by signaling the manual reset event.

 

Serialization and Cloning

The second problem in implementing Transactional<T> is to support
rolling back the state of the underlying resource in case a transaction
is aborted. In addition, Transactional<T> must also be able to
commit the changes as one atomic operation. I’ve chosen to make a
temporary copy of the state of the resource and let the transaction work
on that temporary copy. If the transaction aborts,
Transactional<T> simply discards the temporary copy. If the
transaction commits, Transactional<T> uses the temporary copy as
the new state of the resource.

The question then is how do you make a copy of the state of a resource?
When dealing with reference types, merely using the assignment operator
is not good enough because it only copies the reference. Constraining
the type to support ICloneable is not sufficient because there is no
telling whether the returned clone is a deep clone or a shallow copy of
the reference (it also is not type safe, as IClonable.Clone returns type
object). The best solution is to use serialization, because
serialization makes a deep copy of the object and all its members. You
can serialize an object to a stream and deserialize a deep copy of the
object from that stream. This sequence is encapsulated by the static
generic Clone method of the ResourceManager static helper class, shown
in Figure 8 with some of the code removed for conciseness. Clone
takes a parameter called source of the type parameter T, uses the binary
formatter to serialize and deserialize it to and from a memory stream,
and returns the cloned object.

图片 8  Figure 8 ResourceManager
Helper Class

 

public static class ResourceManager 
{
   public static T Clone<T>(T source)
   {
      IFormatter formatter = new BinaryFormatter();
      using(Stream stream = new MemoryStream())
      {
         formatter.Serialize(stream, source);
         stream.Seek(0, SeekOrigin.Begin);
         return (T)formatter.Deserialize(stream);
      }
   }

   public static void ConstrainType(Type type)
   {
      if(!type.IsSerializable)
      {
         string message = "The type " + type + " is not serializable";
         throw new InvalidOperationException(message);
      }
   }
}

Choosing serialization as the cloning mechanism has an important
consequence: Transactional<T> only works with serializable types.
Sadly, C# 2.0 does not support constraining a type parameter to be a
serializable type. To compensate, Transactional<T> will check the
type parameter T in its static constructor using the ConstrainType
method of ResourceManager (see Figure 9). The static constructor
will be called before anything else is done with Transactional<T>,
and it is a common technique for enforcing constraints that have no
support at compile time. ConstrainType verifies that a given type is
serializable by accessing the IsSerializable property of the type.

图片 9  Figure 9 Implementing
Transactional

 

public class Transactional<T> : IEnlistmentNotification
{
   T m_Value;
   T m_TemporaryValue;
   Transaction m_CurrentTransaction; 
   TransactionalLock m_Lock;

   public Transactional(T value)
   {
      m_Lock = new TransactionalLock();
      m_Value = value;
   }

   public Transactional() : this(default(T)) {}

   static Transactional()
   {
      ResourceManager.ConstrainType(typeof(T));
   }

   void IEnlistmentNotification.Commit(Enlistment enlistment)
   {
      IDisposable disposable = m_Value as IDisposable;
      if(disposable != null) disposable.Dispose();

      m_Value = m_TemporaryValue;
      m_CurrentTransaction = null;
      m_TemporaryValue= default(T);
      m_Lock.Unlock();
      enlistment.Done();
   }

   void IEnlistmentNotification.InDoubt(Enlistment enlistment)
   {
      // Bad for a volatile resource, but not
      // much that can be done about it
      m_Lock.Unlock();
      enlistment.Done();
   }

   void IEnlistmentNotification.Prepare(
       PreparingEnlistment preparingEnlistment)
   {
      preparingEnlistment.Prepared();
   }

   void IEnlistmentNotification.Rollback(Enlistment enlistment)
   {
      m_CurrentTransaction = null;

      IDisposable disposable = m_TemporaryValue as IDisposable;
      if(disposable != null) disposable.Dispose();

      m_TemporaryValue = default(T);
      m_Lock.Unlock();
      enlistment.Done();
   }

   void Enlist(T t)
   {
      Debug.Assert(m_CurrentTransaction == null);
      m_CurrentTransaction = Transaction.Current;
      m_CurrentTransaction.EnlistVolatile(this,EnlistmentOptions.None);
      m_TemporaryValue = ResourceManager.Clone(t);
   }

   void SetValue(T t)
   {
      m_Lock.Lock();
      if(m_CurrentTransaction == null)
      {
         if(Transaction.Current == null)
         {
            m_Value = t;
            return;
         }
         else
         {
            Enlist(t);
            return;
         }
      }
      else m_TemporaryValue = t; 
   }

   T GetValue()
   {
      m_Lock.Lock();
      if(m_CurrentTransaction == null)
      {
         if(Transaction.Current == null) return m_Value; 
         else Enlist(m_Value); 
      }
      return m_TemporaryValue; 
   }

   public T Value
   {
      get { return GetValue(); }
      set { SetValue(value); }
   }
}

 

Transactional<T> Walkthrough

With the key elements of transactional locking and resource state
cloning in place, implementing Transactional<T> is
possible. Figure 9 shows the implementation with some of the code
removed for clarity.

Transactional<T> has two member variables of type T. m_Value
represents the actual consistent state, and m_TemporaryValue is the
temporary copy given to a transaction to work on. Transactional<T>
also has the m_Lock member of the type TransactionalLock, used to
isolate m_Value and allow only serialized access to it. Finally,
Transactional<T> maintains a reference to the current transaction
in the m_CurrentTransaction member variable. If m_CurrentTransaction
is not null, then Transactional<T> is considered to be enlisted in
a transaction.

The Value property delegates to the GetValue and SetValue
methods. Figure 10 illustrates the operation of the SetValue method.
The first thing SetValue does is lock the transactional lock m_Lock. If
the volatile resource is owned by another transaction, then this will
block SetValue until its turn comes to access the resource. Once the
lock is acquired, SetValue checks whether it is already enlisted in a
transaction. If m_CurrentTransaction is null and the call has no
transaction, SetValue sets the actual value m_Value, since no
transaction support is required. If, on the other hand,
Transactional<T> is not enlisted, but the call has a transaction,
SetValue enlists in the transaction by calling the Enlist helper method.
GetValue functions in a similar manner, except it reads rather than
writes to the resource.

图片 10.gif)

Figure 10 Operation of SetValue 

The Enlist method sets m_CurrentTransaction to the incoming current
transaction and calls the EnlistVolatile method of the current
transaction, thus hooking itself up to the transaction manager of
System.Transactions. Finally, Enlist uses ResourceManager.Clone to make
a deep copy of the resource and then assigns that to m_TemporaryValue.
Note that EnlistVolatile accepts an implementation of
IEnlistmentNotification, which Transactional<T> implements
explicitly. IEnlistmentNotification is used by the transaction manager
to ask Transactional<T> to vote during the two-phase commit
protocol and to notify Transactional<T> about the outcome of the
transactions (instructing it to commit or abort). Since
Transactional<T> will always commit if asked to do so, Prepare
simply calls the Prepared method of the preparing element.

Implementing IEnlistmentNotification.Commit and
IEnlistmentNotification.Rollback is now straightforward. Commit disposes
of m_Value (if it offers an IDisposable), copies the reference to the
temporary value in m_TemporaryValue to the actual value m_Value, thus
committing the transaction, and unlocks the lock, allowing another
transaction to set or get the value of the volatile resource.
IEnlistmentNotification.Rollback simply disposes of m_TemporaryValue
and then sets m_Value back to its default value, thus discarding the
changes, and unlocks the lock.

 

Transactional Collections

The generic type parameter T for Transactional<T> can be any
serializable type. However, you are most likely to use it with two main
categories of data structures or resources. The first category includes
primitive types like integers and strings or custom objects like a
customer or an order. Because there is an unlimited number of such
types, what Transactional<T> offers is good enough. Whatever the
type may be, as long as it is serializable, you can treat it as a
volatile resource manager and access it through Value.

The second category is a collection of individual items such as arrays,
linked lists, queues, and so on. You can specify such a collection as
the type parameter and access it through the Value property, as shown in
the following:

 

Transactional<int[]> numbers = new Transactional<int[]>(new int[3]);
numbers.Value[0] = 1;
numbers.Value[1] = 2;
numbers.Value[2] = 3;

using(TransactionScope scope = new TransactionScope())
{
   numbers.Value[0] = 11;
   numbers.Value[1] = 22;
   numbers.Value[2] = 33;
   scope.Complete();
}
Debug.Assert(numbers.Value[2] == 33);

However, such use of the type parameter leads to somewhat cumbersome
programming. I would like to use a transactional array as if it were a
normal array and a transactional linked list as if it were a normal
linked list, without the need to always dereference it through Value.
Since there are only a handful of such useful collections, I defined all
the collections in System.Collections.Generic as transactional
collections. As an example, TransactionalQueue is shown in Figure
11
.

图片 11  Figure 11 Transactional
Collections

 

public abstract class TransactionalCollection<C,T> : 
   Transactional<C>, IEnumerable<T> where C : IEnumerable<T>
{
   public TransactionalCollection(C collection)
   {
      Value = collection;
   }

   IEnumerator<T> IEnumerable<T>.GetEnumerator()
   {
      return Value.GetEnumerator();
   }

   IEnumerator IEnumerable.GetEnumerator()
   {
      return ((IEnumerable<T>)this).GetEnumerator();
   }
}

public class TransactionalQueue<T> : TransactionalCollection<Queue<T>,T>,
                                     ICollection
{
   public TransactionalQueue(int capacity) : 
      base(new Queue<T>(capacity)) {}

   public void Enqueue(T item) { Value.Enqueue(item); }

   public T Dequeue() { return Value.Dequeue(); }
}
...//additional collections

These collections are completely polymorphic with the collections
available in System.Collections.Generic. They offer the same methods,
and they implement implicitly or explicitly the same interfaces as their
respective non-transactional cousins. As a result, they are used in the
same way. Here’s an example:

 

TransactionalArray<int> numbers = new TransactionalArray<int>(3);
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;

using(TransactionScope scope = new TransactionScope())
{
   numbers[0] = 11;
   numbers[1] = 22;
   numbers[2] = 33;
}
Debug.Assert(numbers[2] == 3);

Figure 12 shows an example of using TransactionalQueue as a message
queue. Any message added to the queue is rejected if the enqueuing
transaction is aborted. Any message removed from the queue is added back
if the dequeuing transaction is aborted. This enables programming models
similar to System.Messaging and Windows Communication Foundation MSMQ
binding, where you have automatic canceling (enqueuing is rolled back),
guaranteed delivery, and an auto-retry mechanism (processing the message
fails, so the dequeuing rolls back).

图片 12  Figure 12 TransactionalQueue
as a Message Queue

 

[Serializable]
class MyMessage { ... }

TransactionalQueue<MyMessage> messageQueue = 
   new TransactionalQueue<MyMessage>();
messageQueue.Enqueue(new MyMessage());
using(TransactionScope scope = new TransactionScope())
{
   messageQueue.Enqueue(new MyMessage());
   messageQueue.Enqueue(new MyMessage());
   messageQueue.Enqueue(new MyMessage());
   Debug.Assert(messageQueue.Count == 4);
}
Debug.Assert(messageQueue.Count == 1);

To automate parts of the implementation (all collections need to support
IEnumerable<T>), the transactional collections derive from the
abstract class TransactionalCol-lection<C,T>, which, in turn,
derives from Transactional<C>, passing C as the type parameter to
it. C is constrained to support IEnumerable<T>, and Value is
assigned a value of the type C in the constructor. As a result,
TransactionalCollection<C,T> can delegate to Value the
implementation of IEnumerable<T>. In each of the concrete
transactional collections, implementing the various methods and
properties is done by delegating the implementation to the methods and
properties now exposed by Value. For each concrete transactional
collection, Value exposes the methods it requires because each concrete
transactional collection specifies the collection it wraps as the type
parameter C when deriving from TransactionalCollection<C,T>:

 

public class TransactionalList<T> : TransactionalCollection<List<T>,T>,
                                    IList<T>,ICollection<T>
{...}

You can also use TransactionalCollection<C,T> as the base class
for your own custom transactional collections.

 

Conclusion

I believe that transactional programming, from durable to volatile
resources will be the predominant programming model of the future.
Volatile resource managers allow you to unleash the fundamental benefits
of transactions to everyday objects and common data structures—from
integers to dictionaries. Adding this transactional functionality
requires minimal changes to the conventional programming model. By
eliminating the need to handcraft error handling and recovery, you gain
productivity, quality, and faster time to market. This also lowers the
cost of long-term maintenance and conforming to new requirements and
scenarios.

 

 

Juval Lowy is a software architect providing .NET architecture
consultation and advanced training. He is the Microsoft Regional
Director for the Silicon Valley. His latest book is Programming .NET
Components
, 2nd Edition (O’Reilly, 2005). Contact Juval
at www.idesign.net.

 1、Cache-aside Pattern
缓存方式

打消由一雨后春笋步骤推行的干活,一齐定义三个谈到底一致的操作,如若三个或八个步骤失利。在云托管的应用程序,实现复杂的业务和劳作流程,经常操作是根据的末梢一致性模型。

Load data on demand into a cache from a data store. This pattern can
improve performance and also helps to maintain consistency between data
held in the cache and the data in the underlying data store.

Context and Problem 背景和主题素材

Applications running in the cloud frequently modify data. This data may
be spread across an assortment of data sources held in a variety of
geographic locations. To avoid contention and improve performance in a
distributed environment such as this, an application should not attempt
to provide strong transactional consistency. Rather, the application
should implement eventual consistency. In this model, a typical
business operation consists of a series of autonomous steps. While these
steps are being performed the overall view of the system state may be
inconsistent, but when the operation has completed and all of the steps
have been executed the system should become consistent again.

在云端运维的应用程序平时修改数据,此数额大概会分散在不一致地理地点的数量源中。为了制止竞争和提升品质,如在分布式意况中,应用程序不应该总计提供有力的业务一致性。相反,应用程序应该完成最后的一致性。在那几个模型中,一个出色的事情操作由一雨后春笋的自立步骤组成。固然那么些步骤正在开始展览系统状态的一体化视图可能是区别的,但当操作已经产生,并已进行的享有手续的种类应改为平等。

Note:

The Data Consistency
Primer
provides
more information about why distributed transactions do not scale well,
and the principles that underpin the eventual consistency model.

A significant challenge in the eventual consistency model is how to
handle a step that has failed irrecoverably. In this case it may be
necessary to undo all of the work completed by the previous steps in the
operation. However, the data cannot simply be rolled back because other
concurrent instances of the application may have since changed it. Even
in cases where the data has not been changed by a concurrent instance,
undoing a step might not simply be a matter of restoring the original
state. It may be necessary to apply various business-specific rules (see
the travel website described in the
Example
section).

在最后一致性模型的三个要害的挑战是如何管理二个满盘皆输的手续。在这种景色下,它大概是不可缺少的打消全体的劳作产生的前一步操作。但是,数据不能够大约地回滚,因为应用程序的别的并发实例也许早已转移了它。尽管未有到手数码的产出情形的变动,打消一步恐怕非不过复苏原本的气象。它或者是必不可缺的,适用于各个事务的现实法规(见例子)。

If an operation that implements eventual consistency spans several
heterogeneous data stores, undoing the steps in such an operation will
require visiting each data store in turn. The work performed in every
data store must be undone reliably to prevent the system from remaining
inconsistent.

比如实现最后一致性超越五个异构数据存款和储蓄操作,撤除步骤等操作须要种种拜望每种数据存款和储蓄。在各类数据存款和储蓄中举行的做事都不可能不被裁撤,避防御系统的分化。

Not all data affected by an operation that implements eventual
consistency might be held in a database. In a Service Oriented
Architecture (SOA) environment an operation may invoke an action in a
service, and cause a change in the state held by that service. To undo
the operation, this state change must also be undone. This may involve
invoking the service again and performing another action that reverses
the effects of the first.

并非颇具的数量都会影响到终极的一致性,或许在多个数据库中举行。在面向服务的系统布局(SOA)情况下的操作恐怕在劳务调用动作,而招致的劳动景况的浮动。打消该操作,该景况退换也不能不被吊销。那可能波及再次调用服务,并实践另八个五花大绑的震慑的行路。

图片 13

Solution 化解方案

Implement a compensating transaction. The steps in a compensating
transaction must undo the effects of the steps in the original
operation. A compensating transaction might not be able to simply
replace the current state with the state the system was in at the start
of the operation because this approach could overwrite changes made by
other concurrent instances of an application. Rather, it must be an
intelligent process that takes into account any work done by concurrent
instances. This process will usually be application-specific, driven by
the nature of the work performed by the original operation.

落实互补专门的学问。补偿工作中的步骤必须打消原操作步骤的影响。贰个填补职业可能不可见轻便地更迭当前运营状态的事态,因为这种措施可以覆盖应用程序的别样并发实例举办转移。相反,它必须是一个智能的长河,必要思虑到任何职业所做的出现实例。这几个进程一般是特定于应用程序的,由原操作施行的做事的习性驱动的。

A common approach to implementing an eventually consistent operation
that requires compensation is to use a workflow. As the original
operation proceeds, the system records information about each step and
how the work performed by that step can be undone. If the operation
fails at any point, the workflow rewinds back through the steps it has
completed and performs the work that reverses each step. Note that a
compensating transaction might not have to undo the work in the exact
mirror-opposite order of the original operation, and it may be possible
to perform some of the undo steps in parallel.

一种常见的不二等秘书技来达成四个说起底一致的操作,供给补给操作是行使一个专门的学问流。作为村生泊长的操作举办,系统记录有关每一步的音讯,以及怎么样成功该手续所试行的行事。假使在任哪一天候操作失利,工作流将回到到步骤已经到位和奉行工作的每一步。请小心,补偿工作可能不须求在原本的操作的镜像相反的各在这之中撤除工作,并恐怕有相当大希望进行一些并行的打消步骤。

Note:

This approach is similar to the Sagas strategy. A description of this
strategy is available online in Clemens Vasters’
blog
.

A compensating transaction is itself an eventually consistent operation
and it could also fail. The system should be able to resume the
compensating transaction at the point of failure and continue. It may be
necessary to repeat a step that has failed, so the steps in a
compensating transaction should be defined as idempotent commands. For
more information about idempotency, see Idempotency
Patterns

on Jonathan Oliver’s blog.

补偿性事务本人就是一种最后一致的操作,也恐怕破产。该种类应该力所能及东山复起在故障点的补偿性操作,并承继。大概需求再行战败步骤,那么在补偿工作的步调应定义为幂等的通令。关于幂等性的越多消息,参见Idempotency
Patterns

on Jonathan Oliver’s blog。

In some cases it may not be possible to recover from a step that has
failed except through manual intervention. In these situations the
system should raise an alert and provide as much information as possible
about the reason for the failure.

在有些情形下,它可能不能复苏从三个小败的步调,除了通过人为干预。在这种情状下,系统应该提供预先警告,并尽量为停业的缘由提供多的音信。

从数额存款和储蓄区加载到缓存中的数据。这种形式能够增进品质,也可以有利于保持在缓存中的数据里面包车型客车一致性和尾部数据存款和储蓄的多寡。

Issues and Considerations 难题和注意事项

Consider the following points when deciding how to implement this
pattern:

在调整哪些兑现那一个格局时,缅想以下几点:

  • It might not be easy to determine when a step in an operation that
    implements eventual consistency has failed. A step might not fail
    immediately, but instead it could block. It may be necessary to
    implement some form of time-out mechanism.
  • 那也许不轻巧果决,在贰个最终一致性操作的某步骤的操作失利。二个步骤可能不会及时战败,但它反而被堵塞。它也许需求完结某种格局的超时机制。
  • Compensation logic is not easily generalized. A compensating
    transaction is application-specific; it relies on the application
    having sufficient information to be able to undo the effects of each
    step in a failed operation.
  • 补给论理是不易于广泛的。补偿职业是应用程序特定的,它借助于应用程序有充裕的新闻,能够撤除三遍停业的操作中的每一步的影响。
  • You should define the steps in a compensating transaction as
    idempotent commands. This enables the steps to be repeated if the
    compensating transaction itself fails.
  • 您应该定义一个互补工作为幂等的命令。那使得能够再度的手续,如果补偿本人退步。
  • The infrastructure that handles the steps in the original operation,
    and the compensating transaction, must be resilient. It must not
    lose the information required to compensate for a failing step, and
    it must be able to reliably monitor the progress of the compensation
    logic.
  • 管理在本来操作中的步骤和增加补充专门的学业的基础必须是有弹性的。它无法失去所需的新闻以弥补三个难倒的步子,它必须能够可信地监测补偿论理的长河。
  • A compensating transaction does not necessarily return the data in
    the system to the state it was in at the start of the original
    operation. Instead, it compensates for the work performed by the
    steps that completed successfully before the operation failed.
  • 补充专门的学业不必重临到原有操作起来时的连串中的数据。相反,它弥补了专业达成的步调,在操作失败以前到位的做事。
  • The order of the steps in the compensating transaction does not
    necessarily have to be the mirror opposite of the steps in the
    original operation. For example, one data store may be more
    sensitive to inconsistencies than another, and so the steps in the
    compensating transaction that undo the changes to this store should
    occur first.
  • 在补充职业中步骤的种种不必然是在原先的操作步骤的镜像。比如,二个数目存款和储蓄恐怕比另三个越来越灵活,由此,在补偿职业中,撤销对该存款和储蓄的改换的步子应该首头阵出。
  • Placing a short-term timeout-based lock on each resource that is
    required to complete an operation, and obtaining these resources in
    advance, can help increase the likelihood that the overall activity
    will succeed. The work should be performed only after all the
    resources have been acquired. All actions must be finalized before
    the locks expire.
  • 在每二个能源上放置一个短时间的超时锁,来成功三个操作,并提前得到那个能源,可以支持增添全部活动的可能。全部的财富都被收购后,才实行那职业。全体行动必须在锁到期前成功。
  • Consider using retry logic that is more forgiving than usual to
    minimize failures that trigger a compensating transaction. If a step
    in an operation that implements eventual consistency fails, try
    handling the failure as a transient exception and repeat the step.
    Only abort the operation and initiate a compensating transaction if
    a step fails repeatedly or irrecoverably.
  • 设想选择重试逻辑,常常是的更便于的,以压缩故障,触发一个填补职业。假如在多个操作中完成末了的一致性的手续失利,试着拍卖故障作为三个一时的这一个,然后重新步骤。独有丢弃操作,假诺八个步骤战败恐怕屡次地开发银行补偿职业。

Note:

Many of the challenges and issues of implementing a compensating
transaction are the same as those concerned with implementing eventual
consistency. See the section Considerations for Implementing Eventual
Consistency in the Data Consistency
Primer
for more
information.

2、断路器(CircuitBreaker)设计形式

Handle faults that may take a variable amount of time to rectify when
connecting to a remote service or resource. This pattern can improve the
stability and resiliency of an application.

图片 14

在接连到一个远道服务或能源时,管理故障也许须要贰个变量的时间来修正。这种方式能够增加应用程序的安居和弹性。

3、Compensating Transaction
帕特tern(事务改正格局)

Undo the work performed by a series of steps, which together define an
eventually consistent operation, if one or more of the operations fails.
Operations that follow the eventual consistency model are commonly found
in cloud-hosted applications that implement complex business processes
and workflows.

图片 15

裁撤由一雨后春笋步骤实践的干活,在协同定义一个末尾一致的操作,即使贰个或四个操作战败。在云托管的应用程序,达成复杂的业务流程和劳作流程,服从的尾声一致性模型的操作是常常的做法。

4、Competing Consumers
Pattern(花费者竞争方式)

Enable multiple concurrent consumers to process messages received on the
same messaging channel. This pattern enables a system to process
multiple messages concurrently to optimize throughput, to improve
scalability and availability, and to balance the workload.

图片 16

同意几个冒出用户处理在一直以来新闻通道上抽出的拍卖音讯。这种格局使系统能够管理五个新闻,同临时间优化吞吐量,提升可扩张性和可用性,并以平衡的职业量。

5、Compute Resource Consolidation Pattern
总计能源整合格局

Consolidate multiple tasks or operations into a single computational
unit. This pattern can increase compute resource utilization, and reduce
the costs and management overhead associated with performing compute
processing in cloud-hosted applications.

图片 17

将多个职务或操作合併成一个十足的谋算单元。这种情势能够加强总结能源的利用率,并收缩本钱和保管的支付与实施总计管理在云托管的应用程序。

6、Command and Query Responsibility Segregation (CQ奔驰M级S) Pattern 
命令和查询任务分开(CQ途胜S)情势

Segregate operations that read data from operations that update data by
using separate interfaces. This pattern can maximize performance,
scalability, and security; support evolution of the system over time
through higher flexibility; and prevent update commands from causing
merge conflicts at the domain level.

图片 18

隔离操作,读取数据更新数据的操作通过利用单独的接口。这种方式能够最大限度地进步质量,可扩张性和安全性;支持系统的升华,随着时间的推迟,通过越来越高的八面见光,并防止更新命令导致合併冲突在域级。

7、Event Sourcing 帕特tern
事件源形式

Use an append-only store to record the full series of events that
describe actions taken on data in a domain, rather than storing just the
current state, so that the store can be used to materialize the domain
objects. This pattern can simplify tasks in complex domains by avoiding
the requirement to synchronize the data model and the business domain;
improve performance, scalability, and responsiveness; provide
consistency for transactional data; and maintain full audit trails and
history that may enable compensating actions.

图片 19

动用增添存款和储蓄来记录在域中所选拔的多寡的共同体种类事件,并非储存当前情况,因而该存款和储蓄区能够用来完成域对象。这种形式能够简化复杂的园地中的职务,以制止须要协同的数据模型和事务领域,提升品质,可扩展性和响应性;提供一致性的事务数据,并维持完全的审计跟踪和野史,大概使补偿行动。

8、External Configuration Store Pattern
外部配置存款和储蓄情势

Move configuration information out of the application deployment package
to a centralized location. This pattern can provide opportunities for
easier management and control of configuration data, and for sharing
configuration data across applications and application instances.

将配置新闻从应用程序铺排包中移动到集中地点。这种方式能够提供更便于的保管和决定配备数据的机遇,并在应用程序和使用实例之间分享配置数据。

图片 20

9、Federated Identity Pattern
联合身份格局

Delegate authentication to an external identity provider. This pattern
can simplify development, minimize the requirement for user
administration, and improve the user experience of the application.

将身份验证委托给外部标记提供程序。该格局能够简化开辟,减弱用户管理的供给,升高应用程序的用户体验。

图片 21

10、Gatekeeper Pattern
把关模式

Protect applications and services by using a dedicated host instance
that acts as a broker between clients and the application or service,
validates and sanitizes requests, and passes requests and data between
them. This pattern can provide an additional layer of security, and
limit the attack surface of the system.

图片 22

采用专项使用的主机实例,作为客户和应用程序或劳务时期的代办爱抚的应用程序和劳务,验证和清理的渴求,并经过它们中间的伸手数据。这种情势能够提供一个格外的安全层,并限定了对系统的口诛笔伐。

11、Health Endpoint Monitoring Pattern 健康端点监测形式

Implement functional checks within an application that external tools
can access through exposed endpoints at regular intervals. This pattern
can help to verify that applications and services are performing
correctly.

在二个应用程序内完毕效果与利益检查,外界工具得以在为期的时日间隔中做客揭露的端点。这种格局能够扶持验证应用程序和劳动是或不是科学推行。

12、Index Table 帕特tern 索引表形式

Create indexes over the fields in data stores that are frequently
referenced by query criteria. This pattern can improve query performance
by allowing applications to more quickly retrieve data from a data
store.

开创索引查询数据在储存领域中平时援用的正规。这种形式能够拉长查询质量,让应用程序越来越高效地从存款和储蓄中寻找数据。

13、Leader Election Pattern 领导大选格局

Coordinate the actions performed by a collection of collaborating task
instances in a distributed application by electing one instance as the
leader that assumes responsibility for managing the other instances.
This pattern can help to ensure that tasks do not conflict with each
other, cause contention for shared resources, or inadvertently interfere
with the work that other task instances are performing.

14、Materialized View Pattern 物化视图情势

Generate pre-populated views over the data in one or more data stores
when the data is formatted in a way that does not favor the required
query operations. This pattern can help to support efficient querying
and data extraction, and improve application performance.

15、Pipes and Filters Pattern 管道和过滤器情势

Decompose a task that performs complex processing into a series of
discrete elements that can be reused. This pattern can improve
performance, scalability, and reusability by allowing task elements that
perform the processing to be deployed and scaled independently.

16、Priority Queue Pattern 优先级队列情势

Prioritize requests sent to services so that requests with a higher
priority are received and processed more quickly than those of a lower
priority. This pattern is useful in applications that offer different
service level guarantees to individual types of client.

17、Queue-based Load Leveling Pattern 基于队列的载重均衡格局

Use a queue that acts as a buffer between a task and a service that it
invokes in order to smooth intermittent heavy loads that may otherwise
cause the service to fail or the task to timeout. This pattern can help
to minimize the impact of peaks in demand on availability and
responsiveness for both the task and the service.

18、Retry Pattern 重试格局

Enable an application to handle temporary failures when connecting to a
service or network resource by transparently retrying the operation in
the expectation that the failure is transient. This pattern can improve
the stability of the application.

19、Runtime Reconfiguration Pattern 运转时重新配置情势

Design an application so that it can be reconfigured without requiring
redeployment or restarting the application. This helps to maintain
availability and minimize downtime.

20、Scheduler Agent Supervisor Pattern 调节代理经理情势

Coordinate a set of actions across a distributed set of services and
other remote resources, attempt to transparently handle faults if any of
these actions fail, or undo the effects of the work performed if the
system cannot recover from a fault. This pattern can add resiliency to a
distributed system by enabling it to recover and retry actions that fail
due to transient exceptions, long-lasting faults, and process failures.

21、Sharding Pattern 分片格局

Divide a data store into a set of horizontal partitions shards. This
pattern can improve scalability when storing and accessing large volumes
of data.

22、Static Content Hosting Pattern 静态内容托管形式

Deploy static content to a cloud-based storage service that can deliver
these directly to the client. This pattern can reduce the requirement
for potentially expensive compute instances.

23、Throttling Pattern 节流形式

Control the consumption of resources used by an instance of an
application, an individual tenant, or an entire service. This pattern
can allow the system to continue to function and meet service level
agreements, even when an increase in demand places an extreme load on
resources.

24、Valet Key Pattern

Use a token or key that provides clients with restricted direct access
to a specific resource or service in order to offload data transfer
operations from the application code. This pattern is particularly
useful in applications that use cloud-hosted storage systems or queues,
and can minimize cost and maximize scalability and performance.

When to Use this Pattern 几时利用这种格局

Use this pattern only for operations that must be undone if they fail.
If possible, design solutions to avoid the complexity of requiring
compensating transactions (for more information, see the Data
Consistency
Primer
).

动用此格局仅用于操作必须是只要她们不能够撤废。假若恐怕的话,设计缓慢解决方案以幸免复杂的渴求补偿职业(更加的多音讯,见数据一致性引物)。

Example 例子

A travel website enables customers to book itineraries. A single
itinerary may comprise a series of flights and hotels. A customer
traveling from Seattle to London and then on to Paris could perform the
following steps when creating an itinerary:

四个出境游网址,使客户预约行程。八个单纯的路途可回顾一多级的航班和客栈。一位花费者从爱丁堡到London,然后到时尚之都,在创立行程时,能够试行以下步骤:

  1. Book a seat on flight F1 from Seattle to London.
  2. Book a seat on flight F2 from London to Paris.
  3. Book a seat on flight F3 from Paris to Seattle.
  4. Reserve a room at hotel H1 in London.
  5. Reserve a room at hotel H2 in Paris.

预定一张从金沙萨到London的航班F1。
预定一张从London到法国首都的航班F2。
预约一张从法国巴黎到塔林的航班F3。
储备在London一间旅舍H1。
在巴黎酒楼预约多少个屋企。

These steps constitute an eventually consistent operation, although each
step is essentially a separate atomic action in its own right.
Therefore, as well as performing these steps, the system must also
record the counter operations necessary to undo each step in case the
customer decides to cancel the itinerary. The steps necessary to perform
the counter operations can then run as a compensating transaction if
necessary.

那一个步骤构成最后一致性操作,尽管每一步基本上是在协和的职责单独的原子动作。因而,以及在进行那几个步骤时,系统还非得记录须求撤废在状态下,客户决定收回行程的种种步骤中的计数器的操作。然后必须奉行计数器操作步骤能够在急需时运转补偿性事务。

Notice that the steps in the compensating transaction might not be the
exact opposite of the original steps, and the logic in each step in the
compensating transaction must take into account any business-specific
rules. For example, “unbooking” a seat on a flight might not entitle the
customer to a complete refund of any money paid.

请留神,在补充职业的步子大概不是原来的步子完全相反,并且在补偿专门的学问的各种步骤都必须惦记到别的特定的政工准绳逻辑。比如,“订舱”在航班上的座席也许没有资格客户开垦的别样款项全额退款。

图片 23

Figure 1 – Generating a compensating transaction to undo a long-running
transaction to book a travel itinerary

图1 – 生成补偿事务撤销长日子运作的事体预约旅游行程

Note:

It may be possible for the steps in the compensating transaction to be
performed in parallel, depending on how you have designed the
compensating logic for each step.

有一点都不小希望在补偿职业的手续并行取决于你什么设计用来每一个步骤中的补偿论理被实践。

In many business solutions, failure of a single step does not always
necessitate rolling the system back by using a compensating transaction.
For example, if—after having booked flights F1, F2, and F3 in the travel
website scenario—the customer is unable to reserve a room at hotel H1,
it is preferable to offer the customer a room at a different hotel in
the same city rather than cancelling the flights. The customer may still
elect to cancel (in which case the compensating transaction runs and
undoes the bookings made on flights F1, F2, and F3), but this decision
should be made by the customer rather than by the system.

在无数业务解决方案,单步的波折并不总是不可或缺选用补偿专门的职业滚动系统复苏。举个例子,具有预定航班F1,F2和F3在观景网址情景客户不大概预约时饭馆H1即使-之后,最佳是能为客户提供在同一个都会的屋家在差异的饭馆并非吊销航班。客户还是能够选取裁撤(在这种景色下,补偿职业中运作,并撤回关于航班F1,F2和F3作出的订购),但该调控应由用户,实际不是由系统实行。

Related Patterns and Guidance 相关方式和教导

The following patterns and guidance may also be relevant when
implementing this pattern:

  • Data Consistency
    Primer
    . The
    Compensating Transaction pattern is frequently used to undo
    operations that implement the eventual consistency model. This
    primer provides more information on the benefits and tradeoffs of
    eventual consistency.
  • Scheduler-Agent-Supervisor
    Pattern
    .
    This pattern describes how to implement resilient systems that
    perform business operations that utilize distributed services and
    resources. In some circumstances, it may be necessary to undo the
    work performed by an operation by using a compensating transaction.
  • Retry
    Pattern
    .
    Compensating transactions can be expensive to perform, and it may be
    possible to minimize their use by implementing an effective policy
    of retrying failing operations by following the Retry pattern.

More Information 更加多消息

相关文章