分布式锁和代码实现

举报
一颗小谷粒 发表于 2025/03/31 20:33:11 2025/03/31
【摘要】 分布式锁介绍在单体应用中,为了保证数据的一致性和线程安全,我们可以使用 Java 内置的锁机制,如 synchronized 关键字和 ReentrantLock 类。然而,在分布式系统中,多个服务实例可能同时访问共享资源,传统的单体锁机制无法满足需求,这时就需要使用分布式锁。分布式锁是一种用于在分布式系统中控制对共享资源访问的机制,它可以确保在同一时刻只有一个客户端能够获取到锁并执行关键代...

分布式锁介绍

在单体应用中,为了保证数据的一致性和线程安全,我们可以使用 Java 内置的锁机制,如 synchronized 关键字和 ReentrantLock 类。然而,在分布式系统中,多个服务实例可能同时访问共享资源,传统的单体锁机制无法满足需求,这时就需要使用分布式锁。


分布式锁是一种用于在分布式系统中控制对共享资源访问的机制,它可以确保在同一时刻只有一个客户端能够获取到锁并执行关键代码,从而避免多个客户端同时修改共享资源导致的数据不一致问题。常见的实现分布式锁的技术有基于数据库、Redis、ZooKeeper 等。

各种实现方式的优缺点

  • 基于数据库
    • 优点:实现简单,只需要创建一张锁表,通过插入或更新记录来实现加锁和解锁操作。
    • 缺点:性能较差,因为数据库的操作涉及到磁盘 I/O;锁的释放依赖于数据库连接的正常关闭,可能会出现死锁问题;不支持高并发场景。
  • 基于 Redis
    • 优点:性能高,Redis 是基于内存的数据库,读写速度快;支持多种数据结构和原子操作,方便实现分布式锁;可以设置锁的过期时间,避免死锁。
    • 缺点:需要考虑 Redis 的高可用性,否则可能会出现锁丢失的问题;锁的实现相对复杂,需要处理锁的过期时间和原子性问题。
  • 基于 ZooKeeper
    • 优点:可靠性高,ZooKeeper 是一个分布式协调服务,具有高可用性和一致性;支持分布式锁的公平性和可重入性;可以监听锁的释放事件,实现异步通知。
    • 缺点:性能相对较低,因为 ZooKeeper 的操作涉及到网络通信和节点间的协调;部署和维护成本较高。
import redis.clients.jedis.Jedis;

import java.util.Collections;

public class RedisDistributedLock {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;

    private Jedis jedis;

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

    /**
     * 尝试获取分布式锁
     * @param lockKey 锁的键
     * @param requestId 请求标识,用于区分不同的客户端请求
     * @param expireTime 锁的过期时间,单位为毫秒
     * @return 是否获取锁成功
     */
    public boolean tryLock(String lockKey, String requestId, int expireTime) {
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        return LOCK_SUCCESS.equals(result);
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁的键
     * @param requestId 请求标识,用于区分不同的客户端请求
     * @return 是否释放锁成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        return RELEASE_SUCCESS.equals(result);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        RedisDistributedLock lock = new RedisDistributedLock(jedis);
        String lockKey = "myLock";
        String requestId = "123456";
        int expireTime = 10000;

        // 尝试获取锁
        if (lock.tryLock(lockKey, requestId, expireTime)) {
            try {
                // 模拟业务逻辑
                System.out.println("获取锁成功,开始执行业务逻辑...");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放锁
                lock.releaseLock(lockKey, requestId);
                System.out.println("业务逻辑执行完毕,释放锁...");
            }
        } else {
            System.out.println("获取锁失败,请稍后重试。");
        }
        jedis.close();
    }
}    

代码解释

  • tryLock 方法:使用 Redis 的 SET 命令尝试获取锁,NX 表示只有当键不存在时才设置,PX 表示设置过期时间。如果返回结果为 OK,则表示获取锁成功。
  • releaseLock 方法:使用 Redis 的 EVAL 命令执行 Lua 脚本,确保释放锁的操作是原子性的。只有当锁的键对应的值等于请求标识时,才会删除该键,避免误释放其他客户端的锁。
  • main 方法:演示了如何使用 RedisDistributedLock 类来获取和释放分布式锁。在获取锁成功后,模拟执行业务逻辑,最后释放锁。

注意事项

  • 锁的过期时间:需要合理设置锁的过期时间,避免因业务逻辑执行时间过长导致锁提前过期,从而引发多个客户端同时访问共享资源的问题。
  • 请求标识:在获取锁和释放锁时,需要使用相同的请求标识,以确保只有持有锁的客户端才能释放锁。
  • 异常处理:在执行业务逻辑时,需要捕获异常并在 finally 块中释放锁,确保锁一定会被释放。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。