SpringCloud--分布式锁实现

本文介绍了分布式锁的概念和主要特征,阐述其用于控制分布式系统中不同进程对共享资源的访问。重点讲解了Java中分布式锁的多种实现方案,包括基于Redis、Redisson + RedLock、Lock4j和ZooKeeper的实现,分析了各方案的原理、优缺点及实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、简介

分布式锁是分布式系统中用于协调多个节点对共享资源的访问的一种机制。它的核心目标是确保在分布式环境下,同一时刻只有一个节点可以访问或修改共享资源,从而避免数据不一致或竞争条件的问题。分布式锁的实现需要考虑以下几个关键点:

  1. 互斥性:同一时刻只有一个节点可以持有锁。
  2. 可重入性:同一个节点可以多次获取同一把锁。
  3. 容错性:即使部分节点或网络出现故障,锁机制仍然能够正常工作。
  4. 避免死锁:锁必须能够被释放,即使持有锁的节点崩溃。
  5. 高性能:锁的获取和释放操作应尽可能高效。

二、实现方案

在 Java 中,实现分布式锁的方案有多种,常见的几种方案如下:

  1. 基于数据库实现:可以通过数据库的乐观锁或悲观锁实现分布式锁,但是由于数据库的 IO 操作比较慢,不适合高并发场景,而且可能存在死锁和超时等问题。
  2. 基于 Redis 实现:Redis 是一个高性能的内存数据库,支持分布式部署,可以通过Redis的原子操作实现分布式锁,支持key的过期时间设置,不容易发生死锁,而且具有高性能和高可用性。
  3. 基于 Lock4j 实现:Lock4j 是一个分布式锁组件,它提供了多种不同的支持以满足不同性能和环境的需求,基于Spring AOP的声明式和编程式分布式锁,支持RedisTemplate、Redisson、Zookeeper。
  4. 基于 ZooKeeper 实现:ZooKeeper 是一个高可用性的分布式协调服务,可以通过它来实现分布式锁。但是使用 ZooKeeper 需要部署额外的服务,增加了系统复杂度。
2.1 基于 Redis 实现
2.1.1 实现原理

Redis 分布式锁的核心是利用 Redis 的原子性操作(如 SETNX 和 EXPIRE)来确保锁的互斥性和可靠性。

2.1.2 实现步骤
  1. 加锁 (原子操作):
    • 使用 SET 命令尝试设置一个键值对,键为锁的名称,值为唯一标识(如 UUID)。
    • 设置 NX 选项,确保只有键不存在时才能设置成功。
    • 设置 PX 选项,为锁设置一个过期时间(毫秒)。
      SET lock_key unique_value NX PX 10000
      
      lock_key:锁的名称。
      unique_value:唯一标识锁的持有者(通常使用 UUID)。
      NX:仅在键不存在时设置。
      PX:设置键的过期时间(毫秒)。
      
  2. 执行业务逻辑:
    • 如果上一步返回成功(OK),表示获得了锁,执行业务代码。
  3. 释放锁 (原子操作 - Lua 脚本):
    • 使用 Lua 脚本确保释放锁的原子性。
    • 检查锁的值是否与当前节点的唯一标识匹配。
    • 如果匹配,则删除锁;否则,不执行任何操作。
      if redis.call("get", KEYS[1]) == ARGV[1] then
      	return redis.call("del", KEYS[1])
      else
      	return 0
      end
      
      KEYS[1]:锁的名称。
      ARGV[1]:当前节点的唯一标识。
      
2.1.3 此方案的致命缺点
  1. 锁续期问题:
    • 业务逻辑执行时间可能超过锁的初始过期时间(30秒)。
    • 如果超过,锁会自动失效,其他客户端可以获取锁,导致临界区代码被并发执行,破坏安全性。
  2. 时钟漂移:
    • Redis 服务器和客户端机器的时钟如果不一致,可能影响过期时间的准确性(影响所有基于 TTL 的方案)。
2.1.4 代码示例

(1)添加redis依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

(2)创建一个redis配置类,用来设置redis连接信息,并创建JedisPool和RedisTemplate的实例。

@Configuration
public class RedisConfig {
	@Value("${spring.redis.host}")
	private String host;
	@Value("${spring.redis.port}")
	private int port;
	@Value("${spring.redis.timeout}")
	private int timeout;
	@Value("${spring.redis.password}")
	private String password;
	@Value("${spring.redis.pool.maxTotal}")
	private int maxTotal;
	@Value("${spring.redis.pool.maxWait}")
	private int maxWait;
	@Value("${spring.redis.pool.maxIdle}")
	private int maxIdle;
	@Value("${spring.redis.pool.minIdle}")
	private int minIdle;
	@Value("${spring.redis.blockWhenExhausted}")
	private Boolean blockWhenExhausted;
	@Value("${spring.redis.JmxEnabled}")
	private Boolean JmxEnabled;

	/**
	 * 创建JedisPool实例
	 * 
	 * @return JedisPool
	 */
	@RefreshScope
	@Bean
	public JedisPool jedisPoolFactory() {
		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
		jedisPoolConfig.setMaxTotal(maxTotal);
		jedisPoolConfig.setMaxIdle(maxIdle);
		jedisPoolConfig.setMinIdle(minIdle);
		jedisPoolConfig.setMaxWaitMillis(maxWait);
		// 连接耗尽时是否阻塞, false报异常,true阻塞直到超时, 默认true
		jedisPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
		// 是否启用pool的jmx管理功能, 默认true
		jedisPoolConfig.setJmxEnabled(JmxEnabled);
		return new JedisPool(jedisPoolConfig, host, port, timeout, password);
	}

	/**
	 * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
	 *
	 * @return redisTemplate
	 */
	@Bean
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);

		// 使用Jackson2JsonRedisSerialize 替换默认序列化
		@SuppressWarnings({ "rawtypes", "unchecked" })
		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

		jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

		// 设置value的序列化规则和 key的序列化规则
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
		redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}
}

(3)编写redis的工具类,使用(set的扩展命令+唯一值校验)的方式创建获取和释放分布式锁相应的方法。

@Slf4j
@Component
public class RedisUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final String LOCK_SUCCESS = "OK";

    /**
     * NX: 仅在键不存在时设置键
     * XX: 只有在键已存在时才设置
     */
    private static final String SET_IF_NOT_EXIST = "NX";

    /**
     * 过期时间单位
     * EX: seconds
     * PX: milliseconds
     */
    private static final String SET_WITH_EXPIRE_TIME = "EX";

    private static final Long RELEASE_SUCCESS = 1L;


    /**
     * 尝试获取分布式锁
     *
     * @param lockKey    分布式锁的key,想要获取锁时,判断这个key是否存在于redis中,存在则说明获取分布式锁失败,否则成功获取锁
     * @param requestId  每个请求的全局唯一id,用于释放锁时只能释放自己持有的锁
     * @param expireTime 超期时间,单位:秒
     * @return boolean   是否获取成功
     */
    public boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        if (jedis == null) {
            return false;
        }

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            log.info("========================================获取分布式锁成功, lockKey is:{}, requestId is:{}", lockKey, requestId);
            return true;
        }

        log.info("========================================获取分布式锁失败, lockKey is:{}, requestId is:{}", lockKey, requestId);
        return false;
    }

    /**
     * 释放分布式锁
     *
     * @param jedis     Redis客户端
     * @param lockKey   分布式锁的key
     * @param requestId 每个请求的全局唯一id
     * @return 是否释放成功
     */
    public boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        if (jedis == null) {
            log.info("========================================分布式锁释放失败,Jedis为空, lockKey is:{}, requestId is:{}", lockKey, requestId);
            return false;
        }

        // 通过Lua 脚本保证只释放requestId对应的lockKey
        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));
        if (RELEASE_SUCCESS.equals(result)) {
            log.info("========================================分布式锁释放成功, lockKey is:{}, requestId is:{}", lockKey, requestId);
            return true;
        }

        log.info("========================================分布式锁释放失败, lockKey is:{}, requestId is:{}", lockKey, requestId);
        return false;
    }
}

(4)案例实现

	/**
     * 通过分布式锁来生成全局订单唯一的id
     * 
     * @param type 订单类型
     * @return String 订单唯一的id
     */
    @Override
    public String generate(String type) {
        String orderId = null;
        log.info("========================================要生成的订单类型为:{}", type);
        if (StringUtils.isBlank(type)) {
            return null;
        }

        // 开始获取分布式锁
        Jedis jedis = jedisPool.getResource();
        String lockKey = redisUtils.getRedisKey(RedisTemplateConstant.DISTRIBUTED_LOCK_KEY_TYPE, type);
        String requestId = CommonUtil.getUUID();
        try {
            if (redisUtils.tryGetDistributedLock(jedis, lockKey, requestId, expireTime)) {
                // 生成订单id
                orderId = getOrderId(type);
            }
        } catch (Exception e) {
            log.info("========================================组装id出错================================");
        } finally {
            	// 释放分布式锁
                redisUtils.releaseDistributedLock(jedis, lockKey, requestId);
            if (jedis != null) {
                jedis.close();
            }
        }

        return orderId;
    }
2.2 基于 Redisson 实现

Redisson 是一个成熟的 Redis Java 客户端,其分布式锁 (RLock) 不仅支持基本的互斥锁,还支持可重入锁、公平锁、读写锁等高级特性,同时提供了锁的自动续期机制(Watchdog),极大地简化了分布式锁的实现和使用。

2.2.1 Redisson 分布式锁的核心特性
  1. 互斥性:同一时刻只有一个客户端可以持有锁。
  2. 可重入性:同一个客户端可以多次获取同一把锁。
  3. 公平锁:支持公平锁机制,按照请求的顺序获取锁。
  4. 锁的自动续期:通过 Watchdog 机制,自动延长锁的持有时间,避免锁在业务逻辑执行过程中过期。
  5. 高可用性:支持 Redis 集群、哨兵模式和单节点模式,确保锁服务的高可用性。
  6. 读写锁:支持读写锁,允许多个读操作同时进行,但写操作是互斥的。
2.2.2 实现原理

Redisson 分布式锁通过 Lua脚本 + 看门狗 + 可重入设计 + 异步通信,实现了高可靠、易用的分布式锁。其核心优势在于:

  1. 自动化锁续期:彻底解决业务超时问题。
  2. 客户端级封装:开发者无需关心底层细节。
  3. 完备的锁语义:支持阻塞、超时、公平锁等多种模式。

(1)加锁流程(关键:Lua 脚本原子操作)

-- 参数:KEYS[1]=锁名称, ARGV[1]=锁超时时间, ARGV[2]=客户端唯一ID(格式:UUID + 线程ID)
if (redis.call('exists', KEYS[1]) == 0) then 
    redis.call('hincrby', KEYS[1], ARGV[2], 1);  -- 新增锁,计数=1
    redis.call('pexpire', KEYS[1], ARGV[1]);      -- 设置锁过期时间
    return nil;                                  -- 返回nil表示成功
end;
-- 若锁已存在且是当前线程持有(可重入)
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
    redis.call('hincrby', KEYS[1], ARGV[2], 1);  -- 重入次数+1
    redis.call('pexpire', KEYS[1], ARGV[1]);      -- 刷新过期时间
    return nil;
end;
-- 锁被其他线程占用
return redis.call('pttl', KEYS[1]);               -- 返回锁剩余过期时间(毫秒)

步骤解析:

  1. 尝试获取锁:
    • 如果锁不存在(exists == 0),用 Hash 结构存储:
      Key=锁名称,Field=客户端ID,Value=重入次数(初始1),并设置过期时间。
    • 如果锁存在且是当前线程持有(hexists == 1),重入次数 +1 并刷新过期时间。
  2. 锁冲突处理:
    • 若锁被其他线程持有,返回锁的剩余生存时间(pttl),客户端根据此时间决定是否重试。

(2)锁续期:看门狗机制(Watchdog)

  • 核心解决:业务执行时间 > 锁超时时间导致锁提前释放的问题。
  • 触发条件:加锁时不手动设置超时时间(如 lock.lock()),默认启用看门狗。
  • 工作流程:
    1. 获取锁成功后,启动一个 后台定时任务(守护线程)。
    2. 任务每隔 锁超时时间 / 3(默认 10 秒)检查一次:
      • 若客户端仍持有锁(通过 hexists 验证),则刷新锁过期时间(pexpire)。
      • 若锁已被释放或不属于当前客户端,任务终止。
    3. 业务执行完毕释放锁时,会主动取消续期任务。
// Redisson 源码片段(简化)
private void renewExpiration() {
    Timeout task = commandExecutor.schedule(() -> {
        if (getLockName(threadId) != null) {
            // 调用Lua脚本刷新过期时间
            evalWriteAsync(getRawName(), ...);
            renewExpiration(); // 递归调用,形成循环续期
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 默认每10秒执行一次
}

(3)释放锁流程(Lua 保证原子性)

-- 参数:KEYS[1]=锁名称, KEYS[2]=解锁消息通道, ARGV[1]=超时时间, ARGV[2]=客户端ID
-- 1. 检查锁是否存在且归属当前线程
if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then 
    return nil; -- 锁不属于当前线程,直接返回
end; 
-- 2. 重入次数减1
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); 
if (counter > 0) then 
    redis.call('pexpire', KEYS[1], ARGV[1]); -- 仍有重入,刷新过期时间
    return 0; 
else 
    redis.call('del', KEYS[1]); -- 重入次数=0,删除锁
    redis.call('publish', KEYS[2], ARGV[3]); -- 发布解锁消息(通知等待线程)
    return 1; 
end; 
return nil;

步骤解析:

  1. 验证锁归属:确保只有持有者能释放。
  2. 减少重入计数:
    • 若计数 > 0,刷新过期时间;
    • 若计数 = 0,删除锁并发布解锁消息(通知其他等待线程竞争锁)。

(4)锁等待与重试机制

  • 阻塞等待:lock.lock() 会阻塞线程直到获取锁。
  • 超时等待:lock.tryLock(waitTime, leaseTime, unit) 支持设置最大等待时间。
  • 实现原理:
    1. 获取锁失败后,订阅 Redis 的 Channel(锁名称对应的解锁消息)。
    2. 当收到解锁通知(通过 Lua 脚本中的 publish 触发),尝试再次获取锁。
    3. 若超过 waitTime 仍未获锁,放弃并抛出超时异常。
2.2.3 代码示例

(1)引入依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.17.0</version>
</dependency>

(2)配置 Redisson 客户端

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class RedissonConfig {

    public static RedissonClient createClient() {
        Config config = new Config();
        config.useSingleServer()
              .setAddress("redis://127.0.0.1:6379")
              .setPassword("your_password"); // 如果有密码
        return Redisson.create(config);
    }
}

(3)使用 Redisson 分布式锁

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;

public class RedissonDistributedLock {

    public static void main(String[] args) {
    	// 1. 创建Redisson客户端
        RedissonClient redisson = RedissonConfig.createClient();
        
        // 2. 获取锁对象
        RLock lock = redisson.getLock("myLock");

        try {
            // 3. 加锁(默认30秒过期,启动看门狗)
            lock.lock(); 
    		// lock.tryLock(10, 60, TimeUnit.SECONDS); // 等待10秒,锁60秒后自动释放
    		
            if (isLocked) {
            	// 4. 执行业务代码
                System.out.println("获取锁成功,执行业务逻辑...");
                Thread.sleep(5000); // 模拟业务逻辑执行
            } else {
                System.out.println("获取锁失败,锁已被其他客户端持有");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
            	// 5. 释放锁(自动取消看门狗任务)
                lock.unlock();
                System.out.println("释放锁成功");
            }
            redisson.shutdown();
        }
    }
}
2.2.4 Redisson 分布式锁的高级特性
  1. 可重入锁
    • Redisson 的分布式锁是可重入的,同一个线程可以多次获取同一把锁。
      RLock lock = redisson.getLock("myLock");
      lock.lock();
      try {
      	// 可重入锁
      	lock.lock();
      	try {
      		// 执行业务逻辑
      	} finally {
      		lock.unlock();
      	}
      } finally {
      	lock.unlock();
      }
      
  2. 公平锁
    • Redisson 支持公平锁,按照请求的顺序获取锁。
      RLock fairLock = redisson.getFairLock("myFairLock");
      fairLock.lock();
      try {
      	// 执行业务逻辑
      } finally {
      	fairLock.unlock();
      }
      
  3. 读写锁
    • Redisson 支持读写锁,允许多个读操作同时进行,但写操作是互斥的。
      RReadWriteLock rwLock = redisson.getReadWriteLock("myRWLock");
      RLock readLock = rwLock.readLock();
      RLock writeLock = rwLock.writeLock();
      
      // 读锁
      readLock.lock();
      try {
      	// 执行读操作
      } finally {
      	readLock.unlock();
      }
      
      // 写锁
      writeLock.lock();
      try {
      	// 执行写操作
      } finally {
      	writeLock.unlock();
      }
      
2.3 基于 Lock4j 实现

Lock4j 是一个轻量级的分布式锁框架,基于 Spring Boot 和 Redis 实现,提供了简单易用的分布式锁功能。它的核心目标是简化分布式锁的实现,同时支持多种锁类型(如可重入锁、公平锁)和锁的自动续期机制。

2.3.1 Lock4j 的核心特性
  1. 简单易用:提供注解和 API 两种方式实现分布式锁,开箱即用。
  2. 支持多种锁类型:支持可重入锁、公平锁等。
  3. 锁的自动续期:通过 Watchdog 机制,自动延长锁的持有时间,避免锁在业务逻辑执行过程中过期。
  4. 高可用性:基于 Redis 实现,支持 Redis 集群和哨兵模式。
  5. 灵活的锁配置:支持自定义锁的过期时间、重试次数、重试间隔等。
2.3.2 实现原理

Lock4j 的核心是基于 Redis 的 SET 命令的 NX 选项(仅在键不存在时设置)和 PX 选项(设置过期时间)来实现分布式锁。同时,Lock4j 提供了以下功能:

  1. 锁的获取:
    • 使用 Redis 的 SET 命令尝试获取锁。
    • 如果锁已被其他客户端持有,支持重试机制。
  2. 锁的释放:
    • 使用 Lua 脚本确保释放锁的原子性,避免误删其他客户端的锁。
  3. 锁的自动续期:
    • 通过 Watchdog 机制,定期检查锁的持有状态,并在锁即将过期时自动延长锁的过期时间。
2.3.3 代码示例

(1)引入依赖


<!-- 若使用redisTemplate作为分布式锁底层,则需要引入 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
    <version>2.2.4</version>
</dependency>
<!-- 若使用redisson作为分布式锁底层,则需要引入 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
    <version>2.2.4</version>
</dependency>

(2)配置 Lock4j
在 application.yml 中配置 Lock4j 和 Redis:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: your_password # 如果有密码

lock4j:
  acquire-timeout: 3000 # 获取锁的超时时间(毫秒)
  expire: 30000 # 锁的过期时间(毫秒)
  retry-interval: 500 # 重试间隔(毫秒)
  retry-times: 3 # 重试次数

(3)使用注解实现分布式锁
Lock4j 提供了 @Lock4j 注解,可以轻松实现分布式锁。

import com.baomidou.lock.annotation.Lock4j;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Lock4j(keys = {"#orderId"}) // 使用 orderId 作为锁的 key
    public void createOrder(String orderId) {
        System.out.println("创建订单:" + orderId);
        // 执行业务逻辑
    }
}

(4)使用 API 实现分布式锁
Lock4j 还提供了 API 方式实现分布式锁,适合更复杂的场景。

import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.annotation.Lock4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private LockTemplate lockTemplate;

    public void createOrder(String orderId) {
        boolean isLocked = lockTemplate.lock("orderLock:" + orderId, 3000, 30000, () -> {
            System.out.println("创建订单:" + orderId);
            // 执行业务逻辑
            return true;
        });

        if (!isLocked) {
            System.out.println("获取锁失败");
        }
    }
}
2.4 基于 ZooKeeper 实现

基于 ZooKeeper 实现分布式锁 是一种经典的分布式锁实现方案。ZooKeeper 是一个分布式协调服务,提供了强一致性、高可用性和顺序一致性等特性,非常适合用于实现分布式锁。

2.4.1 实现原理

ZooKeeper 实现分布式锁的核心是利用其 临时顺序节点(Ephemeral Sequential Node) 和 Watcher 机制。

2.4.2 实现步骤
  1. 创建临时顺序节点
    • 每个客户端在 ZooKeeper 的指定路径(如 /locks)下创建一个临时顺序节点。
    • 例如,客户端 A 创建节点 /locks/lock_00000001,客户端 B 创建节点 /locks/lock_00000002。
  2. 判断是否获取锁
    • 客户端获取 /locks 路径下的所有子节点,并按序号排序。
    • 如果自己创建的节点是最小序号的节点,表示获取锁。
    • 否则,监听前一个节点的删除事件。
  3. 释放锁
    • 客户端完成任务后,删除自己创建的节点。
    • 下一个序号的节点会收到通知,尝试获取锁。
2.4.3 代码示例

(1)引入依赖

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.7.0</version>
</dependency>

(2)配置 ZooKeeper 连接

spring:
  zookeeper:
    connect-string: localhost:8081
    namespace: test

(3)实现分布式锁

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class ZkDistributedLock implements Watcher {

    private static final String LOCK_ROOT_PATH = "/locks";
    private static final String LOCK_NODE_NAME = "lock_";
    private ZooKeeper zooKeeper;
    private String lockPath;
    private CountDownLatch latch = new CountDownLatch(1);

    public ZkDistributedLock(ZooKeeper zooKeeper) {
        this.zooKeeper = zooKeeper;
    }

    /**
     * 尝试获取锁
     */
    public void acquireLock() throws Exception {
        // 创建临时顺序节点
        lockPath = zooKeeper.create(LOCK_ROOT_PATH + "/" + LOCK_NODE_NAME,
                null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

        // 尝试获取锁
        while (true) {
            List<String> children = zooKeeper.getChildren(LOCK_ROOT_PATH, false);
            Collections.sort(children);
            String smallestNode = children.get(0);

            if (lockPath.endsWith(smallestNode)) {
                System.out.println("获取锁成功:" + lockPath);
                return;
            } else {
                // 监听前一个节点
                String previousNode = children.get(Collections.binarySearch(children, lockPath.substring(LOCK_ROOT_PATH.length() + 1)) - 1);
                Stat stat = zooKeeper.exists(LOCK_ROOT_PATH + "/" + previousNode, this);

                if (stat != null) {
                    System.out.println("等待锁:" + previousNode);
                    latch.await(); // 阻塞等待
                }
            }
        }
    }

    /**
     * 释放锁
     */
    public void releaseLock() throws Exception {
        zooKeeper.delete(lockPath, -1);
        System.out.println("释放锁:" + lockPath);
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeDeleted) {
            latch.countDown(); // 前一个节点被删除,唤醒等待线程
        }
    }

    public static void main(String[] args) throws Exception {
        ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 3000, null);
        ZkDistributedLock lock = new ZkDistributedLock(zooKeeper);

        try {
            lock.acquireLock();
            System.out.println("执行业务逻辑...");
            Thread.sleep(5000); // 模拟业务逻辑执行
        } finally {
            lock.releaseLock();
            zooKeeper.close();
        }
    }
}
Spring Cloud实现分布式锁可以使用ZooKeeper或Redis实现。这里以Redis为例,实现分布式锁的大致步骤如下: 1. 引入Redis依赖,配置Redis连接信息: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` ``` spring.redis.host=127.0.0.1 spring.redis.port=6379 ``` 2. 实现分布式锁类(以Redis为例): ``` @Component public class RedisLock { private RedisTemplate<String, Object> redisTemplate; private static final String LOCK_PREFIX = "lock:"; private static final long LOCK_EXPIRE = 30000L; // 锁的过期时间,单位毫秒 @Autowired public RedisLock(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } public boolean lock(String lockKey) { String key = LOCK_PREFIX + lockKey; String value = UUID.randomUUID().toString(); Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, LOCK_EXPIRE, TimeUnit.MILLISECONDS); return success != null && success; } public void unlock(String lockKey) { String key = LOCK_PREFIX + lockKey; redisTemplate.delete(key); } } ``` 3. 在定时任务中使用分布式锁: ``` @Component public class MyTask { private RedisLock redisLock; @Autowired public MyTask(RedisLock redisLock) { this.redisLock = redisLock; } @Scheduled(cron = "0/5 * * * * ?") public void run() { if (redisLock.lock("myTask")) { try { // 执行定时任务的逻辑 } finally { redisLock.unlock("myTask"); } } } } ``` 4. 在多个节点上部署相同的定时任务,并启动应用程序。每个节点都会尝试获取分布式锁,只有一个节点能够获取到锁并执行定时任务,其他节点会被阻塞。 需要注意的是,分布式锁实现还需要考虑一些细节问题,如锁的粒度、重试机制、锁超时处理等等。这里的实现只是提供了一个基本的思路,具体实现中还需要根据业务需求进行调整。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值