如何在使用负载平衡时锁定对象

背景 :我正在编写一个函数,使用C#将长时间的操作放入队列中,每个操作分为3个步骤:
1.数据库操作(更新/删除/添加数据)
2.使用Web服务进行长时间计算
3.数据库操作(保存步骤2的计算结果)在步骤1中的同一个db表上,并检查db表的一致性,例如,步骤1中的项目是相同的(请参阅下面的更详细的示例)

为了避免脏数据或损坏,我使用锁对象(静态单例对象)来确保作为整个事务完成3个步骤。 因为当多个用户正在调用该函数来执行操作时,他们可以在他们自己的操作期间的不同步骤修改相同的db表而没有这个锁定,例如,user2在他的step1中删除了项目A,而user1正在检查A是否仍然存在于他的第3步。(附加信息:同时我正在使用Entity框架中的TransactionScope来确保每个数据库操作都是一个事务,但重复可读。)

但是,我需要把它放到一个使用负载均衡机制的云计算平台上,所以实际上我的锁对象不会生效,因为该function将部署在不同的服务器上。

问题:如何使我的锁定对象在上述情况下工作?

这是一个棘手的问题 – 您需要分布式锁或某种共享状态。

由于您已经拥有数据库,因此可以从“静态C#锁”更改您的实现,而是在整个“事务”中为您管理锁定。

您没有说明您使用的数据库,但如果它是SQL Server,那么您可以使用应用程序锁来实现此目的。 这使您可以显式“锁定”对象,所有其他客户端将等待该对象解锁。 查看:

http://technet.microsoft.com/en-us/library/ms189823.aspx

我在下面编写了一个示例实现。 启动两个实例来测试它。

using System; using System.Data; using System.Data.SqlClient; using System.Transactions; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var locker = new SqlApplicationLock("MyAceApplication", "Server=xxx;Database=scratch;User Id=xx;Password=xxx;"); Console.WriteLine("Aquiring the lock"); using (locker.TakeLock(TimeSpan.FromMinutes(2))) { Console.WriteLine("Lock Aquired, doing work which no one else can do. Press any key to release the lock."); Console.ReadKey(); } Console.WriteLine("Lock Released"); } class SqlApplicationLock : IDisposable { private readonly String _uniqueId; private readonly SqlConnection _sqlConnection; private Boolean _isLockTaken = false; public SqlApplicationLock( String uniqueId, String connectionString) { _uniqueId = uniqueId; _sqlConnection = new SqlConnection(connectionString); _sqlConnection.Open(); } public IDisposable TakeLock(TimeSpan takeLockTimeout) { using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Suppress)) { SqlCommand sqlCommand = new SqlCommand("sp_getapplock", _sqlConnection); sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.CommandTimeout = (int)takeLockTimeout.TotalSeconds; sqlCommand.Parameters.AddWithValue("Resource", _uniqueId); sqlCommand.Parameters.AddWithValue("LockOwner", "Session"); sqlCommand.Parameters.AddWithValue("LockMode", "Exclusive"); sqlCommand.Parameters.AddWithValue("LockTimeout", (Int32)takeLockTimeout.TotalMilliseconds); SqlParameter returnValue = sqlCommand.Parameters.Add("ReturnValue", SqlDbType.Int); returnValue.Direction = ParameterDirection.ReturnValue; sqlCommand.ExecuteNonQuery(); if ((int)returnValue.Value < 0) { throw new Exception(String.Format("sp_getapplock failed with errorCode '{0}'", returnValue.Value)); } _isLockTaken = true; transactionScope.Complete(); } return this; } public void ReleaseLock() { using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Suppress)) { SqlCommand sqlCommand = new SqlCommand("sp_releaseapplock", _sqlConnection); sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.Parameters.AddWithValue("Resource", _uniqueId); sqlCommand.Parameters.AddWithValue("LockOwner", "Session"); sqlCommand.ExecuteNonQuery(); _isLockTaken = false; transactionScope.Complete(); } } public void Dispose() { if (_isLockTaken) { ReleaseLock(); } _sqlConnection.Close(); } } } }