一、为什么需要分布式锁

1. 什么是锁

锁(Lock)的本质是:

保证同一时刻只有一个线程(或一个进程)能够执行某段代码。

例如:

public void deductStock() {
    stock--;
}

假设库存为 1:

  • 用户A购买
  • 用户B购买

两个线程同时执行:

stock--;

结果可能变成:

库存:-1

这就是典型的并发问题。

因此需要锁来保证:

synchronized(lock){
    stock--;
}

同一时刻只能有一个线程进入。


2. 单机锁为什么失效

在单体应用中:

          JVM
 ┌─────────────────┐
 │ Thread-1        │
 │ Thread-2        │
 │ synchronized    │
 └─────────────────┘

synchronizedReentrantLock 都能正常工作。

但是微服务部署后:

        Nginx

     /         \
 Service-A   Service-B

     JVM1       JVM2

用户请求可能进入:

请求1 -> JVM1
请求2 -> JVM2

此时:

synchronized(lock)

只锁住当前 JVM。

因为:

JVM1 不知道 JVM2 的锁状态
JVM2 不知道 JVM1 的锁状态

于是两个服务同时执行。

这就是:

本地锁失效问题

因此需要:

所有节点共享同一把锁

这就是分布式锁。


二、分布式锁是什么

定义:

分布式系统中多个节点共同竞争同一个共享资源时,用来保证同一时刻只有一个节点执行操作的机制。

例如:

秒杀活动
定时任务
订单创建
库存扣减
退款操作

都可能需要分布式锁。


三、分布式锁需要满足什么条件

一个合格的分布式锁通常需要满足:

1. 互斥性

同一时间只能一个客户端获得锁

2. 可重入

例如:

methodA()
    -> methodB()

methodA已经获得锁。

methodB再次加锁:

lock.lock();

不能死锁。


3. 防死锁

持有锁的服务挂掉:

JVM Crash

锁必须自动释放。

通常依赖:

TTL过期时间

4. 高可用

Redis挂了:

锁全部失效

需要主从、哨兵或集群保障。


四、Redis实现分布式锁

这是目前最常见的方案。


1. 原理

Redis提供:

SET key value NX EX 30

含义:

NX:不存在才创建
EX:30秒过期

执行成功:

OK

说明获得锁。

执行失败:

null

说明锁已被占用。


2. Spring Boot实现

加锁

@Autowired
private StringRedisTemplate redisTemplate;

public boolean tryLock(String key) {

    return Boolean.TRUE.equals(
            redisTemplate.opsForValue().setIfAbsent(
                    key,
                    UUID.randomUUID().toString(),
                    30,
                    TimeUnit.SECONDS
            )
    );
}

底层执行:

SET lock:order uuid NX EX 30

释放锁

错误写法:

redisTemplate.delete(key);

问题:

线程A锁超时

线程B获得锁

线程A执行delete

把线程B的锁删了

正确做法:

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

redisTemplate.execute(
        new DefaultRedisScript<>(lua, Long.class),
        Collections.singletonList(key),
        uuid
);

保证:

谁加锁
谁解锁

3. Redisson实现

实际项目更推荐。

引入:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
</dependency>

获取锁:

RLock lock = redissonClient.getLock("orderLock");

try {
    lock.lock();

    createOrder();

} finally {
    lock.unlock();
}

优点:

自动续期
可重入
看门狗机制
高可靠

面试中一般回答:

生产环境推荐 Redisson。


五、数据库唯一索引实现分布式锁

这是最简单的方案之一。


1. 建表

CREATE TABLE distributed_lock(
    lock_name VARCHAR(100) PRIMARY KEY,
    create_time DATETIME
);

2. 获取锁

try {

    lockMapper.insert(lockName);

    return true;

} catch (DuplicateKeyException e) {

    return false;
}

对应SQL:

INSERT INTO distributed_lock
VALUES('ORDER_LOCK',NOW());

3. 原理

假设:

JVM1 插入成功
JVM2 插入同样主键

数据库保证:

主键唯一

第二次插入失败。

因此:

只有一个服务获得锁

4. 缺点

数据库压力大:

频繁insert
频繁delete

性能远低于Redis。


六、数据库乐观锁(版本号)

严格来说:

它不是传统意义上的分布式锁

但经常用于解决并发问题。


1. 表结构

CREATE TABLE stock(
    id BIGINT,
    stock INT,
    version INT
);

数据:

stock = 100
version = 1

2. 查询

SELECT stock,version
FROM stock
WHERE id = 1;

得到:

stock=100
version=1

3. 更新

UPDATE stock
SET stock = stock - 1,
    version = version + 1
WHERE id = 1
AND version = 1;

4. 原理

线程A:

version=1

线程B:

version=1

线程A先更新:

version=2

线程B再更新:

WHERE version=1

匹配不到。

更新失败:

update count = 0

然后重试。


5. MyBatis Plus实现

实体:

@Version
private Integer version;

配置:

@Bean
public MybatisPlusInterceptor interceptor() {

    MybatisPlusInterceptor interceptor =
            new MybatisPlusInterceptor();

    interceptor.addInnerInterceptor(
            new OptimisticLockerInnerInterceptor());

    return interceptor;
}

更新时自动带:

version = oldVersion

条件。


七、数据库悲观锁

利用数据库行锁。


1. 获取锁

SELECT *
FROM stock
WHERE id = 1
FOR UPDATE;

2. 原理

事务提交前:

其他事务无法修改这条记录

例如:

@Transactional
public void deductStock(){

    Stock stock =
            stockMapper.selectForUpdate(1L);

    stock.setCount(
            stock.getCount()-1
    );

    stockMapper.update(stock);
}

3. 缺点

性能较差:

锁粒度大
阻塞严重
数据库压力高

八、ZooKeeper实现分布式锁

原理:

临时顺序节点

例如:

/lock/order0001
/lock/order0002
/lock/order0003

最小节点获得锁:

order0001

删除后:

order0002

获得锁。

常见框架:

Apache Curator

优点:

一致性强
可靠

缺点:

实现复杂
性能低于Redis

九、各种方案对比

方案 性能 实现难度 推荐程度
synchronized ★★★★★ 简单 单机
ReentrantLock ★★★★★ 简单 单机
数据库唯一索引 ★★ 简单 小项目
数据库悲观锁 ★★ 简单 一般
数据库乐观锁 ★★★★ 简单 库存扣减
ZooKeeper ★★★ 复杂 大型系统
Redis+Lua ★★★★★ 中等 推荐
Redisson ★★★★★ 简单 生产推荐

十、单机环境如何实现锁

如果系统没有部署多个实例:

只有一个JVM

那么根本不需要分布式锁。


1. synchronized

public synchronized void createOrder() {

}

或者:

private final Object lock = new Object();

synchronized(lock){

}

特点:

JDK内置
自动释放
简单

2. ReentrantLock

private final ReentrantLock lock =
        new ReentrantLock();

public void createOrder(){

    lock.lock();

    try{

        //业务逻辑

    }finally{

        lock.unlock();
    }
}

优点:

可重入
可中断
支持公平锁
支持超时

3. ReadWriteLock

读多写少场景:

private ReadWriteLock lock =
        new ReentrantReadWriteLock();

读锁:

lock.readLock().lock();

写锁:

lock.writeLock().lock();

特点:

多个读线程同时执行
写线程独占

总结

分布式锁的核心目标只有一句话:

在多个 JVM、多个服务实例同时访问同一资源时,保证同一时刻只有一个节点执行关键业务逻辑。

实际生产中:

  • 单机应用:synchronizedReentrantLock
  • 库存扣减:乐观锁(Version)
  • 中小项目:Redis + Lua
  • 企业级项目:Redisson
  • 强一致性场景:ZooKeeper

学习路径,掌握:

synchronized → ReentrantLock → Redis SET NX EX → Lua释放锁 → Redisson看门狗 → 乐观锁Version → ZooKeeper临时顺序节点

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐