侧边栏壁纸
博主头像
拾荒的小海螺博主等级

只有想不到的,没有做不到的

  • 累计撰写 194 篇文章
  • 累计创建 19 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

架构师:常见分布式锁实现的技术指南

拾荒的小海螺
2024-12-04 / 0 评论 / 0 点赞 / 12 阅读 / 7012 字

1、简述

分布式锁是分布式系统中用来控制资源访问的一种机制,主要解决多实例、多线程同时访问共享资源时的并发问题。本文将介绍常见的分布式锁实现方案,包括基于数据库、Redis、ZooKeeper 的实现方式,并提供实践样例。

image-bism.png

2、分布式锁的基本要求

  • 互斥性:同一时刻只能有一个客户端获取锁。
  • 防死锁:即使客户端发生故障,锁也能自动释放。
  • 可重入性:一个线程在持有锁时可以重复获取。
  • 高性能:锁的获取和释放需要高效。

3、常见分布式锁实现方案

3.1 基于数据库实现分布式锁

利用数据库的唯一约束或 SELECT FOR UPDATE 特性实现分布式锁。

package com.example.springbootclient.lock;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class DatabaseDistributedLock {

    private final DataSource dataSource;

    public DatabaseDistributedLock(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public boolean tryLock(String lockKey, long expireTime) {
        String sql = "INSERT INTO distributed_locks (lock_key, expire_time) VALUES (?, ?) ON DUPLICATE KEY UPDATE expire_time=?";
        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setString(1, lockKey);
            stmt.setLong(2, System.currentTimeMillis() + expireTime);
            stmt.setLong(3, System.currentTimeMillis() + expireTime);
            return stmt.executeUpdate() > 0;
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
    }

    public void releaseLock(String lockKey) {
        String sql = "DELETE FROM distributed_locks WHERE lock_key = ?";
        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setString(1, lockKey);
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • 优点
    简单易用,适合小规模分布式应用。
  • 缺点
    性能较差,数据库压力大。
    需要手动清理过期锁。

3.2 基于 Redis 实现分布式锁

首先,在 Spring Boot 项目中引入 Jedis 依赖。你可以在 pom.xml 文件中添加以下内容:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.3</version> <!-- 请根据实际需求选择最新版本 -->
</dependency>

利用 Redis 的 SET NX 命令实现互斥锁,并结合 EXPIRE 设置超时时间。

package com.example.springbootclient.lock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

public class RedisDistributedLock {

    private final Jedis jedis;

    public RedisDistributedLock(Jedis jedis) {
        this.jedis = jedis;
    }

    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        SetParams   setParams = new SetParams();
        setParams.nx();
        setParams.px(expireTime);
        String result = jedis.set(lockKey, requestId, setParams);
        return "OK".equals(result);
    }

    public boolean releaseLock(String lockKey, String requestId) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(luaScript, 1, lockKey, requestId);
        return "1".equals(result.toString());
    }
}
  • 优点
    高性能,适合高并发场景。
    易于扩展和部署。
  • 缺点
    实现稍复杂,需要保证锁释放的原子性

3.3 基于 ZooKeeper 实现分布式锁

首先,在 Spring Boot 项目中引入 ZooKeeper 依赖。你可以在 pom.xml 文件中添加以下内容:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.8.1</version> <!-- 请根据实际需求选择最新版本 -->
</dependency>

通过 ZooKeeper 的节点特性(临时有序节点)实现分布式锁。

package com.example.springbootclient.lock;

import org.apache.zookeeper.*;
import java.util.Collections;
import java.util.List;

public class ZookeeperDistributedLock {

    private final ZooKeeper zk;
    private final String lockBasePath;
    private String currentLockPath;

    public ZookeeperDistributedLock(ZooKeeper zk, String lockBasePath) {
        this.zk = zk;
        this.lockBasePath = lockBasePath;
    }

    public void tryLock() throws Exception {
        currentLockPath = zk.create(lockBasePath + "/lock_", new byte[0],
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

        while (true) {
            List<String> children = zk.getChildren(lockBasePath, false);
            Collections.sort(children);
            if (currentLockPath.endsWith(children.get(0))) {
                return; // 获取锁成功
            }

            // 等待前一个节点释放
            int index = children.indexOf(currentLockPath.substring(lockBasePath.length() + 1));
            String previousNode = children.get(index - 1);
            zk.exists(lockBasePath + "/" + previousNode, watchedEvent -> {
                if (watchedEvent.getType() == Watcher.Event.EventType.NodeDeleted) {
                    synchronized (this) {
                        this.notifyAll();
                    }
                }
            });
            synchronized (this) {
                this.wait();
            }
        }
    }

    public void releaseLock() throws Exception {
        zk.delete(currentLockPath, -1);
    }
}
  • 优点
    可靠性高,天然支持故障恢复。
    无需手动清理过期锁。
  • 缺点
    部署复杂,需要维护 ZooKeeper 集群。
    性能不如 Redis。

4、总结

分布式锁在分布式系统中是重要的组件,根据具体需求和场景选择合适的实现方案尤为关键:

  • 如果系统简单且性能要求不高,可以选择基于数据库的分布式锁。
  • 如果性能和扩展性是首要考虑,可以选择基于 Redis 的分布式锁。
  • 如果系统需要强一致性和高可靠性,可以选择基于 ZooKeeper 的分布式锁。

通过实践这些分布式锁方案,可以有效解决分布式系统中的资源竞争问题,从而提升系统的可靠性和稳定性。

0

评论区