常见的分布式锁--MySQL是如何实现分布式锁功能?

使用MySQL的行级锁机制来实现分布式锁。 设计过程如下:
1.创建一张用于存储锁信息的表,包含以下字段:锁的名称、锁的持有者、锁的过期时间等。
2.当需要获取锁时,向该表插入一条记录,如果插入成功,则表示获取到了锁。
3.当需要释放锁时,根据锁的名称和持有者信息,删除该表中的相应记录,释放锁。

1 创建数据库表

首先,在MySQL数据库中创建一个表用于存储锁的状态。假设有一个表 distributed_lock,包含以下字段:

  • id:锁的唯一标识,可以是自增主键或全局唯一标识符(GUID)。
  • resource_key:锁的资源标识符,用于唯一标识需要加锁的资源。
  • owner:锁的拥有者,表示当前持有锁的客户端。
  • created_time:锁的创建时间,表示锁的获取时间。
  • expiration_time:锁的过期时间,表示锁的有效期。
CREATE TABLE lock (
  id INT PRIMARY KEY AUTO_INCREMENT, -- 锁的唯一标识
  resource_key VARCHAR(255) NOT NULL, -- 锁的资源标识符
  owner VARCHAR(255) NOT NULL, -- 锁的拥有者
  created_time DATETIME NOT NULL, -- 锁的创建时间
  expiration_time DATETIME NOT NULL -- 锁的过期时间
);

2 获取锁

在Java代码中,获取锁的过程可以通过执行MySQL的插入操作来实现。考虑到并发情况下可能会有多个客户端同时尝试获取锁,可以通过以下代码来实现:

import java.sql.*;

public class DistributedLock {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/my_db";
    private static final String DB_USER = "root";
    private static final String DB_PASSWORD = "password";

    public boolean acquireLock(String resourceKey, int timeout) {
        boolean lockAcquired = false;
        Connection conn = null;
        try {
            // 创建数据库连接
            conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

            // 开始事务
            conn.setAutoCommit(false);

            // 查询是否已经有其他客户端持有锁
            String selectSql = "SELECT * FROM distributed_lock WHERE resource_key = ? FOR UPDATE";
            PreparedStatement selectStmt = conn.prepareStatement(selectSql);
            selectStmt.setString(1, resourceKey);
            ResultSet rs = selectStmt.executeQuery();

            if (!rs.next()) {
                // 如果没有其他客户端持有锁,则插入新的锁记录
                String insertSql = "INSERT INTO distributed_lock (resource_key, owner, created_time, expiration_time) VALUES (?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? SECOND))";
                PreparedStatement insertStmt = conn.prepareStatement(insertSql);
                insertStmt.setString(1, resourceKey);
                insertStmt.setString(2, Thread.currentThread().getName());
                insertStmt.setInt(3, timeout);
                insertStmt.executeUpdate();

                // 提交事务
                conn.commit();

                lockAcquired = true;
            }

            rs.close();
            selectStmt.close();
        } catch (SQLException e) {
            // 处理异常
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            // 关闭数据库连接
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

        return lockAcquired;
    }
}

在上述代码中,acquireLock 方法接收两个参数:resourceKey 代表需要加锁的资源标识符,timeout 代表获取锁的超时时间(以秒为单位)。

首先,代码创建数据库连接,然后开始事务。接下来,通过执行 SELECT 语句查询是否已经有其他客户端持有锁。如果没有其他客户端持有锁,则执行 INSERT 语句插入新的锁记录,并提交事务。最后,关闭数据库连接,并返回获取锁的结果。

3 释放锁

获取锁成功后,需要在使用完资源后释放锁。释放锁的过程可以通过执行 MySQL 的删除操作来实现。以下是释放锁的代码:

public class DistributedLock {
    // ...

    public void releaseLock(String resourceKey) {
        Connection conn = null;
        try {
            // 创建数据库连接
            conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

            // 开始事务
            conn.setAutoCommit(false);

            // 删除锁记录
            String deleteSql = "DELETE FROM distributed_lock WHERE resource_key = ?";
            PreparedStatement deleteStmt = conn.prepareStatement(deleteSql);
            deleteStmt.setString(1, resourceKey);
            deleteStmt.executeUpdate();

            // 提交事务
            conn.commit();

            deleteStmt.close();
        } catch (SQLException e) {
            // 处理异常
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            // 关闭数据库连接
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在上述代码中,releaseLock 方法接收一个参数 resourceKey,代表需要释放的锁的资源标识符。

代码创建数据库连接,然后开始事务。接下来,通过执行 DELETE 语句删除锁记录,并提交事务。最后,关闭数据库连接。

4 应用场景:

一个典型的应用场景是分布式系统中的任务调度。假设有多个任务调度节点,每个节点同时只能执行一个任务,当有多个节点同时尝试执行任务时,需要使用分布式锁来保证同一时间只有一个节点执行任务。

示例代码:

public class TaskScheduler {
    private static final String TASK_RESOURCE_KEY = "task_resource";
    private static final int LOCK_TIMEOUT = 10; // 10秒

    public void scheduleTask() {
        DistributedLock lock = new DistributedLock();
        boolean lockAcquired = lock.acquireLock(TASK_RESOURCE_KEY, LOCK_TIMEOUT);
        if (lockAcquired) {
            try {
                // 执行任务
                System.out.println("Task scheduled by " + Thread.currentThread().getName());
                Thread.sleep(5000); // 模拟任务执行时间

                // 释放锁
                lock.releaseLock(TASK_RESOURCE_KEY);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        TaskScheduler scheduler = new TaskScheduler();

        // 创建多个线程模拟任务调度节点
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(scheduler::scheduleTask);
            thread.start();
        }
    }
}

在上述代码中,TaskScheduler 类负责任务调度。首先,创建一个 DistributedLock 对象,然后调用 acquireLock 方法尝试获取锁。如果获取锁成功,则执行任务,并在任务执行完毕后调用 releaseLock 方法释放锁。

main 方法中,创建多个线程模拟任务调度节点。每个线程调用 scheduleTask 方法来尝试执行任务。

5 MySQL实现分布式锁的优缺点

数据库实现分布式锁的优点是简单易用,可以实现互斥访问共享资源。但是在高并发、高性能的场景中,数据库实现分布式锁的效率不高,会增加数据库的开销。另外,数据库实现分布式锁还有以下缺点

  1. 单点故障:如果使用单个数据库实现分布式锁,那么数据库成为了单点故障的风险。如果数据库出现故障,那么所有使用该锁的应用都无法正常运行。
  2. 阻塞:当有多个请求同时争夺锁时,只有一个请求能够成功获取到锁,其他请求需要等待。这样会导致请求被阻塞,延长了响应时间。
  3. 容错性差:数据库实现分布式锁只能在一个数据库中实现,如果该数据库发生故障或者网络中断,那么整个分布式锁功能就无法正常工作。

为了解决数据库实现分布式锁的缺点,可以考虑使用其他工具或技术,如分布式缓存(如Redis)、ZooKeeper等来实现分布式锁。这些工具或技术可以提供更高效、更可靠的分布式锁实现方式,适用于高并发、高性能的场景。

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT_WEH_coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值