java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > 分布式锁及Spring Boot实战

分布式锁详解以及Spring Boot实战示例代码

作者:老张还是少年

在分布式系统中,使用分布式锁是保证共享资源独占性的重要方式,这篇文章主要介绍了分布式锁详解及Spring Boot实战的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

在分布式系统中,多个服务实例可能同时操作共享资源(如数据库中的同一订单、库存记录),若缺乏协调机制,会导致数据不一致(如超卖、重复下单)。分布式锁正是解决这类问题的核心技术,它能保证同一时间只有一个服务实例执行特定临界区代码。

一、分布式锁的核心特性

一个可靠的分布式锁需满足以下特性:

二、分布式锁的实现方案及原理

常见实现方式包括基于数据库、Redis、ZooKeeper 等,不同方式的原理各有不同。

1. 基于数据库的分布式锁

原理:利用数据库的唯一索引或悲观锁来实现。例如,创建一张锁表,包含资源标识、持有线程标识、过期时间等字段,给资源标识字段创建唯一索引。当需要获取锁时,向表中插入一条记录,若插入成功则表示获取到锁;释放锁时,删除该记录。为防止死锁,可定期清理过期未释放的锁。

优缺点

2. 基于 Redis 的分布式锁

原理:利用 Redis 的SET命令的原子性。核心命令如下:

# 仅当key不存在时设置值,过期时间10秒,返回OK表示获取锁成功

SET lock:resource true NX PX 10000

释放锁时,需通过 Lua 脚本保证原子性,先判断锁是否由当前线程持有,再删除锁:

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

优缺点

由于 Redis 的高性能和易用性,它成为分布式锁的主流选择,本文后续重点介绍基于 Redis 的分布式锁实现。

3. 基于 ZooKeeper 的分布式锁

原理:利用 ZooKeeper 的节点特性和 Watcher 机制。ZooKeeper 的节点分为持久节点、临时节点、持久顺序节点、临时顺序节点。分布式锁通常使用临时顺序节点,当需要获取锁时,在指定节点下创建一个临时顺序节点,然后判断当前节点是否为序号最小的节点,若是则获取到锁;若不是,则监听序号比当前节点小的最后一个节点,当该节点被删除时,重新判断。释放锁时,删除创建的临时节点,由于是临时节点,当持有锁的线程崩溃时,节点会自动删除,避免死锁。

优缺点

三、Spring Boot 集成分布式锁的相关依赖及对比

1. 基于 Redis 的依赖

2. 基于 ZooKeeper 的依赖

3. 依赖对比

依赖

基于中间件

特点

适用场景

spring-boot-starter-data-redis

Redis

基础操作支持,需自行实现锁逻辑

简单场景,对灵活性要求高

redisson-spring-boot-starter

Redis

内置完整锁实现,支持高级特性

生产环境,复杂业务场景

spring-cloud-starter-zookeeper-discovery

ZooKeeper

主要用于服务发现,锁实现需自行开发

已使用 ZooKeeper 做服务发现,简单锁场景

curator-recipes

ZooKeeper

封装了分布式锁功能,可靠性高

对锁可靠性要求高的场景

四、Spring Boot 集成 Redis 分布式锁实战

1. 环境准备

pom.xml 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.redisson</groupId> <!-- 推荐使用Redisson简化锁操作 -->
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.3</version>
</dependency>

Redis 配置(application.yml)

spring:
  redis:
    host: localhost
    port: 6379
    database: 0
    timeout: 3000ms

2. 基于 Redisson 的分布式锁实现

Redisson 是 Redis 官方推荐的 Java 客户端,内置了分布式锁的完整实现,支持自动续期、公平锁、可重入锁等高级特性。

分布式锁工具类

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {

    private final RedissonClient redissonClient;

    public RedisDistributedLock(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 获取分布式锁
     * @param lockKey 锁标识
     * @param waitTime 等待时间(获取锁的最大等待时长)
     * @param leaseTime 锁持有时间(自动释放时间)
     * @return 锁对象
     */
    public RLock lock(String lockKey, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试获取锁,最多等待waitTime,持有leaseTime后自动释放
            boolean isLocked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (isLocked) {
                return lock;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return null;
    }

    /**
     * 释放锁
     * @param lock 锁对象
     */
    public void unlock(RLock lock) {
        if (lock != null && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

3. 业务场景示例:库存扣减

Service 层代码

import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class InventoryService {

    @Resource
    private RedisDistributedLock distributedLock;
    @Resource
    private InventoryMapper inventoryMapper;  // 假设已实现数据库操作

    /**
     * 扣减商品库存
     * @param productId 商品ID
     * @param quantity 扣减数量
     * @return 操作结果
     */
    public boolean deductInventory(Long productId, int quantity) {
        // 锁标识:通常用业务资源唯一标识(如商品ID)
        String lockKey = "lock:inventory:" + productId;
        RLock lock = null;
        try {
            // 获取锁:最多等待3秒,持有10秒后自动释放
            lock = distributedLock.lock(lockKey, 3, 10);
            if (lock == null) {
                // 获取锁失败(如超时)
                return false;
            }

            // 临界区代码:查询库存并扣减
            int currentStock = inventoryMapper.selectStockByProductId(productId);
            if (currentStock >= quantity) {
                inventoryMapper.updateStock(productId, currentStock - quantity);
                return true;
            } else {
                // 库存不足
                return false;
            }
        } finally {
            // 确保锁释放
            distributedLock.unlock(lock);
        }
    }
}

五、关键注意事项

六、进阶优化方向

通过不同的分布式锁实现方式和相关依赖,Spring Boot 应用可在分布式环境中安全地操作共享资源。在实际开发中,需根据业务场景和性能需求选择合适的实现方式和依赖,Redisson 等成熟工具可大幅降低实现复杂度,建议在生产环境中优先采用。

总结

到此这篇关于分布式锁详解以及Spring Boot实战的文章就介绍到这了,更多相关分布式锁及Spring Boot实战内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文