分布式锁的3种实现!附代码

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 分布式锁的3种实现!附代码

分布式锁是一种用于保证分布式系统中多个进程或线程同步访问共享资源的技术。同时它又是面试中的常见问题,所以我们本文就重点来看分布式锁的具体实现(含实现代码)。

在分布式系统中,由于各个节点之间的网络通信延迟、故障等原因,可能会导致数据不一致的问题。分布式锁通过协调多个节点的行为,保证在任何时刻只有一个节点可以访问共享资源,以避免数据的不一致性和冲突。

1.分布式锁要求

分布式锁通常需要满足以下几个要求:

  1. 互斥性:在任意时刻只能有一个客户端持有锁。
  2. 不会发生死锁:即使持有锁的客户端发生故障,也能保证锁最终会被释放。
  3. 具有容错性:分布式锁需要能够容忍节点故障等异常情况,保证系统的稳定性。

    2.实现方案

    在 Java 中,实现分布式锁的方案有多种,包括:

  4. 基于数据库实现的分布式锁:可以通过数据库的乐观锁或悲观锁实现分布式锁,但是由于数据库的 IO 操作比较慢,不适合高并发场景。

  5. 基于 ZooKeeper 实现的分布式锁:ZooKeeper 是一个高可用性的分布式协调服务,可以通过它来实现分布式锁。但是使用 ZooKeeper 需要部署额外的服务,增加了系统复杂度。
  6. 基于 Redis 实现的分布式锁:Redis 是一个高性能的内存数据库,支持分布式部署,可以通过Redis的原子操作实现分布式锁,而且具有高性能和高可用性。

    3.数据库分布式锁

    数据库的乐观锁或悲观锁都可以实现分布式锁,下面分别来看。

    3.1 悲观锁

    在数据库中使用 for update 关键字可以实现悲观锁,我们在 Mapper 中添加 for update 即可对数据加锁,实现代码如下:

    <!-- UserMapper.xml -->
    <select id="selectByIdForUpdate" resultType="User">
     SELECT * FROM user WHERE id = #{id} FOR UPDATE
    </select>
    

    在 Service 中调用 Mapper 方法,即可获取到加锁的数据:

    @Transactional
    public void updateWithPessimisticLock(int id, String name) {
         
     User user = userMapper.selectByIdForUpdate(id);
     if (user != null) {
         
         user.setName(name);
         userMapper.update(user);
     } else {
         
         throw new RuntimeException("数据不存在");
     }
    }
    

    3.2 乐观锁

    在 MyBatis 中,可以通过给表添加一个版本号字段来实现乐观锁。在 Mapper 中,使用 标签定义更新语句,同时使用 set 标签设置版本号的增量。

    <!-- UserMapper.xml -->
    <update id="updateWithOptimisticLock">
     UPDATE user SET
     name = #{
         name},
     version = version + 1
     WHERE id = #{
         id} AND version = #{
         version}
    </update>
    

    在 Service 中调用 Mapper 方法,需要传入更新数据的版本号。如果更新失败,说明数据已经被其他事务修改,具体实现代码如下:

    @Transactional
    public void updateWithOptimisticLock(int id, String name, int version) {
         
     User user = userMapper.selectById(id);
     if (user != null) {
         
         user.setName(name);
         user.setVersion(version);
         int rows = userMapper.updateWithOptimisticLock(user);
         if (rows == 0) {
         
             throw new RuntimeException("数据已被其他事务修改");
         }
     } else {
         
         throw new RuntimeException("数据不存在");
     }
    }
    

    4.Zookeeper 分布式锁

    在 Spring Boot 中,可以使用 Curator 框架来实现 ZooKeeper 分布式锁,具体实现分为以下 3 步:

  7. 引入 Curator 和 ZooKeeper 客户端依赖;

  8. 配置 ZooKeeper 连接信息;
  9. 编写分布式锁实现类。

    4.1 引入 Curator 和 ZooKeeper

    <dependency>
     <groupId>org.apache.curator</groupId>
     <artifactId>curator-framework</artifactId>
     <version>latest</version>
    </dependency>
    <dependency>
     <groupId>org.apache.curator</groupId>
     <artifactId>curator-recipes</artifactId>
     <version>latest</version>
    </dependency>
    <dependency>
     <groupId>org.apache.zookeeper</groupId>
     <artifactId>zookeeper</artifactId>
     <version>latest</version>
    </dependency>
    

    4.2 配置 ZooKeeper 连接

    在 application.yml 中添加 ZooKeeper 连接配置:

    spring:
    zookeeper:
     connect-string: localhost:2181
     namespace: demo
    

    4.3 编写分布式锁实现类

    @Component
    public class DistributedLock {
         
    
     @Autowired
     private CuratorFramework curatorFramework;
    
     /**
      * 获取分布式锁
      *
      * @param lockPath   锁路径
      * @param waitTime   等待时间
      * @param leaseTime  锁持有时间
      * @param timeUnit   时间单位
      * @return 锁对象
      * @throws Exception 获取锁异常
      */
     public InterProcessMutex acquire(String lockPath, long waitTime, long leaseTime, TimeUnit timeUnit) throws Exception {
         
         InterProcessMutex lock = new InterProcessMutex(curatorFramework, lockPath);
         if (!lock.acquire(waitTime, timeUnit)) {
         
             throw new RuntimeException("获取分布式锁失败");
         }
         if (leaseTime > 0) {
         
             lock.acquire(leaseTime, timeUnit);
         }
         return lock;
     }
    
     /**
      * 释放分布式锁
      *
      * @param lock 锁对象
      * @throws Exception 释放锁异常
      */
     public void release(InterProcessMutex lock) throws Exception {
         
         if (lock != null) {
         
             lock.release();
         }
     }
    }
    

    5.Redis 分布式锁

    我们可以使用 Redis 客户端 Redisson 实现分布式锁,它的实现步骤如下:

  10. 添加 Redisson 依赖

  11. 配置 Redisson 连接信息
  12. 编写分布式锁代码类

    5.1 添加 Redisson 依赖

    在 pom.xml 中添加如下配置:
    <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
    <dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.20.0</version>
    </dependency>
    

    5.2 配置 Redisson 连接

    在 Spring Boot 项目的配置文件 application.yml 中添加 Redisson 配置:
    spring:
    data:
     redis:
       host: localhost
       port: 6379
       database: 0
    redisson:
    codec: org.redisson.codec.JsonJacksonCodec
    single-server-config:
     address: "redis://${spring.data.redis.host}:${spring.redis.port}"
     database: "${spring.data.redis.database}"
     password: "${spring.data.redis.password}"
    

    5.3 编写分布式锁代码类

    ```java
    import jakarta.annotation.Resource;
    import org.redisson.Redisson;
    import org.redisson.api.RLock;
    import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class RedissonLockService {
@Resource
private Redisson redisson;

/**
 * 加锁
 *
 * @param key     分布式锁的 key
 * @param timeout 超时时间
 * @param unit    时间单位
 * @return
 */
public boolean tryLock(String key, long timeout, TimeUnit unit) {
    RLock lock = redisson.getLock(key);
    try {
        return lock.tryLock(timeout, unit);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return false;
    }
}

/**
 * 释放分布式锁
 *
 * @param key 分布式锁的 key
 */
public void unlock(String key) {
    RLock lock = redisson.getLock(key);
    lock.unlock();
}

}
```

6.Redis VS Zookeeper

Redis 和 ZooKeeper 都可以用来实现分布式锁,它们在实现分布式锁的机制和原理上有所不同,具体区别如下:

  1. 数据存储方式:Redis 将锁信息存储在内存中,而 ZooKeeper 将锁信息存储在 ZooKeeper 的节点上,因此 ZooKeeper 需要更多的磁盘空间。
  2. 锁的释放:Redis 的锁是通过设置锁的过期时间来自动释放的,而 ZooKeeper 的锁需要手动释放,如果锁的持有者出现宕机或网络中断等情况,需要等待锁的超时时间才能自动释放。
  3. 锁的竞争机制:Redis 使用的是单机锁,即所有请求都直接连接到同一台 Redis 服务器,容易发生单点故障;而 ZooKeeper 使用的是分布式锁,即所有请求都连接到 ZooKeeper 集群,具有较好的可用性和可扩展性。
  4. 一致性:Redis 的锁是非严格意义下的分布式锁,因为在多台机器上运行多个进程时,由于 Redis 的主从同步可能会存在数据不一致的问题;而 ZooKeeper 是强一致性的分布式系统,保证了数据的一致性。
  5. 性能:Redis 的性能比 ZooKeeper 更高,因为 Redis 将锁信息存储在内存中,而 ZooKeeper 需要进行磁盘读写操作。

总之,Redis 适合实现简单的分布式锁场景,而 ZooKeeper 适合实现复杂的分布式协调场景,也就是 ZooKeeper 适合强一致性的分布式系统。

强一致性是指系统中的所有节点在任何时刻看到的数据都是一致的。ZooKeeper 中的数据是有序的树形结构,每个节点都有唯一的路径标识符,所有节点都共享同一份数据,当任何一个节点对数据进行修改时,所有节点都会收到通知,更新数据,并确保数据的一致性。
在 ZooKeeper 中,强一致性体现在数据的读写操作上。ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议来保证数据的一致性,该协议确保了数据更新的顺序,所有的数据更新都需要经过集群中的大多数节点确认,保证了数据的一致性和可靠性。

小结

在 Java 中,使用数据库、ZooKeeper 和 Redis 都可以实现分布式锁。但数据库 IO 操作比较慢,不适合高并发场景;Redis 执行效率最高,但在主从切换时,可能会出现锁丢失的情况;ZooKeeper 是一个高可用性的分布式协调服务,可以保证数据的强一致性,但是使用 ZooKeeper 需要部署额外的服务,增加了系统复杂度。所以没有最好的解决方案,只有最合适自己的解决方案。

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
22天前
|
NoSQL 关系型数据库 MySQL
分布式锁的原理解析与实现工具介绍
分布式锁的原理解析与实现工具介绍
44 1
|
22天前
|
存储 NoSQL 关系型数据库
聊一聊分布式锁的设计模型
本文介绍了分布式锁的设计模型、运行原理以及具体用法,作者也在文中体现了自己的关于分布式锁的思考以及具体实践。
52616 0
|
22天前
|
存储 NoSQL Java
分布式锁,Redission,其它实现问题讲解,以及面试题回答案例
分布式锁,Redission,其它实现问题讲解,以及面试题回答案例
141 1
|
7月前
|
NoSQL 关系型数据库 MySQL
手把手实现分布式锁
手把手实现分布式锁
50 0
|
11月前
|
NoSQL 算法 Redis
分布式锁的一些细节问题,值得收藏
分布式锁的一些细节问题,值得收藏
|
消息中间件 NoSQL JavaScript
图解 Redis 分布式锁,写得太好了!
图解 Redis 分布式锁,写得太好了!
|
存储 NoSQL 数据可视化
一文讲清楚硬核分布式锁
一文讲清楚硬核分布式锁
117 0
|
NoSQL Java Redis
图解 Redis 分布式锁,写得太好了!(1)
图解 Redis 分布式锁,写得太好了!
图解 Redis 分布式锁,写得太好了!(1)
|
NoSQL Java Redis
图解 Redis 分布式锁,写得太好了!(2)
图解 Redis 分布式锁,写得太好了!
图解 Redis 分布式锁,写得太好了!(2)
|
监控 NoSQL 算法