c# 数据库分布式锁

在 C# 中实现数据库分布式锁,可以使用数据库中的行级锁来实现。具体来说,可以在数据库中创建一个表,用于存储锁的信息,包括锁的名称、持有者、过期时间等。当需要获取锁时,可以在表中插入一条记录,如果插入成功,则表示获取锁成功;否则,表示锁已被其他进程持有,需要等待或者放弃获取锁。

下面是一个简单的实现示例,使用 SQL Server 数据库作为例子:.

public class SqlServerDistributedLock : IDistributedLock
{
    private readonly string _connectionString;

    public SqlServerDistributedLock(string connectionString)
    {
        _connectionString = connectionString;
    }

    public bool TryAcquireLock(string lockName, TimeSpan expirationTime, out IDistributedLockHandle lockHandle)
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();

            using (var transaction = connection.BeginTransaction())
            {
                try
                {
                    // 尝试插入一条记录,如果插入成功,则表示获取锁成功
                    var insertCommand = new SqlCommand($"INSERT INTO DistributedLocks (LockName, Holder, ExpirationTime) VALUES (@LockName, @Holder, @ExpirationTime)", connection, transaction);
                    insertCommand.Parameters.AddWithValue("@LockName", lockName);
                    insertCommand.Parameters.AddWithValue("@Holder", Environment.MachineName);
                    insertCommand.Parameters.AddWithValue("@ExpirationTime", DateTime.UtcNow.Add(expirationTime));
                    var rowsAffected = insertCommand.ExecuteNonQuery();

                    if (rowsAffected == 1)
                    {
                        // 获取锁成功,返回一个锁句柄
                        lockHandle = new SqlServerDistributedLockHandle(connection, transaction, lockName);
                        return true;
                    }
                }
                catch (SqlException)
                {
                    // 如果插入失败,则表示锁已被其他进程持有,返回获取锁失败
                }

                transaction.Rollback();
                lockHandle = null;
                return false;
            }
        }
    }
}

public class SqlServerDistributedLockHandle : IDistributedLockHandle
{
    private readonly SqlConnection _connection;
    private readonly SqlTransaction _transaction;
    private readonly string _lockName;

    public SqlServerDistributedLockHandle(SqlConnection connection, SqlTransaction transaction, string lockName)
    {
        _connection = connection;
        _transaction = transaction;
        _lockName = lockName;
    }

    public void Dispose()
    {
        // 释放锁时,删除对应的记录
        var deleteCommand = new SqlCommand($"DELETE FROM DistributedLocks WHERE LockName = @LockName AND Holder = @Holder", _connection, _transaction);
        deleteCommand.Parameters.AddWithValue("@LockName", _lockName);
        deleteCommand.Parameters.AddWithValue("@Holder", Environment.MachineName);
        deleteCommand.ExecuteNonQuery();

        _transaction.Commit();
        _connection.Close();
    }
}

在上面的示例中,TryAcquireLock 方法用于尝试获取锁,如果获取成功,则返回一个实现了 IDistributedLockHandle 接口的锁句柄,用于在释放锁时删除对应的记录。SqlServerDistributedLockHandle 类实现了 IDisposable 接口,用于在释放锁时提交事务并关闭数据库连接。

如果在使用分布式锁的过程中,异常关闭导致锁没有被删除,可以考虑以下几种处理方式:

  1. 手动删除锁:可以通过登录到数据库中手动删除锁。但是这种方式需要手动操作,不够方便,而且容易出错。

  2. 设置过期时间:可以在获取锁的时候,设置一个过期时间,当锁过期后,自动释放锁。这种方式可以避免锁一直被占用,但是需要注意过期时间的设置,不能设置过短或者过长。

  3. 使用 Redis 等支持自动过期的分布式锁:Redis 支持设置锁的过期时间,当锁过期后会自动释放锁。使用这种方式可以避免异常关闭导致锁没有被删除的问题。

  4. 使用基于 ZooKeeper 等的分布式锁:ZooKeeper 支持在节点上设置锁,当节点异常关闭时,ZooKeeper 会自动删除节点上的锁。使用这种方式可以避免异常关闭导致锁没有被删除的问题,但是需要额外的 ZooKeeper 集群支持。

以上是几种常见的处理方式,具体选择哪种方式,需要根据实际情况进行考虑。