您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

分布式锁的三种实现!

时间:2023-09-13 12:46:12  来源:Java中文社群  作者:

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

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

1、分布式锁要求

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

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

2、实现方案

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

  • 基于数据库实现的分布式锁:可以通过数据库的乐观锁或悲观锁实现分布式锁,但是由于数据库的 IO 操作比较慢,不适合高并发场景。
  • 基于 ZooKeeper 实现的分布式锁:ZooKeeper 是一个高可用性的分布式协调服务,可以通过它来实现分布式锁。但是使用 ZooKeeper 需要部署额外的服务,增加了系统复杂度。
  • 基于 redis 实现的分布式锁:Redis 是一个高性能的内存数据库,支持分布式部署,可以通过Redis的原子操作实现分布式锁,而且具有高性能和高可用性。

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("数据不存在");
    }
}

(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 步:

  1. 引入 Curator 和 ZooKeeper 客户端依赖;
  2. 配置 ZooKeeper 连接信息;
  3. 编写分布式锁实现类。

(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>

(2)配置 ZooKeeper 连接

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

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

(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 实现分布式锁,它的实现步骤如下:

  • 添加 Redisson 依赖
  • 配置 Redisson 连接信息
  • 编写分布式锁代码类

(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>

(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}"

(3)编写分布式锁代码类

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 都可以用来实现分布式锁,它们在实现分布式锁的机制和原理上有所不同,具体区别如下:

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

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

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

小结

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



Tags:分布式锁   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除。
▌相关推荐
分布式锁是一种用于保证分布式系统中多个进程或线程同步访问共享资源的技术。同时它又是面试中的常见问题,所以我们本文就重点来看分布式锁的具体实现(含实现代码)。在分布式系...【详细内容】
2023-09-13  Tags: 分布式锁  点击:(0)  评论:(0)  加入收藏
我们将分布式锁基于缓存扩展了一版,也就是说本starter即有分布式缓存功能,又有分布式锁功能。而注解版的分布式锁能够解决大多数场景的并核问题,小粒度的Lock锁方式补全其他场...【详细内容】
2023-08-28  Tags: 分布式锁  点击:(37)  评论:(0)  加入收藏
这个玩意儿问的太多,现在很多面试官已经避开了这个面试题我这里也不讲太多,而是在实现上做一些总结和分析看看有没有帮助 1. 什么是分布式锁?锁大家已经很清楚了,就是避免线程竞...【详细内容】
2023-07-12  Tags: 分布式锁  点击:(13)  评论:(0)  加入收藏
现在很多码农都有接触到Redis和ZK,那么Redis和ZK实现分布式锁的区别有哪些呢?由于本地锁的作用范围只限于当前应用的线程。高并发场景下,集群中某个应用的本地锁并不会对其它应...【详细内容】
2023-05-23  Tags: 分布式锁  点击:(131)  评论:(0)  加入收藏
一、 分布式锁简介分布式锁是一种常见的协调分布式系统的机制,在分布式环境下保证数据的一致性和可用性。分布式锁的实现有很多种方式,其中较为常见的方式是利用Redis实现分布...【详细内容】
2023-04-12  Tags: 分布式锁  点击:(135)  评论:(0)  加入收藏
一、背景在《​​# 分布式锁上-初探​​》中有提到一个分布式锁应具备的功能特点中有避免死锁这一条:如果某个客户端获得锁之后处理时间超过最大约定时间,或者持锁期间内发生...【详细内容】
2023-03-07  Tags: 分布式锁  点击:(81)  评论:(0)  加入收藏
关于如何实现分布式锁,大家可能对基于Redis​实现比较熟悉,但是往往很多情况是一些并发量不大的项目用不上Redis,Redis往往适用于并发量比较大的场景。但是MySQL基本都是有的,所...【详细内容】
2023-03-06  Tags: 分布式锁  点击:(96)  评论:(0)  加入收藏
什么是分布式锁,分布式锁应用在哪些业务场景、如何来实现分布式锁呢?今天来详解Redis分布式锁的实现@mikechen分布式锁的由来在开始讲分布式锁之前,有必要简单介绍一下,为什么...【详细内容】
2022-12-28  Tags: 分布式锁  点击:(149)  评论:(0)  加入收藏
仓库地址:https://gitee.com/J_look/ssm-zookeeper/blob/master/README.md 锁:我们在多线程中接触过,作用就是让当前的资源不会被其他线程访问! 我的日记本,不可以被别人看到。所...【详细内容】
2022-07-20  Tags: 分布式锁  点击:(202)  评论:(0)  加入收藏
在后端并不是写完一个接口的业务逻辑就能投入使用的,接口的优化更是一个难点与麻烦之处(下面的内容我们不考虑前端的处理,因为不能完全靠前端,前后端都需要做自己的处理工作)1.幂...【详细内容】
2022-07-05  Tags: 分布式锁  点击:(360)  评论:(0)  加入收藏
▌简易百科推荐
分布式锁是一种用于保证分布式系统中多个进程或线程同步访问共享资源的技术。同时它又是面试中的常见问题,所以我们本文就重点来看分布式锁的具体实现(含实现代码)。在分布式系...【详细内容】
2023-09-13    Java中文社群  Tags:分布式锁   点击:(0)  评论:(0)  加入收藏
作者 | David Linthicum策划 | 言征 从数据可用性、安全性到模型选择和监控,生成式AI的加入便意味着要重新审视云架构。 所以,如果在构建一个云架构同时也在设计生成式AI驱动...【详细内容】
2023-09-13  David Linthicum    Tags:云架构   点击:(1)  评论:(0)  加入收藏
本文主要介绍BSTS模型原理以及CausalImpact对模型的代码实现,旨在面对一些具有特定周期性特点的数据时,更精准科学地进行因果效应值的估计。作者简介Yiwen,携程数据分析师,专注...【详细内容】
2023-09-12  携程技术    Tags:结构模型   点击:(3)  评论:(0)  加入收藏
人工智能(AI)和机器学习(Machine Learning)的崛起正在深刻地改变着各行各业。随着数据量的不断增大和计算能力的提升,利用AI和机器学习来做出智能决策已经成为企业和组织的关键战...【详细内容】
2023-09-11  高级互联网架构    Tags:架构   点击:(4)  评论:(0)  加入收藏
在这个程序中我们还可以让这个程序成为一个受应用服务管理的Servlet程序。可以将注解改成@WebServlet("/others/servlet")。只是换成这个注解还并不能生效,还需要在启动类(任...【详细内容】
2023-09-08  Springboot实战案例锦集    Tags:Spring   点击:(4)  评论:(0)  加入收藏
1.背景小红书是以年轻人为主的生活记录、分享平台,用户可以通过短视频、图文等形式记录生活点滴,分享生活方式。在小红书的社交领域里,我们有用户、笔记、商品等实体,这些实体之...【详细内容】
2023-09-08    小红书技术REDtech  Tags:REDtao   点击:(9)  评论:(0)  加入收藏
开发环境:JDK1.8+SpringBoot2.4.12+Oracle这里我们假设要使用两个数据源分别为:master和slave。 pom.xml 依赖包<dependencies> <dependency> <groupId>org.springframe...【详细内容】
2023-09-07    Springboot实战案例锦集  Tags:SpringBoot   点击:(8)  评论:(0)  加入收藏
前言本文主要是简单的讲述了Spring的事件机制,基本概念,讲述了事件机制的三要素事件、事件发布、事件监听器。如何实现一个事件机制,应用的场景,搭配@Async注解实现异步的操作等...【详细内容】
2023-09-07      Tags:Spring   点击:(11)  评论:(0)  加入收藏
作者 | 波哥审校 | 重楼在当今互联网时代,技术的发展日新月异。为了满足用户对高性能、高并发、高可靠性的需求,开发人员必须不断探索新的编程范式和架构。在这方面,异步编程和...【详细内容】
2023-09-06    51CTO  Tags:异步编程   点击:(24)  评论:(0)  加入收藏
1 Scope作用通过@Scope注解可以指定Bean的作用域,默认情况都是单例的( ConfigurableBeanFactory.SCOPE_SINGLETON=singleton)在创建bean实例时就是根据当前定义BeanDefinition...【详细内容】
2023-09-05  Springboot实战案例锦集    Tags:Spring   点击:(21)  评论:(0)  加入收藏
站内最新
站内热门
站内头条