1. 功能简介

分布式锁是一种用于协调分布式系统中多个进程或线程之间访问共享资源的机制。它能确保某一时刻只有一个进程可以访问共享资源,防止出现竞争条件。

在分布式系统中,常常需要使用外部系统来管理锁,最常见的方案是通过 Redis 来实现分布式锁。Redis 提供了非常高效的键值存储机制,结合 Redis 的原子操作,可以实现分布式锁。

本文将展示如何在 Java 中实现一个基于 Redis 的分布式锁。该分布式锁将允许多个服务实例协调对某个资源的访问,确保同一时刻只有一个服务实例能够获取到锁并执行任务,其他实例将等待直到锁被释放。

2. 代码实现

1. 添加 Redis 依赖

首先,我们需要在项目中添加 Redis 依赖。在 pom.xml 中添加以下内容:

<dependencies>
    <!-- Lettuce Redis Client -->
    <dependency>
        <groupId>io.lettuce.core</groupId>
        <artifactId>lettuce-core</artifactId>
        <version>6.1.5</version>
    </dependency>
    <!-- Jedis Redis Client (Optional) -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.6.0</version>
    </dependency>
</dependencies>
2. 创建 Redis 配置类

接下来,我们需要配置 Redis 连接。我们使用 Lettuce 客户端来与 Redis 进行通信。

package com.example.lock;

import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

public class RedisLockConfig {
    private static final String REDIS_URI = "redis://localhost:6379"; // Redis 连接地址

    private static RedisClient redisClient;
    private static StatefulRedisConnection<String, String> connection;
    private static RedisCommands<String, String> syncCommands;

    static {
        // 创建 Redis 客户端
        redisClient = RedisClient.create(REDIS_URI);
        connection = redisClient.connect();
        syncCommands = connection.sync();
    }

    public static RedisCommands<String, String> getSyncCommands() {
        return syncCommands;
    }

    public static void close() {
        connection.close();
        redisClient.shutdown();
    }
}
3. 创建分布式锁类

这个类将提供锁的获取、释放等功能。我们使用 Redis 的 SETNX 命令来实现分布式锁,它能够保证只有一个客户端能够设置成功。

package com.example.lock;

import io.lettuce.core.api.sync.RedisCommands;

import java.util.concurrent.TimeUnit;

public class DistributedLock {

    private static final String LOCK_PREFIX = "lock:";
    private String lockKey;

    public DistributedLock(String lockKey) {
        this.lockKey = LOCK_PREFIX + lockKey;
    }

    // 获取分布式锁
    public boolean lock(long timeout, TimeUnit timeUnit) {
        RedisCommands<String, String> syncCommands = RedisLockConfig.getSyncCommands();
        long timeoutMillis = timeUnit.toMillis(timeout);

        // 使用 SETNX 命令,只有没有锁时才会设置成功
        boolean isLocked = syncCommands.setnx(lockKey, String.valueOf(System.currentTimeMillis() + timeoutMillis));
        if (isLocked) {
            return true; // 成功获得锁
        }

        // 锁已存在,检查锁是否过期
        String currentLockValue = syncCommands.get(lockKey);
        if (currentLockValue != null && Long.parseLong(currentLockValue) < System.currentTimeMillis()) {
            // 锁已过期,尝试重置锁
            String oldLockValue = syncCommands.getset(lockKey, String.valueOf(System.currentTimeMillis() + timeoutMillis));
            if (oldLockValue != null && oldLockValue.equals(currentLockValue)) {
                // 成功获取锁
                return true;
            }
        }

        return false; // 获取锁失败
    }

    // 释放分布式锁
    public void unlock() {
        RedisCommands<String, String> syncCommands = RedisLockConfig.getSyncCommands();
        String currentLockValue = syncCommands.get(lockKey);
        if (currentLockValue != null && Long.parseLong(currentLockValue) > System.currentTimeMillis()) {
            // 只有当前持锁者才能释放锁
            syncCommands.del(lockKey);
        }
    }
}
4. 创建测试类

在测试类中,我们将模拟两个不同的线程来尝试获取锁,执行任务,并释放锁。

package com.example.lock;

import java.util.concurrent.TimeUnit;

public class DistributedLockTest {

    public static void main(String[] args) throws InterruptedException {
        // 创建两个线程,分别模拟两个服务实例
        Thread thread1 = new Thread(new Task(), "Thread-1");
        Thread thread2 = new Thread(new Task(), "Thread-2");

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            DistributedLock lock = new DistributedLock("myResource");

            try {
                // 尝试获取锁,超时设置为5秒
                if (lock.lock(5, TimeUnit.SECONDS)) {
                    System.out.println(Thread.currentThread().getName() + " acquired the lock!");
                    // 执行一些任务
                    Thread.sleep(2000);  // 模拟任务执行
                    System.out.println(Thread.currentThread().getName() + " finished the task!");

                    // 释放锁
                    lock.unlock();
                    System.out.println(Thread.currentThread().getName() + " released the lock.");
                } else {
                    System.out.println(Thread.currentThread().getName() + " could not acquire the lock.");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

3. 使用说明

  1. 配置 Redis:首先,确保本地或远程的 Redis 服务已启动,并能够通过 localhost:6379 或其他指定地址进行连接。

  2. 添加依赖:在 pom.xml 文件中添加 Redis 客户端依赖,本文使用 Lettuce(也可以选择 Jedis)。

  3. 创建 RedisLockConfig:此类提供了 Redis 客户端的配置和连接,供其他类使用。

  4. 实现 DistributedLock:该类封装了分布式锁的核心逻辑,提供了 lockunlock 方法来分别获取和释放锁。lock 方法支持超时设置。

  5. 测试类 DistributedLockTest:此类模拟了两个线程对共享资源的争夺。线程会尝试获取锁并执行任务。若获取成功,线程将执行任务并在完成后释放锁;若失败,线程将提示无法获取锁。

4. 示例输出

Thread-1 acquired the lock!
Thread-1 finished the task!
Thread-1 released the lock.
Thread-2 could not acquire the lock.

在这个例子中,Thread-1 成功获取了锁并执行了任务,而 Thread-2Thread-1 执行任务时无法获取锁。

5. 总结

通过 Redis 实现的分布式锁能够有效地避免多个分布式服务同时访问同一资源,从而保证资源的访问安全。在分布式系统中,常常需要通过类似的方式来协调不同服务之间的行为,保证系统的稳定性和一致性。本文展示了如何在 Java 中实现一个基于 Redis 的分布式锁,这样的锁可以有效防止并发访问冲突,保证业务的正确性。

Logo

更多推荐